BTL4PB.CPP reconstructed against its AUTHENTIC surviving header (BTL4PB.HPP) from the binary's 0x4d3b74-0x4d40d8 cluster, cross-checked against the surviving RP411 twin -- and the review/camera launch in BTL4.CPP's main() is real code now. btl4.lib 11 -> 12 members, link clean, zero unresolved: the linker pulled the entire playback chain for the first time. The launch decode closed on two identifications: The `if (spool_file)` guard in the decomp means the manager getter is GetStoredSpoolFile -- the empty-spool getter cannot fail, so a review pod with no recorded battle simply starts nothing, which is the right field behaviour. The camera app's ctor (FUN_0047c3fc) sits in the ENGINE range with the (ResourceFile*, ApplicationID) shape: the camera pod's recorder IS the engine's own L4SpoolingApplication -- restored one commit ago -- started with ApplicationID 1 == BTL4. BT shipped no camera app of its own. What the playback app does, all binary-anchored: BTSpoolerTask pumps recorded packets through DispatchPacket -> the client's own ReceiveNetworkPacket; RunMission synchronises the clock (timeBias = Now - the recorded run packet's stamp); the frame loop runs controls ONLY in interactive review (mode 2), then update/interest/renderer/intercom; and StopMission plays the WINNERS CIRCLE -- release the spool, park each player's mech on the dropzone named "win1".."winN" by finishing rank, freeze it (DoNothing performance), and tilt the camera up 45 degrees over the tableau. DOS-authentic ctor shape throughout: the binary's base call is (resource, 0x3eb, DefaultData) -- no windowing parameters ever existed in 4.10; the RP411 twin's HINSTANCE/HWND threading is port drift, dropped. Also fixed from 5.3.105's debt: L4PlaybackNetworkManager chains NetworkManager::DefaultData -- the 1995 header declares no statics of its own; the RP411 self-reference was drift too. One [T3] in source: the idle frame's renderer service (the descendant's ExecuteIdle) has no 1995 entry point; pre-egg the idle frame spins. RUNTIME TEST PENDING, stated plainly: exercising review needs a camera/review egg and a recorded spool on the rig (CAMERA-REVIEW-NOTES.md has the roles). The normal pod path regressed clean (mode gate untouched). The Fail() census moves: BTL4.CPP and SEQCTL.CPP are now stub-free; 22 stubs remain across 15 files. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
445 lines
13 KiB
C++
445 lines
13 KiB
C++
//===========================================================================//
|
|
// File: btl4pb.cpp //
|
|
// Project: BattleTech Brick: BattleTech LBE Application //
|
|
// Contents: BTL4PlaybackApplication -- the mission-review application, and //
|
|
// BTSpoolerTask, the pump that replays recorded NetworkPackets //
|
|
//---------------------------------------------------------------------------//
|
|
// Date Who Modification //
|
|
// -------- --- ---------------------------------------------------------- //
|
|
// //
|
|
//---------------------------------------------------------------------------//
|
|
// Copyright (C) 1995, Virtual World Entertainment, Inc. //
|
|
// All Rights reserved worldwide //
|
|
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL //
|
|
//===========================================================================//
|
|
//
|
|
// RECONSTRUCTED against the AUTHENTIC surviving header (BTL4PB.HPP) from the
|
|
// shipped binary's 0x4d3b74-0x4d40d8 cluster, cross-checked against the
|
|
// surviving RP411 descendant RPL4PB.cpp (a line-for-line structural twin).
|
|
// The binary's own base-ctor call (FUN_004d34c4(this, resource, 0x3eb,
|
|
// DefaultData)) confirms the DOS ctor shape this file uses -- no windowing
|
|
// parameters ever existed in 4.10.
|
|
//
|
|
|
|
#include <btl4.hpp>
|
|
#pragma hdrstop
|
|
|
|
#if !defined(BTL4PB_HPP)
|
|
# include <btl4pb.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>
|
|
#endif
|
|
#if !defined(MECH_HPP)
|
|
# include <mech.hpp>
|
|
#endif
|
|
#if !defined(L4VIDEO_HPP)
|
|
# include <l4video.hpp>
|
|
#endif
|
|
#if !defined(L4CTRL_HPP)
|
|
# include <l4ctrl.hpp>
|
|
#endif
|
|
|
|
//#############################################################################
|
|
//########################## BTSpoolerTask ##############################
|
|
//#############################################################################
|
|
|
|
//
|
|
// @004d3b74 / @004d3bb8 -- chain-only construction; the task adds no data.
|
|
//
|
|
BTSpoolerTask::BTSpoolerTask()
|
|
{
|
|
}
|
|
|
|
BTSpoolerTask::~BTSpoolerTask()
|
|
{
|
|
Check_Fpu();
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// @004d3b90 / @004d3ba8 -- the pump's two callbacks forward to the running
|
|
// playback application.
|
|
//#############################################################################
|
|
//
|
|
void
|
|
BTSpoolerTask::DispatchPacket(NetworkPacket *packet)
|
|
{
|
|
BTL4PlaybackApplication
|
|
*playback_app =
|
|
Cast_Object(BTL4PlaybackApplication *, application);
|
|
playback_app->DispatchPacket(packet);
|
|
Check_Fpu();
|
|
}
|
|
|
|
long
|
|
BTSpoolerTask::GetTimeBias()
|
|
{
|
|
BTL4PlaybackApplication
|
|
*playback_app =
|
|
Cast_Object(BTL4PlaybackApplication *, application);
|
|
return playback_app->GetTimeBias();
|
|
}
|
|
|
|
//#############################################################################
|
|
//####################### BTL4PlaybackApplication #######################
|
|
//#############################################################################
|
|
|
|
const Receiver::HandlerEntry
|
|
BTL4PlaybackApplication::MessageHandlerEntries[] =
|
|
{
|
|
MESSAGE_ENTRY(BTL4PlaybackApplication, RunMission), // @004d3ef8
|
|
MESSAGE_ENTRY(BTL4PlaybackApplication, StopMission) // @004d3f44
|
|
};
|
|
|
|
BTL4PlaybackApplication::MessageHandlerSet
|
|
BTL4PlaybackApplication::MessageHandlers(
|
|
ELEMENTS(BTL4PlaybackApplication::MessageHandlerEntries),
|
|
BTL4PlaybackApplication::MessageHandlerEntries,
|
|
BTL4Application::MessageHandlers
|
|
);
|
|
|
|
Derivation
|
|
BTL4PlaybackApplication::ClassDerivations(
|
|
BTL4Application::ClassDerivations,
|
|
"BTL4PlaybackApplication" // @0051f182
|
|
);
|
|
|
|
BTL4PlaybackApplication::SharedData
|
|
BTL4PlaybackApplication::DefaultData( // @0051ee5c
|
|
BTL4PlaybackApplication::ClassDerivations,
|
|
BTL4PlaybackApplication::MessageHandlers
|
|
);
|
|
|
|
//
|
|
//#############################################################################
|
|
// @004d3be4 -- the binary's base call is (resource, 0x3eb, DefaultData): the
|
|
// L4ApplicationClassID, no windowing. The spool lands in the inherited
|
|
// spoolFile slot.
|
|
//#############################################################################
|
|
//
|
|
BTL4PlaybackApplication::BTL4PlaybackApplication(
|
|
ResourceFile *resource_file,
|
|
SpoolFile *spool_file
|
|
):
|
|
BTL4Application(resource_file, L4ApplicationClassID, DefaultData)
|
|
{
|
|
spoolFile = spool_file;
|
|
timeBias = 0;
|
|
Check_Fpu();
|
|
}
|
|
|
|
BTL4PlaybackApplication::~BTL4PlaybackApplication()
|
|
{
|
|
Check_Fpu();
|
|
}
|
|
|
|
Logical
|
|
BTL4PlaybackApplication::TestInstance() const
|
|
{
|
|
return IsDerivedFrom(ClassDerivations);
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// @004d3c14 -- playback drives the spool-fed network manager, not a live one.
|
|
//#############################################################################
|
|
//
|
|
NetworkManager *
|
|
BTL4PlaybackApplication::MakeNetworkManager()
|
|
{
|
|
Check_Fpu();
|
|
return new L4PlaybackNetworkManager;
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// @004d3c34
|
|
//#############################################################################
|
|
//
|
|
void
|
|
BTL4PlaybackApplication::Initialize()
|
|
{
|
|
Check(this);
|
|
L4Application::InitializeTillConsole();
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// @004d3c44 -- the playback task pipeline: our own spool pump first, then the
|
|
// stock event / gauge / cycle / audio tasks.
|
|
//#############################################################################
|
|
//
|
|
void
|
|
BTL4PlaybackApplication::LoadBackgroundTasks()
|
|
{
|
|
Check(this);
|
|
|
|
ApplicationTask
|
|
*application_task;
|
|
|
|
application_task = new BTSpoolerTask;
|
|
Register_Object(application_task);
|
|
backgroundTasks->AddTask(application_task);
|
|
|
|
application_task = new ProcessEventTask;
|
|
Register_Object(application_task);
|
|
backgroundTasks->AddTask(application_task);
|
|
|
|
application_task = new GaugeRendererTask;
|
|
Register_Object(application_task);
|
|
backgroundTasks->AddTask(application_task);
|
|
|
|
application_task = new CompleteCyclesTask;
|
|
Register_Object(application_task);
|
|
backgroundTasks->AddTask(application_task);
|
|
|
|
application_task = new AudioRendererTask;
|
|
Register_Object(application_task);
|
|
backgroundTasks->AddTask(application_task);
|
|
|
|
Check_Fpu();
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// @004d3d40 -- the mission-review frame loop. Idle until frames execute;
|
|
// controls only in INTERACTIVE review (mode 2); then the update, interest,
|
|
// renderer and intercom managers; and while running, keep the game clock
|
|
// current from the recorded start.
|
|
//#############################################################################
|
|
//
|
|
Logical
|
|
BTL4PlaybackApplication::ExecuteForeground(
|
|
Time start_of_frame,
|
|
Scalar frame_duration
|
|
)
|
|
{
|
|
SET_FOREGROUND_PROCESSING();
|
|
|
|
Check(this);
|
|
|
|
if (!executeFrames || GetApplicationState() == WaitingForEgg)
|
|
{
|
|
//
|
|
// [T3] The binary services a renderer idle slot here (the RP411
|
|
// descendant names it ExecuteIdle); no such entry exists in the
|
|
// 1995 renderer headers, and pre-egg there is nothing to draw --
|
|
// the idle frame simply spins. Revisit if the review screen shows
|
|
// stale video before the egg arrives.
|
|
//
|
|
CLEAR_FOREGROUND_PROCESSING();
|
|
return executeFrames && !Exit_Code;
|
|
}
|
|
|
|
Time
|
|
frame_ticks;
|
|
frame_ticks = frame_duration;
|
|
|
|
if (GetMissionReviewMode() == 2)
|
|
{
|
|
Check(controlsManager);
|
|
((LBE4ControlsManager *)controlsManager)->Execute();
|
|
}
|
|
|
|
SET_UPDATE_MANAGER();
|
|
Check(updateManager);
|
|
updateManager->Execute(start_of_frame);
|
|
CLEAR_UPDATE_MANAGER();
|
|
|
|
Check(rendererManager);
|
|
rendererManager->UpdateInterestOrigins(start_of_frame);
|
|
Check(interestManager);
|
|
interestManager->Execute();
|
|
|
|
SET_RENDERER_MANAGER();
|
|
Check(rendererManager);
|
|
rendererManager->Execute(start_of_frame, frame_ticks, frame_ticks);
|
|
CLEAR_RENDERER_MANAGER();
|
|
|
|
Check(intercomManager);
|
|
intercomManager->Execute();
|
|
|
|
if (GetApplicationState() == RunningMission)
|
|
{
|
|
secondsRemainingInGame =
|
|
currentMission->GetGameLength() - (Now() - gameStarted);
|
|
}
|
|
|
|
CLEAR_FOREGROUND_PROCESSING();
|
|
Check_Fpu();
|
|
return executeFrames && !Exit_Code;
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// Inherited-shape background task (the OPT build emitted no BT override; the
|
|
// body matches the L4 base, kept for the header's declaration).
|
|
//#############################################################################
|
|
//
|
|
void
|
|
BTL4PlaybackApplication::ExecuteBackgroundTask()
|
|
{
|
|
Check(this);
|
|
|
|
if (ProcessOneEvent(HighEventPriority))
|
|
{
|
|
return;
|
|
}
|
|
|
|
Check(backgroundTasks);
|
|
backgroundTasks->Execute();
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// @004d3ecc -- route a recorded packet to its client.
|
|
//#############################################################################
|
|
//
|
|
void
|
|
BTL4PlaybackApplication::DispatchPacket(NetworkPacket *packet)
|
|
{
|
|
Check(this);
|
|
|
|
NetworkManager
|
|
*net_mgr = GetNetworkManager();
|
|
Check(net_mgr);
|
|
NetworkClient
|
|
*client = net_mgr->GetNetworkClientPointer(packet->clientID);
|
|
Check(client);
|
|
client->ReceiveNetworkPacket(packet, &packet->messageData);
|
|
Check_Fpu();
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// @004d3ef8 -- first launch synchronises the playback clock: the bias is the
|
|
// difference between NOW and the recorded RunMission packet's timestamp, and
|
|
// every replayed packet is re-timed by it.
|
|
//#############################################################################
|
|
//
|
|
void
|
|
BTL4PlaybackApplication::RunMissionMessageHandler(RunMissionMessage *message)
|
|
{
|
|
Check(this);
|
|
|
|
if (GetApplicationState() == WaitingForLaunch)
|
|
{
|
|
SpoolFile
|
|
*spool = GetSpoolFile();
|
|
Check(spool);
|
|
NetworkPacket
|
|
*packet = (NetworkPacket *)spool->GetPointer();
|
|
timeBias = Now().ticks - packet->timeStamp.ticks;
|
|
spool->NextPacket();
|
|
}
|
|
|
|
BTL4Application::RunMissionMessageHandler(message);
|
|
Check_Fpu();
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// @004d3f44 -- the WINNERS CIRCLE. When the recorded mission ends, release
|
|
// the spool, park every player's mech on the dropzone matching its finishing
|
|
// rank ("win1".."winN"), freeze them, refresh the name bitmaps and tilt the
|
|
// camera up 45 degrees for the final tableau.
|
|
//#############################################################################
|
|
//
|
|
|
|
static char
|
|
winners_spot[] = "win?"; // @0051ee63
|
|
|
|
void
|
|
BTL4PlaybackApplication::StopMissionMessageHandler(StopMissionMessage *message)
|
|
{
|
|
MissionReviewApplicationManager
|
|
*app_mgr = GetApplicationManager();
|
|
Check(app_mgr);
|
|
|
|
SpoolFile
|
|
*spool = GetSpoolFile();
|
|
if (spool)
|
|
{
|
|
app_mgr->ReleaseSpoolFile(spool);
|
|
spoolFile = NULL;
|
|
|
|
EntityManager
|
|
*entity_manager = application->GetEntityManager();
|
|
EntityGroup
|
|
*dropzones = entity_manager->FindGroup("DropZones");
|
|
char
|
|
*place = winners_spot + 3;
|
|
|
|
Player
|
|
*player;
|
|
int
|
|
rank;
|
|
for (
|
|
rank = 0;
|
|
(player = CameraDirector::FindPlayerByRank(rank)) != NULL;
|
|
++rank
|
|
)
|
|
{
|
|
*place = (char)('1' + rank);
|
|
|
|
ChainIteratorOf<Node *>
|
|
iterator(dropzones->groupMembers);
|
|
DropZone
|
|
*dropzone;
|
|
while ((dropzone = (DropZone *)iterator.ReadAndNext()) != NULL)
|
|
{
|
|
if (!strcmp(dropzone->GetDropZoneName(), winners_spot))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
if (!dropzone)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
Mech
|
|
*mech = (Mech *)player->GetPlayerVehicle();
|
|
if (mech->GetClassID() == RegisteredClass::MechClassID)
|
|
{
|
|
mech->Reset(dropzone->localOrigin, False);
|
|
mech->SetPerformance(
|
|
(Mech::Performance)&Simulation::DoNothing);
|
|
mech->FlushEvents();
|
|
}
|
|
}
|
|
|
|
DPLRenderer
|
|
*dpl_renderer = (DPLRenderer *)GetVideoRenderer();
|
|
Check(dpl_renderer);
|
|
dpl_renderer->SortAndReloadNameBitmaps();
|
|
dpl_renderer->SetViewAngle(Degree(45.0f));
|
|
}
|
|
|
|
BTL4Application::StopMissionMessageHandler(message);
|
|
Check_Fpu();
|
|
}
|