Completes the marshal chain: menu -> mission -> timed stop -> results -> relaunch. BT had no mission-end score flow; this builds one from the data the Comm MFD pilotList already tracks. - btl4console: at the timed stop (game thread, state still live) snapshot each roster pilot's kills/deaths via the BTResolveRosterPilot / BTPilotKills / BTPilotDeaths bridges; expose BTLocalConsole_ResultCount / GetResult. - btl4fe: BTFrontEnd_ShowResults -- a GDI green-on-black 'MISSION COMPLETE' scoreboard (PILOT / KILLS / DEATHS, slot 0 named from the menu), any key or ~12s to dismiss; store the last pilot name. - btl4main: show the scoreboard after a marshaled mission, before the relaunch. Verified: MISSION COMPLETE screen renders with the pilot row and the snapshotted score (0/0 on a no-combat test run; the data path is the same one the live Comm MFD reads). Phase 5 (front end) COMPLETE: menu + egg builder + marshal + timed stop + results + single-binary (relaunch) loop. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
144 lines
4.3 KiB
C++
144 lines
4.3 KiB
C++
//===========================================================================//
|
|
// btl4console.cpp -- BT412 in-process LocalConsole marshal (solo path).
|
|
//
|
|
// See btl4console.hpp. Models RP412's RPL4CONSOLE ConsoleTick, reduced to the
|
|
// single-player case: no remote pods, no wire -- just own the mission clock and
|
|
// dispatch StopMissionMessage at expiry. The tick runs on the game thread
|
|
// (gPerFrameHook), so the engine call (Dispatch) is safe.
|
|
//===========================================================================//
|
|
#include <bt.hpp> // Application, application, DEBUG_STREAM
|
|
#pragma hdrstop
|
|
|
|
#include "appmsg.hpp" // full Application::StopMissionMessage definition
|
|
#include "appmgr.hpp" // gPerFrameHook
|
|
#include "btl4console.hpp"
|
|
|
|
// The pilot roster + score bridges (btl4gau3.cpp / mechmppr.cpp / btplayer.cpp):
|
|
// the pilotList reads them for the Comm MFD, and the marshal snapshots them at
|
|
// the stop so the results screen has the final scores.
|
|
extern void *BTResolveRosterPilot(int slot);
|
|
extern int BTPilotKills(void *pilot);
|
|
extern int BTPilotDeaths(void *pilot);
|
|
|
|
namespace
|
|
{
|
|
enum Phase { PhaseIdle, PhaseWaiting, PhaseRunning, PhaseStopped };
|
|
|
|
Phase gPhase = PhaseIdle;
|
|
int gMissionSeconds = 0;
|
|
unsigned long gStartTick = 0;
|
|
Application *gWatched = 0;
|
|
|
|
// Final scores snapshotted at the stop (roster order; slot 0 = local).
|
|
enum { maxResults = 8 };
|
|
struct Result { int kills, deaths; };
|
|
Result gResults[maxResults];
|
|
int gResultCount = 0;
|
|
|
|
void SnapshotResults()
|
|
{
|
|
gResultCount = 0;
|
|
for (int slot = 0; slot < maxResults; ++slot)
|
|
{
|
|
void *pilot = BTResolveRosterPilot(slot);
|
|
if (pilot == 0)
|
|
break; // past the last occupied roster slot
|
|
gResults[gResultCount].kills = BTPilotKills(pilot);
|
|
gResults[gResultCount].deaths = BTPilotDeaths(pilot);
|
|
++gResultCount;
|
|
}
|
|
DEBUG_STREAM << "[marshal] snapshot " << gResultCount << " pilot score(s)\n" << std::flush;
|
|
}
|
|
|
|
//
|
|
// The game-thread tick (called every frame while an application is live).
|
|
//
|
|
void ConsoleTick()
|
|
{
|
|
if (application == 0)
|
|
return;
|
|
|
|
int state = application->GetApplicationState();
|
|
|
|
switch (gPhase)
|
|
{
|
|
case PhaseWaiting:
|
|
// The mission has staged and started running -> start the clock.
|
|
if (state == Application::RunningMission)
|
|
{
|
|
gPhase = PhaseRunning;
|
|
gWatched = application;
|
|
gStartTick = GetTickCount();
|
|
DEBUG_STREAM << "[marshal] mission running -- length "
|
|
<< gMissionSeconds << "s\n" << std::flush;
|
|
}
|
|
break;
|
|
|
|
case PhaseRunning:
|
|
if (application != gWatched)
|
|
return; // only marshal our own mission
|
|
|
|
if (state != Application::RunningMission)
|
|
{
|
|
// ended some other way (pilot abort, teardown)
|
|
gPhase = PhaseStopped;
|
|
DEBUG_STREAM << "[marshal] mission ended (state change)\n" << std::flush;
|
|
}
|
|
else if (gMissionSeconds > 0 &&
|
|
(GetTickCount() - gStartTick) >= (unsigned long)(gMissionSeconds * 1000))
|
|
{
|
|
// The console clock expired: snapshot the final scores (the
|
|
// game state is still live here), then stop the race exactly
|
|
// as the arcade console did (StopMissionMessage on our pod).
|
|
DEBUG_STREAM << "[marshal] time expired -- stopping mission\n" << std::flush;
|
|
SnapshotResults();
|
|
Application::StopMissionMessage message(0);
|
|
application->Dispatch(&message);
|
|
gPhase = PhaseStopped;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void BTLocalConsole_Install(int mission_seconds)
|
|
{
|
|
gMissionSeconds = mission_seconds;
|
|
gPhase = PhaseWaiting;
|
|
gStartTick = 0;
|
|
gWatched = 0;
|
|
// Marshaled: the in-process console owns the clock, so a console-loss
|
|
// (there is none locally) would end the race -- harmless for solo.
|
|
gConsoleLossEndsMission = True;
|
|
gPerFrameHook = &ConsoleTick;
|
|
DEBUG_STREAM << "[marshal] installed (length " << mission_seconds << "s)\n" << std::flush;
|
|
}
|
|
|
|
int BTLocalConsole_MissionCompleted()
|
|
{
|
|
return (gPhase == PhaseStopped) ? 1 : 0;
|
|
}
|
|
|
|
int BTLocalConsole_ResultCount()
|
|
{
|
|
return gResultCount;
|
|
}
|
|
|
|
int BTLocalConsole_GetResult(int index, int *kills, int *deaths)
|
|
{
|
|
if (index < 0 || index >= gResultCount)
|
|
return 0;
|
|
if (kills) *kills = gResults[index].kills;
|
|
if (deaths) *deaths = gResults[index].deaths;
|
|
return 1;
|
|
}
|
|
|
|
void BTLocalConsole_Uninstall()
|
|
{
|
|
gPerFrameHook = 0;
|
|
gPhase = PhaseIdle;
|
|
}
|