Files
BT411/engine/MUNGA/DIRECTOR.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

310 lines
6.9 KiB
C++

#include "munga.h"
#pragma hdrstop
#include "director.h"
#include "mission.h"
#include "player.h"
#include "app.h"
#include "hostmgr.h"
#include "nttmgr.h"
//#############################################################################
// Shared Data support
//
Derivation* CameraDirector::GetClassDerivations()
{ static Derivation classDerivations(Player::GetClassDerivations(), "CameraDirector");
return &classDerivations;
}
CameraDirector::SharedData
CameraDirector::DefaultData(
CameraDirector::GetClassDerivations(),
CameraDirector::MessageHandlers,
CameraDirector::GetAttributeIndex(),
CameraDirector::StateCount,
(Entity::MakeHandler) CameraDirector::Make
);
//#############################################################################
// Messaging Support
//
const Receiver::HandlerEntry
CameraDirector::MessageHandlerEntries[]=
{
MESSAGE_ENTRY(CameraDirector, AttachCameraShip)
};
CameraDirector::MessageHandlerSet
CameraDirector::MessageHandlers(
ELEMENTS(CameraDirector::MessageHandlerEntries),
CameraDirector::MessageHandlerEntries,
Player::GetMessageHandlers()
);
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
CameraDirector::AttachCameraShipMessageHandler(
AttachCameraShipMessage *in_message
)
{
Check(in_message);
Check(application);
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Get the cameraship ID to attach to
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
HostManager *host = application->GetHostManager();
Check(host);
Entity *entity_ptr = host->GetEntityPointer(in_message->cameraShipID);
Check(entity_ptr);
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Get the cameraShip Pointer Object
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
CameraShip *camera_ship;
camera_ship = Cast_Object(CameraShip*, entity_ptr);
Check(this);
Check(camera_ship);
cameraShip = camera_ship;
}
//#############################################################################
// Model Support
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
CameraDirector::UpdateHUD(
Scalar time_slice
)
{
Check(this);
Check(application);
//
//~~~~~~~~~~~~
// HUD Support
//~~~~~~~~~~~~
//
if (GetGoalEntity())
{
Check(GetGoalEntity());
Player *goal_player = GetGoalEntity()->GetPlayerLink();
if (goal_player)
{
Check(goal_player);
goalPlayerIndex = goal_player->playerBitmapIndex;
}
}
if (
application->GetApplicationState() == Application::StoppingMission ||
application->GetApplicationState() == Application::EndingMission
)
{
displayRankingWindow = False;
return;
}
else if (application->GetSecondsRemainingInGame() <= 30.0f)
{
displayRankingWindow = True;
}
else
{
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Check times for Ranking Window Display
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
if (displayRankingWindow)
{
timeDisplayed += time_slice;
if(timeDisplayed >= displayIntervalOn)
{
timeTillDisplayed = displayIntervalOff;
displayRankingWindow = False;
}
}
else
{
timeTillDisplayed -= time_slice;
if (timeTillDisplayed <= 0.0f)
{
timeDisplayed = 0.0f;
displayRankingWindow = True;
}
}
}
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
CameraDirector::BeADirector(Scalar time_slice)
{
Check(this);
Player::PlayerSimulation(time_slice);
UpdateHUD(time_slice);
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Time To Cut To a new Player?
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
if (
timeLeftOnPlayer > 0.0f
&& application->GetApplicationState() == Application::RunningMission)
{
timeLeftOnPlayer -= time_slice;
return;
}
Player *top_dog = FindPlayerByRank(0);
if (top_dog)
{
Check(top_dog);
Entity *vehicle = top_dog->GetPlayerVehicle();
if (vehicle)
{
Check(vehicle);
if ((GetGoalEntity() != vehicle))
{
SetGoalEntity(vehicle);
timeLeftOnPlayer = 10.0f;
Check(cameraShip);
CameraShip::DirectionMessage direction_message(
CameraShip::DirectionMessageID,
sizeof(CameraShip::DirectionMessage),
GetGoalEntity()->GetEntityID(),
0.0f,
Point3D::Identity
);
Check(cameraShip);
cameraShip->Dispatch(&direction_message);
}
}
}
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
CameraDirector::CreateCameraShip(Scalar)
{
CreatePlayerVehicle(localOrigin);
PlayerLinkMessage player_link_message (
PlayerLinkMessageID,
sizeof(PlayerLinkMessage),
this->GetEntityID()
);
Check(playerVehicle);
playerVehicle->Dispatch(&player_link_message);
playerVehicle->DispatchToReplicants(&player_link_message);
SetPerformance(&CameraDirector::BeADirector);
deathCount = 0;
CameraDirector::AttachCameraShipMessage
message(
CameraDirector::AttachCameraShipMessageID,
sizeof(CameraDirector::AttachCameraShipMessage),
playerVehicle->GetEntityID()
);
Dispatch(&message);
}
//#############################################################################
// Construction and Destruction Support
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
CameraDirector::CameraDirector(
CameraDirector::MakeMessage *creation_message,
CameraDirector::SharedData &virtual_data
) :
Player(creation_message, virtual_data),
goalEntity(this)
{
cameraShip = NULL;
SetValidFlag();
timeLeftOnPlayer = -1.0f;
if (GetInstance() != ReplicantInstance)
{
SetPerformance(&CameraDirector::CreateCameraShip);
}
//
//~~~~~~~~~~~~
// HUD Support
//~~~~~~~~~~~~
//
goalPlayerIndex = -1;
displayRankingWindow = False;
displayIntervalOn = 10.0f;
displayIntervalOff = 15.0f;
timeDisplayed = 0.0f;
timeTillDisplayed = displayIntervalOff;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
CameraDirector*
CameraDirector::Make(MakeMessage *creation_message)
{
return new CameraDirector(creation_message);
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
CameraDirector::~CameraDirector()
{
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Player*
CameraDirector::FindPlayerByRank(int rank_to_find)
{
Check(application);
//
//---------------------------------------
// Get all players from the entity group
//---------------------------------------
//
EntityGroup *player_group =
application->GetEntityManager()->FindGroup("Players");
if(player_group)
{
Player *leading_player;
//
//-----------------------------------------------------
// Iterate through the players find the leading player
//-----------------------------------------------------
//
ChainIteratorOf<Node*> iterator(player_group->groupMembers);
while ((leading_player = (Player*) iterator.ReadAndNext()) != NULL)
{
Check(leading_player);
if (leading_player->playerRanking == rank_to_find)
{
return leading_player;
}
}
}
return NULL;
}
Logical
CameraDirector::TestInstance() const
{
return IsDerivedFrom(*GetClassDerivations());
}