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>
This commit is contained in:
@@ -0,0 +1,258 @@
|
||||
//===========================================================================//
|
||||
// File: searchlight.cpp //
|
||||
// Project: BattleTech //
|
||||
// Contents: Searchlight (illumination) subsystem //
|
||||
//---------------------------------------------------------------------------//
|
||||
// Copyright (C) 1995, Virtual World Entertainment, Inc. All Rights reserved //
|
||||
// PROPRIETARY AND CONFIDENTIAL //
|
||||
//===========================================================================//
|
||||
//
|
||||
// RECONSTRUCTED from the shipped binary (Ghidra pseudo-C, recovered shard
|
||||
// part_013.c). Cluster @004b84dc..@004b8648. Each method cites its @ADDR.
|
||||
//
|
||||
// Helper-function name mapping:
|
||||
// FUN_004b18a4 PowerWatcher base constructor (powersub.cpp)
|
||||
// FUN_004b198c PowerWatcher::CreateStreamedSubsystem (powersub.cpp)
|
||||
// FUN_004b181c PowerWatcher base per-frame update (powersub.cpp)
|
||||
// FUN_004b179c PowerWatcher::HandleMessage (slot 9) (powersub.cpp)
|
||||
// FUN_004b1804 PowerWatcher::ResetToInitialState (slot10) (powersub.cpp)
|
||||
// FUN_004b1930 ~PowerWatcher (powersub.cpp)
|
||||
// FUN_0041b9ec AlarmIndicator(levels)
|
||||
// FUN_0041baa4 ~AlarmIndicator
|
||||
// FUN_0041bbd8 AlarmIndicator::SetLevel(n)
|
||||
// FUN_0041a1a4 IsDerivedFrom(classDerivations)
|
||||
// FUN_0041bd34 Subsystem::ReadUpdate (base, slot 6)
|
||||
// FUN_0041c500 Subsystem::WriteUpdate (base, slot 7)
|
||||
// FUN_004022d0 operator delete / global free
|
||||
//
|
||||
#include <bt.hpp>
|
||||
#pragma hdrstop
|
||||
|
||||
#if !defined(SEARCHLIGHT_HPP)
|
||||
# include <searchlight.hpp>
|
||||
#endif
|
||||
#if !defined(APP_HPP)
|
||||
# include <app.hpp>
|
||||
#endif
|
||||
#if !defined(TESTBT_HPP)
|
||||
# include <testbt.hpp>
|
||||
#endif
|
||||
|
||||
//###########################################################################
|
||||
// Shared Data Support
|
||||
//
|
||||
Derivation
|
||||
Searchlight::ClassDerivations( // @005111a8
|
||||
PowerWatcher::GetClassDerivations(),
|
||||
"Searchlight"
|
||||
);
|
||||
|
||||
Receiver::MessageHandlerSet
|
||||
Searchlight::MessageHandlers; // includes { "ToggleLamp", &Searchlight::ToggleLamp } @00511210
|
||||
|
||||
Searchlight::AttributeIndexSet
|
||||
Searchlight::AttributeIndex;
|
||||
|
||||
Searchlight::SharedData
|
||||
Searchlight::DefaultData( // @00511198
|
||||
&Searchlight::ClassDerivations,
|
||||
Searchlight::MessageHandlers,
|
||||
Searchlight::AttributeIndex,
|
||||
Searchlight::StateCount
|
||||
);
|
||||
|
||||
//###########################################################################
|
||||
// Construction -- @004b84dc
|
||||
//
|
||||
// Chains to the PowerWatcher ctor (FUN_004b18a4) with &DAT_00511198, installs
|
||||
// the Searchlight vtable (PTR_FUN_005114d4), builds the 2-level state
|
||||
// AlarmIndicator (@0x1E4) and seeds commandedOn from the segment field
|
||||
// (+0x28). When this is a live (non-copy) segment with the has-performance
|
||||
// flag set it registers SearchlightSimulation (this[7..9] <- PTR_FUN_00511200);
|
||||
// otherwise it marks the instance "no per-frame performance" (flag bit 2).
|
||||
//
|
||||
Searchlight::Searchlight(
|
||||
Mech *owner,
|
||||
int subsystem_ID,
|
||||
SubsystemResource *subsystem_resource,
|
||||
SharedData &shared_data
|
||||
):
|
||||
PowerWatcher(owner, subsystem_ID, subsystem_resource, shared_data)
|
||||
{
|
||||
Check(owner);
|
||||
Check_Pointer(subsystem_resource);
|
||||
|
||||
segmentFlags = 0;
|
||||
hostShutDown = False;
|
||||
watchedVoltageLevel = 4; // Ready
|
||||
heatStateLevel = 0; // NormalHeat
|
||||
controlsAllowLights = True;
|
||||
graphicsDirty = False;
|
||||
instanceFlags = 0;
|
||||
|
||||
lightState = 0; // @0x1D8
|
||||
requestedOn = 0; // @0x1E0
|
||||
stateAlarm.Initialize(2); // FUN_0041b9ec(this+0x1E4, 2)
|
||||
commandedOn = subsystem_resource->segmentIndex; // @0x1DC <- inherited resource +0x28
|
||||
|
||||
if (((GetSegmentFlags() & 0xC) == 0) &&
|
||||
((GetSegmentFlags() & 0x100) != 0))
|
||||
{
|
||||
SetPerformance(&Searchlight::SearchlightSimulation); // this[7..9] <- PTR_FUN_00511200
|
||||
}
|
||||
else
|
||||
{
|
||||
SetInstanceFlag(2); // this[10] |= 2 (no active performance)
|
||||
}
|
||||
|
||||
Check_Fpu();
|
||||
}
|
||||
|
||||
//
|
||||
// Destruction -- @004b8568 (vtable slot0). Reinstalls the vtable, tears down
|
||||
// the AlarmIndicator (@0x1E4), chains to ~PowerWatcher (FUN_004b1930), frees
|
||||
// the block when the deleting-dtor bit is set.
|
||||
//
|
||||
Searchlight::~Searchlight()
|
||||
{
|
||||
Check(this);
|
||||
stateAlarm.Finalize(); // FUN_0041baa4(this+0x1E4, 2)
|
||||
Check_Fpu();
|
||||
}
|
||||
|
||||
//###########################################################################
|
||||
// TestInstance -- @004b85f0
|
||||
//
|
||||
Logical
|
||||
Searchlight::TestInstance() const
|
||||
{
|
||||
return IsDerivedFrom(ClassDerivations); // FUN_0041a1a4(**this[3], 0x005111a8)
|
||||
}
|
||||
|
||||
Logical
|
||||
Searchlight::TestClass(Mech &)
|
||||
{
|
||||
return True; // BEST-EFFORT (family convention)
|
||||
}
|
||||
|
||||
//###########################################################################
|
||||
// ToggleLamp -- @004b860c ("ToggleLamp" message handler)
|
||||
//
|
||||
// Bound by the shared-data message table @00511210. On a positive command
|
||||
// (message arg @0xC > 0) and when the host mech's controls allow it
|
||||
// (mech +0xD0 -> +0x190 -> +0x25C != 0), flips commandedOn (@0x1DC).
|
||||
//
|
||||
Logical
|
||||
Searchlight::ToggleLamp(Message &message)
|
||||
{
|
||||
if ((0 < MessageArg(message)) && // *(message + 0xC)
|
||||
ControlsAllowLights()) // *(*(owner +0xD0)+0x190)+0x25C
|
||||
{
|
||||
commandedOn = (commandedOn == 0); // toggle @0x1DC
|
||||
}
|
||||
return True;
|
||||
}
|
||||
|
||||
//###########################################################################
|
||||
// SearchlightSimulation -- @004b841c (Performance)
|
||||
//
|
||||
// Gates the reported light state by power and heat:
|
||||
// * base tick (FUN_004b181c).
|
||||
// * if the host is shut down (this[0x40] == 1) the light is forced off,
|
||||
// else it follows requestedOn (@0x1E0).
|
||||
// * the watched-voltage alarm level (@0x198) must read Ready(4) for the
|
||||
// light to stay lit; otherwise it blanks.
|
||||
// * the heat-state level (@0x140): <2 keeps the light, ==2 blanks it.
|
||||
// * pushes the resulting on/off into the state AlarmIndicator (@0x1E4) and,
|
||||
// if it changed, raises the subsystem "graphics dirty" bit (this[0x18] |= 1).
|
||||
//
|
||||
void
|
||||
Searchlight::SearchlightSimulation(Scalar time_slice)
|
||||
{
|
||||
Check(this);
|
||||
|
||||
PowerWatcher::Simulation(time_slice); // FUN_004b181c (base per-frame update)
|
||||
int previous = lightState; // @0x1D8
|
||||
|
||||
lightState = (HostShutDown()) ? 0 : requestedOn; // this[0x40]==1 ? 0 : @0x1E0
|
||||
|
||||
if (WatchedVoltageLevel() == 4) // @0x198 == Ready
|
||||
lightState = (lightState != 0);
|
||||
else
|
||||
lightState = 0;
|
||||
|
||||
if (HeatStateLevel() < 2) // @0x140
|
||||
lightState = (lightState != 0);
|
||||
else if (HeatStateLevel() == 2)
|
||||
lightState = 0;
|
||||
|
||||
stateAlarm.SetLevel(lightState); // FUN_0041bbd8(this+0x1E4, lightState)
|
||||
|
||||
if (previous != lightState)
|
||||
SetGraphicsDirty(); // this[0x18] |= 1
|
||||
|
||||
Check_Fpu();
|
||||
}
|
||||
|
||||
//###########################################################################
|
||||
// ReadUpdate / WriteUpdate -- network state replication
|
||||
//
|
||||
// slot 6 @004b83b8: chain base ReadUpdate (FUN_0041bd34), then take the
|
||||
// replicated on/off (packet +0x10) as lightState (@0x1D8) and drive the
|
||||
// alarm (@0x1E4).
|
||||
// slot 7 @004b83f0: chain base WriteUpdate (FUN_0041c500), then stamp the
|
||||
// record kind 0x14 and write lightState into record field [4].
|
||||
//
|
||||
// The decomp's NetworkPacket.value / NetworkRecord{kind,value} do not exist on
|
||||
// the engine UpdateRecord (SIMULATE.h). The replicated on/off maps to
|
||||
// UpdateRecord::simulationState and the 0x14 record tag to ::recordID.
|
||||
//
|
||||
void
|
||||
Searchlight::ReadUpdateRecord(UpdateRecord *message)
|
||||
{
|
||||
PowerWatcher::ReadUpdateRecord(message); // inherited Simulation::ReadUpdateRecord
|
||||
lightState = (int)message->simulationState; // @0x1D8 <- replicated on/off
|
||||
stateAlarm.SetLevel(lightState); // FUN_0041bbd8(this+0x1E4, lightState)
|
||||
}
|
||||
|
||||
void
|
||||
Searchlight::WriteUpdateRecord(UpdateRecord *message, int update_model)
|
||||
{
|
||||
PowerWatcher::WriteUpdateRecord(message, update_model); // Subsystem::WriteUpdateRecord
|
||||
message->recordID = 0x14; // record kind tag
|
||||
message->simulationState = lightState; // replicated on/off <- @0x1D8
|
||||
}
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// CreateStreamedSubsystem -- Searchlight @004b85a8
|
||||
//
|
||||
// Chains to PowerWatcher::CreateStreamedSubsystem (FUN_004b198c) then stamps
|
||||
// classID 0x0BD8 (+0x20) and model size 0xF4 (+0x24). No searchlight-specific
|
||||
// fields are read (empty resource extension).
|
||||
//
|
||||
int
|
||||
Searchlight::CreateStreamedSubsystem(
|
||||
NotationFile *model_file,
|
||||
const char *model_name,
|
||||
const char *subsystem_name,
|
||||
SubsystemResource *subsystem_resource,
|
||||
NotationFile *subsystem_file,
|
||||
const ResourceDirectories *directories,
|
||||
int passes
|
||||
)
|
||||
{
|
||||
if (
|
||||
!PowerWatcher::CreateStreamedSubsystem( // FUN_004b198c
|
||||
model_file, model_name, subsystem_name,
|
||||
subsystem_resource, subsystem_file, directories, passes
|
||||
)
|
||||
)
|
||||
{
|
||||
return False;
|
||||
}
|
||||
|
||||
subsystem_resource->subsystemModelSize = 0xF4; // +0x24
|
||||
subsystem_resource->classID = RegisteredClass::SearchlightClassID; // 0x0BD8, +0x20
|
||||
|
||||
return True;
|
||||
}
|
||||
Reference in New Issue
Block a user