Files
BT412/game/reconstructed/thermalsight.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

210 lines
6.9 KiB
C++

//===========================================================================//
// File: thermalsight.cpp //
// Project: BattleTech //
// Contents: ThermalSight (thermal / infra-red vision mode) 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 @004b8718..@004b881c. 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_0045fe44 ToggleGlobalThermalVision (flips _DAT_004f1ab8 "pvision",
// prints "pvision ON/OFF", re-applies the view palette)
// FUN_004022d0 operator delete / global free
// PTR_DAT_004fd4b8 the active L4 application/viewport (its +0x48 flag is
// non-zero only when THIS mech is the locally-viewed mech)
//
#include <bt.hpp>
#pragma hdrstop
#if !defined(THERMALSIGHT_HPP)
# include <thermalsight.hpp>
#endif
#if !defined(APP_HPP)
# include <app.hpp>
#endif
#if !defined(TESTBT_HPP)
# include <testbt.hpp>
#endif
//###########################################################################
// Shared Data Support
//
Derivation
ThermalSight::ClassDerivations( // @00511238
PowerWatcher::GetClassDerivations(),
"ThermalSight"
);
Receiver::MessageHandlerSet
ThermalSight::MessageHandlers;
ThermalSight::AttributeIndexSet
ThermalSight::AttributeIndex;
ThermalSight::SharedData
ThermalSight::DefaultData( // @00511228
&ThermalSight::ClassDerivations,
ThermalSight::MessageHandlers,
ThermalSight::AttributeIndex,
ThermalSight::StateCount
);
//###########################################################################
// Construction -- @004b8718
//
// Chains to the PowerWatcher ctor (FUN_004b18a4) with &DAT_00511228, installs
// the ThermalSight vtable (PTR_FUN_00511498), builds the 2-level state
// AlarmIndicator (@0x1E0). When this is a live (non-copy) segment with the
// has-performance flag set it registers ThermalSightSimulation
// (this[7..9] <- PTR_FUN_00511290).
//
ThermalSight::ThermalSight(
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
thermalActive = 0; // @0x1D8
requestedOn = 0; // @0x1DC
stateAlarm.Initialize(2); // FUN_0041b9ec(this+0x1E0, 2)
if (((GetSegmentFlags() & 0xC) == 0) &&
((GetSegmentFlags() & 0x100) != 0))
{
SetPerformance(&ThermalSight::ThermalSightSimulation); // this[7..9] <- PTR_FUN_00511290
}
Check_Fpu();
}
//
// Destruction -- @004b8794 (vtable slot0). Reinstalls the vtable, tears down
// the AlarmIndicator (@0x1E0), chains to ~PowerWatcher (FUN_004b1930), frees
// the block when the deleting-dtor bit is set.
//
ThermalSight::~ThermalSight()
{
Check(this);
stateAlarm.Finalize(); // FUN_0041baa4(this+0x1E0, 2)
Check_Fpu();
}
//###########################################################################
// TestInstance -- @004b881c
//
Logical
ThermalSight::TestInstance() const
{
return IsDerivedFrom(ClassDerivations); // FUN_0041a1a4(**this[3], 0x00511238)
}
Logical
ThermalSight::TestClass(Mech &)
{
return True; // BEST-EFFORT (family convention)
}
//###########################################################################
// ThermalSightSimulation -- @004b8648 (Performance)
//
// Same power/heat gating as the Searchlight, but the watched quantity is the
// thermal-vision mode, and when the active state CHANGES on the locally-viewed
// mech it toggles the global "pvision" render flag:
// * base tick (FUN_004b181c).
// * thermalActive follows requestedOn (@0x1DC) unless the host is shut down
// (this[0x40] == 1), in which case it is forced off.
// * the watched-voltage alarm level (@0x198) must read Ready(4); else blank.
// * heat-state level (@0x140): <2 keeps it, ==2 blanks it.
// * push on/off into the AlarmIndicator (@0x1E0).
// * if the state changed AND this mech is the one being viewed
// (PTR_DAT_004fd4b8 -> +0x48 != 0), call ToggleGlobalThermalVision
// (FUN_0045fe44) to flip the cockpit IR view.
//
void
ThermalSight::ThermalSightSimulation(Scalar time_slice)
{
Check(this);
PowerWatcher::Simulation(time_slice); // FUN_004b181c (base per-frame update)
int previous = thermalActive; // @0x1D8
thermalActive = (HostShutDown()) ? 0 : requestedOn; // this[0x40]==1 ? 0 : @0x1DC
if (WatchedVoltageLevel() == 4) // @0x198 == Ready
thermalActive = (thermalActive != 0);
else
thermalActive = 0;
if (HeatStateLevel() < 2) // @0x140
thermalActive = (thermalActive != 0);
else if (HeatStateLevel() == 2)
thermalActive = 0;
stateAlarm.SetLevel(thermalActive); // FUN_0041bbd8(this+0x1E0, thermalActive)
if ((previous != thermalActive) && (LocalViewport()->viewingThisMech != 0))
{
ToggleGlobalThermalVision(); // FUN_0045fe44
}
Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// CreateStreamedSubsystem -- ThermalSight @004b87d4
//
// Chains to PowerWatcher::CreateStreamedSubsystem (FUN_004b198c) then stamps
// classID 0x0BDE (+0x20) and model size 0xF4 (+0x24). No thermal-specific
// fields are read.
//
int
ThermalSight::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::ThermalSightClassID; // 0x0BDE, +0x20
return True;
}