Files
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

230 lines
9.8 KiB
C++

//============================================================================//
// File: projtile.cpp //
// Project: BattleTech //
// Contents: In-flight projectile entity base (Projectile : Entity) //
//----------------------------------------------------------------------------//
// Copyright (C) 1995, Virtual World Entertainment, Inc. All Rights reserved //
// PROPRIETARY AND CONFIDENTIAL //
//============================================================================//
//
// RECONSTRUCTED from the shipped binary. There is NO surviving projtile.cpp --
// only PROJTILE.TCP (Projectile::TestClass). Behaviour follows the Ghidra
// pseudo-C in part_013.c (@004be1bc ctor, @004be358 dtor, @004be384 allocator,
// @004be3b4 TestInstance). Each non-trivial method cites the originating @ADDR.
//
// Coverage:
// confident : ctor @004be1bc (Entity base chain, world/segment registration,
// launch-kinematics snapshot, Performance install), dtor thunk
// @004be358, allocator @004be384, TestInstance @004be3b4
// best-effort: the names of the descriptor-sourced members (+0x300..+0x33C)
// and the modelUpdate binding (+0x250/+0x254); MoveAndCollide
// (the base Performance variants PTR_LAB_005129e8/f4 -- bodies sit
// past the captured window; the live integrator that ships is the
// Missile override @004bef78)
// excluded : the 0x41xxxx/0x42xxxx Entity engine vtable slots and the base
// Entity ctor @004234f0
//
// Helper-function name mapping (engine internals referenced by the decomp):
// FUN_004234f0 Entity base constructor (ctx, default record)
// FUN_0041db7c init embedded connection/descriptor sub-object
// FUN_0041bbd8 AlarmIndicator::SetLevel FUN_0041a1a4 IsDerivedFrom
// FUN_00408440 Point3D / Vector3D copy FUN_00433ed4 motion-source lookup
// FUN_00402298 operator new FUN_004022d0 operator delete
// DAT_004efc94 global game/world context block
//
#include <bt.hpp>
#pragma hdrstop
#if !defined(PROJTILE_HPP)
# include <projtile.hpp>
#endif
#if !defined(TESTBT_HPP)
# include <testbt.hpp>
#endif
//###########################################################################
//###########################################################################
// Projectile
//###########################################################################
//###########################################################################
//#############################################################################
// Decompiler-artifact stand-ins (label/record operands).
//
static char DAT_005129b0[0x340] = { 0 }; // default streamed-entity record
//#############################################################################
// Shared Data Support
//
Derivation
Projectile::ClassDerivations(
Entity::GetClassDerivations(),
"Projectile"
);
Receiver::MessageHandlerSet Projectile::MessageHandlers;
Simulation::AttributeIndexSet Projectile::AttributeIndex;
Projectile::SharedData
Projectile::DefaultData(
&Projectile::ClassDerivations,
Projectile::MessageHandlers,
Projectile::AttributeIndex,
Projectile::StateCount,
&Projectile::Make
);
//
// Entity factory hook (Entity::SharedData requires a MakeHandler). Projectiles
// are spawned through New(), not the generic Make() path, so this is a stub.
//
Entity *Projectile::Make(MakeMessage * /*creation_message*/)
{
return 0;
}
//#############################################################################
// Allocation
//
// @004be384 Projectile::New(spawn_context) -- allocate 0x340 bytes and
// construct with the default streamed-entity record &DAT_005129b0.
//
Projectile *Projectile::New(int spawn_context)
{
Projectile *p = (Projectile *)::operator new(0x340); // FUN_00402298
if (p != 0)
p = new(p) Projectile(spawn_context, DAT_005129b0); // @004be1bc
return p;
}
//#############################################################################
// Construction
//
// @004be1bc Projectile::Projectile(int owner, const char *spawn_descriptor)
//
// 1. Chains the Entity base ctor and stamps the Projectile vtable.
// 2. Initialises the embedded segment/connection sub-object (this+0x30C),
// flags itself as a transient/guided entity (flags |= 0x10) and raises its
// status alarm to level 2 (in-flight).
// 3. Registers itself into the owning world's segment table (the segment is
// resolved from descriptor+0x84 indexing world+0x128) and snapshots the
// launch kinematics out of the spawn descriptor:
// segmentIndex <- descriptor +0x84
// sourceEntity <- descriptor +0x88
// sourceWeapon <- descriptor +0x8C
// launchPosition <- descriptor +0x90 (Point3D)
// launchVelocity <- descriptor +0x9C (Vector3D)
// aimDirection <- descriptor +0xA8 (Vector3D)
// modelIndex <- descriptor +0xB4
// damageRecord <- descriptor +0xB8
// 4. Binds the per-frame model-update callback (this[0x94..0x97],
// PTR_FUN_005129dc) and installs the active Performance: the authoritative
// integrator (PTR_LAB_005129e8) when flags&0xC==4, else the ghost variant
// (PTR_LAB_005129f4). Finally fires vtable slot 14 (initial placement).
//
Projectile::Projectile(int owner, const char *spawn_descriptor)
:
Entity((Entity::MakeMessage *)0, DefaultData) // @004234f0
{
(void)owner;
// vtable installed by the compiler (PTR_FUN_00512a5c)
InitSegmentLink(&this->sourceEntity); // @0041db7c (this+0x30C)
simulationFlags |= 0x10; // transient / projectile
SetStatusLevel(2); // FUN_0041bbd8(this+0xb, 2) -- in flight
// register into the world segment table
world = ResolveWorld(DAT_004efc94); // this[0xC0] @0x300
segmentIndex = *(int *)(spawn_descriptor + 0x84); // this[0xC2] @0x308
currentSegment = *(Segment **)(*(int *)((int)world + 0x128) + segmentIndex * 4); // this[0xC1] @0x304
sourceEntity = *(int *)(spawn_descriptor + 0x88); // this[0xC3] @0x30C
sourceWeapon = *(int *)(spawn_descriptor + 0x8C); // this[0xC4] @0x310
modelIndex = *(int *)(spawn_descriptor + 0xB4); // this[0xCE] @0x338
launchPosition = *(Point3D *)(spawn_descriptor + 0x90); // this[0xC5] @0x314 FUN_00408440
launchVelocity = *(Vector3D *)(spawn_descriptor + 0x9C); // this[0xC8] @0x320 FUN_00408440
aimDirection = *(Vector3D *)(spawn_descriptor + 0xA8); // this[0xCB] @0x32C FUN_00408440
damageRecord = *(int *)(spawn_descriptor + 0xB8); // this[0xCF] @0x33C
// model-update callback context + bound member function pointer triple
modelUpdateContext = ResolveMotionSource(DAT_004efc94); // this[0x94] @0x250 (FUN_00433ed4)
BindModelUpdate(0); // this[0x95..0x97] @0x254 (PTR_FUN_005129dc)
// select the per-frame integrator
if ((simulationFlags & 0xC) == 4)
SetPerformance(/*authoritative*/ &Projectile::MoveAndCollide); // PTR_LAB_005129e8
else
SetPerformance(/*ghost*/ &Projectile::MoveAndCollide); // PTR_LAB_005129f4
// slot 14 (initial placement): a virtual call in the shipped ctor; the
// concrete placement runs through the installed vtable at run time.
}
//#############################################################################
// Destruction
//
// @004be358 ~Projectile() -- restores the Projectile vtable then chains the
// Entity base destructor (which un-registers from the segment table).
//
Projectile::~Projectile()
{
// vtable reset + base ~Entity chaining handled by the compiler.
}
//#############################################################################
// MoveAndCollide -- default per-frame integrator (best-effort)
//
// The base Projectile integrate-and-collide bodies (PTR_LAB_005129e8 /
// 005129f4) lie past the captured decomp window. The concrete behaviour that
// ships for the only instantiated subclass is the Missile override
// (Missile::MoveAndCollide @004bef78): age the projectile, integrate motion
// (FUN_00421b2c) and advance the model (FUN_00421bac), run the world collision
// query (FUN_0042291c) and -- on a hit -- spawn the explosion/Damage entity and
// retire (FUN_0042061c). Declared here to anchor the vtable slot; the body is
// reconstructed structurally.
//
void Projectile::MoveAndCollide(Scalar time_slice)
{
IntegrateMotion(); // FUN_00421b2c
AdvanceModel(time_slice); // FUN_00421bac
Entity *hit = CollisionQuery(time_slice); // FUN_0042291c
if (hit != 0)
Retire(); // FUN_0042061c
}
//#############################################################################
// TestInstance
//
// @004be3b4 IsDerivedFrom(ClassDerivations tag 0x512980).
//
Logical Projectile::TestInstance() const
{
return IsDerivedFrom(Projectile::ClassDerivations);
}
//#############################################################################
// TestClass (PROJTILE.TCP)
//
// Surviving fragment: takes no argument and returns True.
//
Logical Projectile::TestClass()
{
return True;
}
//#############################################################################
// Recovered internal helpers (engine-internal world/segment/motion plumbing).
// These wrap Entity-engine operations the decompiler rendered as raw FUN_*
// calls; reconstructed as structural stubs.
//
void Projectile::InitSegmentLink(void * /*link*/) {}
void Projectile::SetStatusLevel(int /*level*/) {}
void *Projectile::ResolveWorld(void * /*app_context*/) { return 0; }
void *Projectile::ResolveMotionSource(void * /*app_context*/) { return 0; }
void Projectile::BindModelUpdate(void * /*callback*/) {}
void Projectile::IntegrateMotion() {}
void Projectile::AdvanceModel(Scalar /*time_slice*/) {}
Entity *Projectile::CollisionQuery(Scalar /*time_slice*/) { return 0; }
void Projectile::Retire() {}