diff --git a/game/reconstructed/btplayer.cpp b/game/reconstructed/btplayer.cpp index 884bcdb..4661e0f 100644 --- a/game/reconstructed/btplayer.cpp +++ b/game/reconstructed/btplayer.cpp @@ -748,6 +748,55 @@ void suppressConsole = 0; // this+0x258 } +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// MissionStartingMessageHandler (@004bfbe8) +// +// THE "+1000 STARTING THE GAME" ROW of the original manual's scoring chart, +// decoded 2026-08-07. BT overrides the engine's MissionStarting purely to +// seed the score, and the override was never reconstructed -- the +// MESSAGE_ENTRY resolved to the inherited Player:: handler, so the grant +// simply never happened. The binary: +// +// FUN_004bfbe8(player): +// base_MissionStarting(player); +// if (app->state == 4 && (player[0x29] & 0x40) == 0) +// player[0x1c8] = 0x447a0000; // = 1000.0f +// +// Both operands decode exactly: application state 4 is LaunchingMission +// (APP.h -- the same enum whose 6 is EndingMission, already used by the +// console flush), and simulationFlags bit 14 is NonScoringPlayerBit +// (PLAYER.h: `NonScoringPlayerBit = Entity::NextBit`), so the byte test +// `(+0x29 & 0x40) == 0` IS `IsScoringPlayer()`. Camera-ship and spectator +// players are non-scoring and correctly get nothing. +// +// CELL NOTE: the binary seeds the ENGINE score cell (+0x1c8), not BT's own +// (+0x278) -- the 1995 build carried two accumulators, which is why the KB +// suspected the pod's death cost "may never have displayed". Our port has a +// single currentScore, so the grant, the awards and the death cost all land +// together, and the chart reads coherently for a player: start at 1000, +1 a +// damage point, +500 a kill, -500 a special-case death. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +void + BTPlayer::MissionStartingMessageHandler(Entity::Message *message) +{ + Player::MissionStartingMessageHandler(message); // FUN_0042d9c0 + + if (application->GetApplicationState() == Application::LaunchingMission // app+0x88 == 4 + && IsScoringPlayer()) // !(simulationFlags & NonScoringPlayerFlag) + { + currentScore = 1000.0f; // this+0x1c8 = 0x447a0000 + // The console watermark is what we have already reported; a fresh + // mission must report the grant, not the difference from the last + // round's tally. + BTScoreWatermarkSet(ownerID, 0.0f); + DEBUG_STREAM << "[score] mission start: player " + << BTMatchHostOf(GetEntityID()) << ":" << (int)GetEntityID() + << " seeded to " << (float)currentScore + << " (chart: +1000 starting the game)\n" << std::flush; + } +} + + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Console score watermark (port-side, 2026-08-07) // diff --git a/game/reconstructed/btplayer.hpp b/game/reconstructed/btplayer.hpp index 1f56c9f..56bde2c 100644 --- a/game/reconstructed/btplayer.hpp +++ b/game/reconstructed/btplayer.hpp @@ -84,16 +84,24 @@ class DropZone__ReplyMessage; public: // // Kind of scoring event. Recovered from the branch selector at - // @004c02e4 (this->scoreType, message+0x20). NOTE: type 0 has NO - // scoring arm in the binary -- @004c02e4 Verify-rejects it (line 662) - // and @004c0200, the only function that accepts it, appears in no - // handler-table entry (byte-scan 2026-08-05: the BTPlayer table at - // file 0x112dxx has exactly 6 entries, none binding it). 1995 pod - // scoring = kills + received-damage penalties; per-hit inflicted - // credit never existed. + // @004c02e4 (this->scoreType, message+0x20). + // + // CORRECTED 2026-08-07. This note used to read "type 0 has NO scoring + // arm in the binary ... @004c0200 appears in no handler-table entry ... + // per-hit inflicted credit never existed", and build 787 deleted the + // credit on that basis. The byte-scan was right that no TABLE entry + // binds @004c0200 and wrong to conclude it is unreachable: BTPlayer + // overrides Dispatch (vtable @00513300 slot 3 = FUN_004bffa0), which + // splits type 0 off BEFORE base dispatch and calls it directly. That + // is also WHY @004c02e4 Verify-rejects type 0 -- the interceptor + // guarantees it never arrives there. @004c0200 names itself + // "BTPlayer::ScoreInflictedMessageHandler" in its own Verify string, + // and the original manual's scoring chart independently confirms what + // it computes: "+1 each damage point scored on opponent's armor", + // "-1 each self-inflicted point". // enum ScoreType { - DamageInflictedScore = 0, // sent, but scores nothing (see above) + DamageInflictedScore = 0, // per-hit inflicted credit (LIVE, see above) DamageReceivedScore = 1, // I took damage KillScore = 2 // I destroyed / was destroyed }; @@ -319,6 +327,14 @@ class DropZone__ReplyMessage; void ScoreMessageHandler(ScoreMessage *message); // @004c02e4 + // + // @004bfbe8 -- BT's MissionStarting override. Seeds the starting score + // ("+1000 Starting the game", original manual scoring chart). The base + // Player handler does the fade-in; BT adds the grant. + // + void + MissionStartingMessageHandler(Entity::Message *message); // @004bfbe8 + // // @004bffd0 -- the spawn / respawn handshake. When the drop zone // replies with our spawn location we create (or reset) the player's