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>
602 lines
22 KiB
C++
602 lines
22 KiB
C++
//===========================================================================//
|
|
// File: btl4pb.cpp //
|
|
// Project: BattleTech Brick: BattleTech LBE Application //
|
|
// Contents: BTL4PlaybackApplication -- the LBE mission-review / playback //
|
|
// application, and BTSpoolerTask, the background task that pumps //
|
|
// recorded NetworkPackets out of a SpoolFile back through the //
|
|
// network manager so a finished battle can be re-watched. //
|
|
//---------------------------------------------------------------------------//
|
|
// 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 declarations
|
|
// are VERBATIM from the surviving header BTL4PB.HPP; the method bodies are
|
|
// recovered from the Ghidra pseudo-C for the 0x4d3b74-0x4d3f44 cluster
|
|
// (all/part_014.c) and cross-checked against the direct Red Planet analog
|
|
// RP_L4\RPL4PB.cpp (RPSpoolerTask / RPL4PlaybackApplication), which is a line-
|
|
// for-line structural twin.
|
|
//
|
|
// The object file maps onto these recovered functions:
|
|
//
|
|
// BTSpoolerTask --------------------------------- vtable @0051f204
|
|
// @004d3b74 BTSpoolerTask::BTSpoolerTask (ctor)
|
|
// @004d3bb8 BTSpoolerTask::~BTSpoolerTask (scalar-deleting dtor)
|
|
// @004d3b90 BTSpoolerTask::DispatchPacket
|
|
// @004d3ba8 BTSpoolerTask::GetTimeBias (16-byte tail-call thunk)
|
|
//
|
|
// BTL4PlaybackApplication ----------------------- vtable @0051f1a4
|
|
// @004d3be4 BTL4PlaybackApplication::BTL4PlaybackApplication (ctor)
|
|
// @004d3ea0 BTL4PlaybackApplication::~BTL4PlaybackApplication (dtor)
|
|
// @004d40d8 BTL4PlaybackApplication::TestInstance (out of OPT vtable)
|
|
// @004d3c14 BTL4PlaybackApplication::MakeNetworkManager
|
|
// @004d3c34 BTL4PlaybackApplication::Initialize
|
|
// @004d3c44 BTL4PlaybackApplication::LoadBackgroundTasks
|
|
// @004d3d40 BTL4PlaybackApplication::ExecuteForeground
|
|
// @004d3ecc BTL4PlaybackApplication::DispatchPacket
|
|
// @004d3ef8 BTL4PlaybackApplication::RunMissionMessageHandler
|
|
// @004d3f44 BTL4PlaybackApplication::StopMissionMessageHandler
|
|
// (ExecuteBackgroundTask is inherited from L4Application -- the OPT build
|
|
// did not emit a BT override; see note at that method.)
|
|
//
|
|
// Engine-helper name mapping (FUN_ / DAT_ -> intended symbol):
|
|
// FUN_0047c5d8 SpoolerTask::SpoolerTask (base ctor)
|
|
// FUN_0047c734 SpoolerTask::~SpoolerTask (base dtor)
|
|
// FUN_004d34c4 BTL4Application::BTL4Application (base ctor, btl4app.cpp)
|
|
// FUN_004d350c BTL4Application::~BTL4Application (base dtor)
|
|
// FUN_004d3a2c BTL4Application::RunMissionMessageHandler
|
|
// FUN_004d3a94 BTL4Application::StopMissionMessageHandler
|
|
// FUN_0047b468 L4Application::InitializeTillConsole
|
|
// FUN_0047bfc4 L4PlaybackNetworkManager::L4PlaybackNetworkManager (0x28)
|
|
// FUN_00402298 operator new (Make_Pointer)
|
|
// FUN_004022d0 operator delete
|
|
// FUN_0044de1c TaskManager::AddTask(task) (backgroundTasks@+0x64)
|
|
// FUN_0044dd08 ApplicationTask::ApplicationTask(priority)
|
|
// FUN_0041d7f0 NetworkManager::GetNetworkClientPointer(clientID)
|
|
// FUN_00414b60 Now() -> Time*
|
|
// FUN_0044f850 SpoolFile::NextPacket
|
|
// FUN_0044e13c Application::GetApplicationManager
|
|
// FUN_0044fac0 MissionReviewApplicationManager::ReleaseSpoolFile
|
|
// FUN_00403ad0 Group::FindGroup(root, name)
|
|
// FUN_0042efe4 CameraDirector::FindPlayerByRank(rank)
|
|
// FUN_00421414/00421452 ChainIteratorOf<Node*> ctor/dtor
|
|
// FUN_0049fb74 Mech::Reset(origin, MissionReviewReset)
|
|
// FUN_00419cb8 Entity::FlushEvents
|
|
// FUN_00436668/004364e4 rebuild/replace name bitmap entry
|
|
// FUN_00460840 DPLRenderer::SortAndReloadNameBitmaps
|
|
// FUN_0045a7a8 DPLRenderer::SetViewAngle
|
|
// FUN_0041a1a4 IsDerivedFrom(classDerivations)
|
|
// DAT_004efc94 the global Application*
|
|
// DAT_004efc98 Exit_Code
|
|
// DAT_004fd550 the global mission-review mode (== 2 -> interactive review)
|
|
//
|
|
// Observed Application sub-object byte offsets (this == Application*):
|
|
// +0x18 spoolFile (inherited L4Application member; SetSpoolFile)
|
|
// +0x20 networkManager
|
|
// +0x24 entityManager
|
|
// +0x34 updateManager
|
|
// +0x38 rendererManager
|
|
// +0x3c controlsManager
|
|
// +0x40 intercomManager
|
|
// +0x48 videoRenderer (DPLRenderer)
|
|
// +0x58 secondsRemainingInGame +0x5c gameStarted
|
|
// +0x64 backgroundTasks
|
|
// +0x70 executeFrames +0x88 applicationState
|
|
// +0xc8 currentMission +0xd4 timeBias (BTL4PlaybackApplication)
|
|
//
|
|
|
|
#include <bt.hpp>
|
|
#pragma hdrstop
|
|
|
|
#if !defined(BTL4PB_HPP)
|
|
# include <btl4pb.hpp>
|
|
#endif
|
|
#if !defined(L4VIDEO_HPP)
|
|
# include <l4video.hpp>
|
|
#endif
|
|
#if !defined(MISSION_HPP)
|
|
# include <mission.hpp>
|
|
#endif
|
|
#if !defined(L4NET_HPP)
|
|
# include <l4net.hpp>
|
|
#endif
|
|
#if !defined(UPDATE_HPP)
|
|
# include <update.hpp>
|
|
#endif
|
|
#if !defined(RENDERER_HPP)
|
|
# include <renderer.hpp>
|
|
#endif
|
|
#if !defined(ICOM_HPP)
|
|
# include <icom.hpp>
|
|
#endif
|
|
#if !defined(DROPZONE_HPP)
|
|
# include <dropzone.hpp>
|
|
#endif
|
|
#if !defined(DIRECTOR_HPP)
|
|
# include <director.hpp>
|
|
#endif
|
|
#if !defined(NTTMGR_HPP)
|
|
# include <nttmgr.hpp> // EntityManager / EntityGroup / FindGroup
|
|
#endif
|
|
#if !defined(MECH_HPP)
|
|
# include <mech.hpp>
|
|
#endif
|
|
|
|
//#############################################################################
|
|
//########################## BTSpoolerTask ##############################
|
|
//#############################################################################
|
|
|
|
//
|
|
//#############################################################################
|
|
// @004d3b74 -- chains to SpoolerTask() (FUN_0047c5d8) then stamps the
|
|
// BTSpoolerTask vtable (&0051f204). Adds no data of its own.
|
|
//#############################################################################
|
|
//
|
|
BTSpoolerTask::BTSpoolerTask()
|
|
{
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// @004d3bb8 -- scalar-deleting destructor; reinstalls the vtable and chains
|
|
// to ~SpoolerTask (FUN_0047c734).
|
|
//#############################################################################
|
|
//
|
|
BTSpoolerTask::~BTSpoolerTask()
|
|
{
|
|
Check_Fpu();
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// @004d3b90 -- the spooler's per-packet callback simply forwards to the
|
|
// running playback application (the global Application*), exactly as the Red
|
|
// Planet RPSpoolerTask::DispatchPacket does.
|
|
//#############################################################################
|
|
//
|
|
void
|
|
BTSpoolerTask::DispatchPacket(NetworkPacket *packet)
|
|
{
|
|
BTL4PlaybackApplication *playback_app =
|
|
Cast_Object(BTL4PlaybackApplication*, application);
|
|
playback_app->DispatchPacket(packet);
|
|
Check_Fpu();
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// @004d3ba8 -- 16-byte tail-call thunk: returns the application's playback
|
|
// time bias so the spooler can re-time recorded packets.
|
|
//#############################################################################
|
|
//
|
|
long
|
|
BTSpoolerTask::GetTimeBias()
|
|
{
|
|
BTL4PlaybackApplication *playback_app =
|
|
Cast_Object(BTL4PlaybackApplication*, application);
|
|
return playback_app->GetTimeBias();
|
|
}
|
|
|
|
|
|
//#############################################################################
|
|
//####################### BTL4PlaybackApplication #######################
|
|
//#############################################################################
|
|
|
|
//#############################################################################
|
|
// Message Support
|
|
//
|
|
const Receiver::HandlerEntry
|
|
BTL4PlaybackApplication::MessageHandlerEntries[]=
|
|
{
|
|
MESSAGE_ENTRY(BTL4PlaybackApplication, RunMission), // handler @004d3ef8
|
|
MESSAGE_ENTRY(BTL4PlaybackApplication, StopMission) // handler @004d3f44
|
|
};
|
|
|
|
Receiver::MessageHandlerSet&
|
|
BTL4PlaybackApplication::GetMessageHandlers()
|
|
{
|
|
static Receiver::MessageHandlerSet messageHandlers(
|
|
ELEMENTS(BTL4PlaybackApplication::MessageHandlerEntries),
|
|
BTL4PlaybackApplication::MessageHandlerEntries,
|
|
BTL4Application::GetMessageHandlers() // inherited Application set
|
|
);
|
|
return messageHandlers;
|
|
}
|
|
|
|
//#############################################################################
|
|
// Shared Data Support
|
|
//
|
|
Derivation*
|
|
BTL4PlaybackApplication::GetClassDerivations() // @0051ee2c
|
|
{
|
|
static Derivation classDerivations(
|
|
BTL4Application::GetClassDerivations(),
|
|
"BTL4PlaybackApplication" // @0051f182
|
|
);
|
|
return &classDerivations;
|
|
}
|
|
|
|
BTL4PlaybackApplication::SharedData
|
|
BTL4PlaybackApplication::DefaultData( // @0051ee5c
|
|
BTL4PlaybackApplication::GetClassDerivations(),
|
|
BTL4PlaybackApplication::GetMessageHandlers()
|
|
);
|
|
|
|
//
|
|
//#############################################################################
|
|
// BTL4PlaybackApplication @004d3be4
|
|
//#############################################################################
|
|
//
|
|
// Base call FUN_004d34c4(this, resource, 0x3eb, DefaultData) in the DOS binary:
|
|
// BattleTech's BTL4Application ctor there took (ResourceFile*, ClassID,
|
|
// SharedData&) -- the DOS build carried no HINSTANCE/HWND. 0x3eb is the
|
|
// L4ApplicationClassID. For the WinTesla port the L4Application base ctor
|
|
// requires (HINSTANCE, HWND, ...), so those are threaded through here exactly
|
|
// as RPL4PlaybackApplication does. The spool file is stashed in the inherited
|
|
// spoolFile slot (this+0x18).
|
|
//
|
|
BTL4PlaybackApplication::BTL4PlaybackApplication(
|
|
HINSTANCE hInstance,
|
|
HWND hWnd,
|
|
ResourceFile *resource_file,
|
|
SpoolFile *spool_file
|
|
):
|
|
BTL4Application(hInstance, hWnd, resource_file, L4ApplicationClassID, DefaultData)
|
|
{
|
|
// vtable installed by the compiler == &PTR_FUN_0051f1a4
|
|
spoolFile = spool_file; // this[6] == this+0x18
|
|
Check_Fpu();
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// ~BTL4PlaybackApplication @004d3ea0
|
|
//#############################################################################
|
|
//
|
|
BTL4PlaybackApplication::~BTL4PlaybackApplication()
|
|
{
|
|
// scalar-deleting dtor; reinstalls vtable and chains to ~BTL4Application
|
|
Check_Fpu();
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// MakeNetworkManager @004d3c14
|
|
//#############################################################################
|
|
//
|
|
// new(0x28)/FUN_0047bfc4 -- identical to RP: the playback application drives a
|
|
// L4PlaybackNetworkManager rather than a live one.
|
|
//
|
|
NetworkManager*
|
|
BTL4PlaybackApplication::MakeNetworkManager()
|
|
{
|
|
Check_Fpu();
|
|
return new L4PlaybackNetworkManager;
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// Initialize @004d3c34
|
|
//#############################################################################
|
|
//
|
|
void
|
|
BTL4PlaybackApplication::Initialize()
|
|
{
|
|
Check(this);
|
|
L4Application::InitializeTillConsole(); // FUN_0047b468
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// LoadBackgroundTasks @004d3c44
|
|
//#############################################################################
|
|
//
|
|
// Adds the playback task pipeline. The first task is our own BTSpoolerTask
|
|
// (new/FUN_004d3b74); the remaining four are stock ApplicationTasks built with
|
|
// priority 0x48 and the engine task vtables observed in the decomp. (In the
|
|
// shipped binary the trailing tasks are only distinguished by their vtable
|
|
// pointers; the RP analog names them ProcessEventTask, GaugeRendererTask,
|
|
// CompleteCyclesTask, FryDeathRowTask, AudioRendererTask -- BT emits five of
|
|
// these stock tasks plus the spooler. Names below are best-effort against RP.)
|
|
//
|
|
void
|
|
BTL4PlaybackApplication::LoadBackgroundTasks()
|
|
{
|
|
Check(this);
|
|
|
|
ApplicationTask *application_task;
|
|
|
|
application_task = new BTSpoolerTask; // FUN_004d3b74
|
|
Register_Object(application_task);
|
|
backgroundTasks->AddTask(application_task); // FUN_0044de1c(this+0x64,..)
|
|
|
|
application_task = new ProcessEventTask; // vtable &PTR_FUN_004f037c
|
|
Register_Object(application_task);
|
|
backgroundTasks->AddTask(application_task);
|
|
|
|
application_task = new GaugeRendererTask; // vtable &PTR_FUN_004f031c
|
|
Register_Object(application_task);
|
|
backgroundTasks->AddTask(application_task);
|
|
|
|
application_task = new CompleteCyclesTask; // vtable &PTR_FUN_004f0304
|
|
Register_Object(application_task);
|
|
backgroundTasks->AddTask(application_task);
|
|
|
|
application_task = new AudioRendererTask; // vtable &PTR_FUN_004f0364
|
|
Register_Object(application_task);
|
|
backgroundTasks->AddTask(application_task);
|
|
|
|
Check_Fpu();
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// ExecuteForeground @004d3d40
|
|
//#############################################################################
|
|
//
|
|
// The mission-review frame loop. Structurally the RP ExecuteForeground:
|
|
// when idle/not executing it merely services the video renderer; otherwise it
|
|
// runs the controls manager (only in interactive review, DAT_004fd550==2), the
|
|
// update manager, the interest/renderer managers, the intercom, and -- when
|
|
// actually running a mission (state==5) -- recomputes the remaining game time.
|
|
//
|
|
Logical
|
|
BTL4PlaybackApplication::ExecuteForeground(
|
|
Time start_of_frame,
|
|
Scalar frame_duration
|
|
)
|
|
{
|
|
SET_FOREGROUND_PROCESSING();
|
|
|
|
Check(this);
|
|
|
|
//
|
|
// executeFrames(+0x70)==0, or state(+0x88)==WaitingForEgg(1): idle.
|
|
//
|
|
if (!executeFrames || GetApplicationState() == WaitingForEgg)
|
|
{
|
|
videoRenderer->ExecuteIdle();
|
|
CLEAR_FOREGROUND_PROCESSING();
|
|
return executeFrames && !Exit_Code; // DAT_004efc98
|
|
}
|
|
|
|
Time frame_ticks;
|
|
frame_ticks = frame_duration; // Time::operator=(Scalar)
|
|
|
|
//
|
|
// Controls only matter in interactive ("scrub") review mode.
|
|
//
|
|
if (GetMissionReviewMode() == 2) // DAT_004fd550
|
|
{
|
|
Check(controlsManager);
|
|
controlsManager->Execute(); // vtbl+0x18
|
|
}
|
|
|
|
//
|
|
// Update manager -> interest manager -> renderer manager -> intercom.
|
|
//
|
|
SET_UPDATE_MANAGER();
|
|
Check(updateManager);
|
|
updateManager->Execute(start_of_frame); // FUN_00435794 / FUN_004363ec
|
|
CLEAR_UPDATE_MANAGER();
|
|
|
|
Check(rendererManager);
|
|
rendererManager->UpdateInterestOrigins(start_of_frame); // FUN_00434a08
|
|
Check(interestManager);
|
|
interestManager->Execute(); // FUN_0043649c
|
|
|
|
SET_RENDERER_MANAGER();
|
|
Check(rendererManager);
|
|
rendererManager->Execute(start_of_frame, frame_ticks, frame_ticks); // FUN_00436440
|
|
CLEAR_RENDERER_MANAGER();
|
|
|
|
Check(intercomManager);
|
|
intercomManager->Execute(); // (this+0x40)->vtbl+0x24
|
|
|
|
//
|
|
// Running a recorded mission: keep the on-screen game clock current.
|
|
//
|
|
if (GetApplicationState() == RunningMission) // state(+0x88)==5
|
|
{
|
|
// The decomp showed "(Now()-gameStarted)/TicksPerSecond": that
|
|
// division reflects the DOS Time-in-ticks representation. In the
|
|
// ported engine Time::operator-(Time) already yields Scalar seconds
|
|
// (time.h Time_To_Float), so the value is used directly here -- the
|
|
// same net result, matching the RP twin.
|
|
secondsRemainingInGame = // +0x58
|
|
currentMission->GetGameLength() // (this+0xc8)+0xe0
|
|
- (Now() - gameStarted); // +0x5c
|
|
}
|
|
|
|
CLEAR_FOREGROUND_PROCESSING();
|
|
Check_Fpu();
|
|
return executeFrames && !Exit_Code;
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// ExecuteBackgroundTask
|
|
//#############################################################################
|
|
//
|
|
// NOTE: the OPT build did not emit a BT-specific override for this slot -- the
|
|
// playback application inherits L4Application::ExecuteBackgroundTask unchanged
|
|
// (the RP twin's body -- "if (ProcessOneEvent(HighEventPriority)) return; else
|
|
// backgroundTasks->Execute();" -- already lives in the L4 base here). Declared
|
|
// in the header for completeness; reconstructed best-effort to match RP.
|
|
//
|
|
void
|
|
BTL4PlaybackApplication::ExecuteBackgroundTask()
|
|
{
|
|
Check(this);
|
|
|
|
if (ProcessOneEvent(HighEventPriority))
|
|
{
|
|
return;
|
|
}
|
|
|
|
Check(backgroundTasks);
|
|
backgroundTasks->Execute();
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// DispatchPacket @004d3ecc
|
|
//#############################################################################
|
|
//
|
|
// Route a recorded packet to its client. packet->clientID is the first word
|
|
// of the NetworkPacket; messageData lives at packet+0x10.
|
|
//
|
|
void
|
|
BTL4PlaybackApplication::DispatchPacket(NetworkPacket *packet)
|
|
{
|
|
Check(this);
|
|
|
|
NetworkManager *net_mgr = GetNetworkManager(); // this+0x20
|
|
Check(net_mgr);
|
|
NetworkClient *client =
|
|
net_mgr->GetNetworkClientPointer(packet->clientID); // FUN_0041d7f0
|
|
Check(client);
|
|
client->ReceiveNetworkPacket(packet, &packet->messageData); // vtbl+0x14
|
|
Check_Fpu();
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// RunMissionMessageHandler @004d3ef8
|
|
//#############################################################################
|
|
//
|
|
// On the very first launch (state==WaitingForLaunch, 3) synchronise the
|
|
// playback clock: timeBias = Now - (timestamp of the spooled RunMission
|
|
// packet), then advance the spool one packet. Then chain to the base.
|
|
//
|
|
void
|
|
BTL4PlaybackApplication::RunMissionMessageHandler(
|
|
RunMissionMessage *message
|
|
)
|
|
{
|
|
Check(this);
|
|
|
|
if (GetApplicationState() == WaitingForLaunch) // state(+0x88)==3
|
|
{
|
|
SpoolFile *spool = GetSpoolFile(); // this+0x18
|
|
Check(spool);
|
|
NetworkPacket *packet = (NetworkPacket*)spool->GetPointer(); // vtbl slot0
|
|
timeBias = // this+0xd4
|
|
Now().ticks - packet->timeStamp.ticks; // FUN_00414b60, packet+0xc
|
|
spool->NextPacket(); // FUN_0044f850
|
|
}
|
|
|
|
BTL4Application::RunMissionMessageHandler(message); // FUN_004d3a2c
|
|
Check_Fpu();
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// StopMissionMessageHandler @004d3f44
|
|
//#############################################################################
|
|
//
|
|
char winners_spot[]="win?"; // @0051ee63 (literal "win?\0")
|
|
|
|
void
|
|
BTL4PlaybackApplication::StopMissionMessageHandler(
|
|
StopMissionMessage *message
|
|
)
|
|
{
|
|
MissionReviewApplicationManager *app_mgr = GetApplicationManager(); // FUN_0044e13c
|
|
Check(app_mgr);
|
|
|
|
SpoolFile *spool = GetSpoolFile(); // this+0x18
|
|
if (spool)
|
|
{
|
|
app_mgr->ReleaseSpoolFile(spool); // FUN_0044fac0
|
|
spoolFile = NULL;
|
|
|
|
//
|
|
// Move every player into the dropzone matching its finishing rank
|
|
// (the "winners circle").
|
|
//
|
|
EntityManager *entity_manager = application->GetEntityManager(); // DAT_004efc94+0x24
|
|
EntityGroup *dropzones = entity_manager->FindGroup("DropZones"); // FUN_00403ad0
|
|
char *place = winners_spot + 3; // the trailing '?'
|
|
|
|
Player *p;
|
|
for (
|
|
int rank = 0;
|
|
(p = CameraDirector::FindPlayerByRank(rank)) != NULL; // FUN_0042efe4
|
|
++rank
|
|
)
|
|
{
|
|
*place = (char)('1' + rank); // "win1".."winN"
|
|
|
|
ChainIteratorOf<Node*> iterator(dropzones->groupMembers); // FUN_00421414
|
|
DropZone *dropzone;
|
|
while ((dropzone = (DropZone*)iterator.ReadAndNext()) != NULL)
|
|
{
|
|
if (!strcmp(dropzone->GetDropZoneName(), winners_spot)) // name@+0x1c8
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
if (!dropzone)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Park the player's Mech on that dropzone for the freeze-frame.
|
|
//
|
|
Mech *mech = (Mech*)p->GetPlayerVehicle(); // p+0x1fc
|
|
if (mech->GetClassID() == MechClassID) // classID(+4)==0xbb9
|
|
{
|
|
mech->Reset(dropzone->localOrigin, Mech::MissionReviewReset); // FUN_0049fb74, +0x100, 0
|
|
mech->SetPerformance(&Mech::DoNothing); // mech[7..9]=&PTR_0051ee69..
|
|
mech->FlushEvents(); // FUN_00419cb8
|
|
|
|
//
|
|
// Refresh this player's name bitmap entry.
|
|
//
|
|
// RECONSTRUCTION NOTE: the shipped BTL4OPT.EXE here built a
|
|
// per-entity NameBitmapEntry (FUN_00436668) and handed it to
|
|
// RendererManager::ReplaceNameBitmap (FUN_004364e4). That
|
|
// per-entry name-bitmap API belonged to the original Division
|
|
// IG image-generator renderer and was removed wholesale in the
|
|
// WinTesla port (L4D3D); RendererManager / NameBitmapEntry no
|
|
// longer expose it. The bulk reload below
|
|
// (SortAndReloadNameBitmaps) -- which the port retained and the
|
|
// RP twin RPL4PlaybackApplication::StopMissionMessageHandler
|
|
// relies on alone -- performs the equivalent refresh, so the
|
|
// per-entry rebuild is intentionally folded into it here.
|
|
}
|
|
}
|
|
|
|
//
|
|
// Re-sort the name bitmaps and tilt the camera up for the circle.
|
|
//
|
|
DPLRenderer *dpl_renderer = GetVideoRenderer(); // this+0x48
|
|
Check(dpl_renderer);
|
|
dpl_renderer->SortAndReloadNameBitmaps(); // FUN_00460840
|
|
dpl_renderer->SetViewAngle(Degree(45.0f)); // FUN_0045a7a8(.,4); cf. literal @0051ee66
|
|
}
|
|
|
|
//
|
|
// Let the application stop normally.
|
|
//
|
|
BTL4Application::StopMissionMessageHandler(message); // FUN_004d3a94
|
|
Check_Fpu();
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// TestInstance @004d40d8
|
|
//#############################################################################
|
|
//
|
|
Logical
|
|
BTL4PlaybackApplication::TestInstance() const
|
|
{
|
|
return IsDerivedFrom(*GetClassDerivations()); // FUN_0041a1a4(.,0x51ee2c)
|
|
}
|