Files
BT411/game/reconstructed/btplayer.hpp
T
Joe DiPrimaandClaude Fable 5 91bd28669e #45/#134 authentic score/death report tail -- replaces the scoring stand-ins
Reconstructs the dark-gap tail of Mech::TakeDamageMessageHandler
(@0x4a02f4-0x4a0890, raw disasm): the three id-0x16 score reports (kill to
the shooter's player / type-0 wire-fidelity / received to the victim's
player) and the BT 0x38-byte VehicleDeadMessage extension {killed-by player,
kill zone} dispatched from the death tail.  Retires BTPostDamageScore /
BTPostKillScore and the per-hit inflicted credit (never existed in 1995:
@0x4c0200 is bound in no handler-table entry -- byte-scan receipt in
decomp-reference).  Suicides now dispatch and the handler negates the award
(the #134 panic penalty).  Collision divert falls through to the death tail
per @0x4a0375 (wall deaths respawn + blast; no score).  ScoreMessage fields
renamed to decoded truth (vitalHit/zoneIndex/subsysID) + wire asserts;
console VTVDamaged points_transfered corrected (Round(award), not Now()).

Benches: scorekill.sh cross-node kill (kills=1 award=4.88, killedBy=2:1
zone=3, single death cycle), scoreself.sh suicide (type=2 award=-39.00
kills=0), deathblast2.sh re-verified (72 bursts at ~9u).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-05 18:36:58 -05:00

549 lines
23 KiB
C++

//===========================================================================//
// File: btplayer.hpp //
// Project: BattleTech Brick: Player / Scoring //
// Contents: BattleTech player & scoring entity (BT analog of RPPLAYER) //
//---------------------------------------------------------------------------//
// Date Who Modification //
// -------- --- ---------------------------------------------------------- //
// --/--/96 JM Initial coding. //
//---------------------------------------------------------------------------//
// Copyright (C) 1995, Virtual World Entertainment, Inc. All Rights reserved //
// PROPRIETARY AND CONFIDENTIAL //
//===========================================================================//
//
// RECONSTRUCTED from the shipped binary (BTL4OPT.EXE) Ghidra pseudo-C
// (the bt/btplayer.cpp cluster @004c012c-@004c0f28) cross-referenced against
// the surviving Red Planet sibling RPPLAYER.cpp/.h (same engine, same Player
// base, same scoring/message-handler/registry idioms) and the surviving BT
// headers BTTEAM.HPP, BTREG.HPP and MUNGA PLAYER.HPP / SCNROLE.HPP.
//
// BTPlayer is the BattleTech player/scoring entity. Where Red Planet's
// RPPlayer scored "score zones", speed bonuses and crusher/runner roles,
// BTPlayer scores mech-vs-mech combat: damage inflicted, damage received,
// kills and friendly-fire, all scaled by the players' relative tonnage and
// by a per-role ScenarioRole modifier set (see SCNROLE.HPP).
//
// Field offsets in comments are the byte offsets observed in the decompiled
// 0x294-byte object (e.g. "@0x278" == this[0x9e]). Names are taken from
// PLAYER.HPP / SCNROLE.HPP where the field is inherited, and inferred from
// usage (and flagged) where BT-specific. See btplayer.cpp for per-method
// @ADDR evidence.
//
#if !defined(BTPLAYER_HPP)
# define BTPLAYER_HPP
#if !defined(PLAYER_HPP)
# include <player.hpp>
#endif
#if !defined(SCNROLE_HPP)
# include <scnrole.hpp>
#endif
#if !defined(CSTR_HPP)
# include <cstr.hpp>
#endif
//##################### Forward Class Declarations #######################
class Mech;
class BTTeam;
class DropZone__ReplyMessage;
//###########################################################################
//##################### BTPlayer::ScoreMessage ########################
//###########################################################################
//
// Extends the MUNGA Player::ScoreMessage (which carries only scoreAward
// @0x1c) with a scoreType selector and the EntityID of the mech that
// generated the points. Parallels RPPlayer__ScoreMessage, but the BT
// score types describe mech combat instead of score zones.
//
// Message layout, sizeof 0x3C (field meanings CORRECTED 2026-08-05 from the
// raw disasm of the three sender builds in the Mech::TakeDamageMessageHandler
// report tail @0x4a04da/@0x4a05d9/@0x4a06c0 -- the old "pointSender" names
// were guesses from RP's smaller message, which stops at +0x28):
// +0x1c scoreAward (Scalar, base) -- the sender puts the APPLIED
// damage tally here; the receiving handler
// overwrites it with the computed award before
// delegating to Player::ScoreMessageHandler
// +0x20 scoreType (int)
// +0x24 damageAmount (Scalar) the scoring BASIS: type 0 = tally,
// type 1 = intended (burstCount x amount),
// type 2 = the VICTIM role's killBonus (role+0x1c)
// +0x28 vitalHit (int) a zone reached BurningState during
// this message's applications (loop local_2c)
// +0x2c zoneIndex (int) the struck damage zone (msg+0x24,
// the INITIAL resolve -- not the last burst's)
// +0x30 subsysID (int) inflictingSubsystemID passed through
// from the TakeDamageMessage (+0x5c)
// +0x34 senderMechID (EntityID) types 0/2: the VICTIM mech;
// type 1: the INFLICTING mech
//
class BTPlayer__ScoreMessage:
public Player::ScoreMessage
{
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.
//
enum ScoreType {
DamageInflictedScore = 0, // sent, but scores nothing (see above)
DamageReceivedScore = 1, // I took damage
KillScore = 2 // I destroyed / was destroyed
};
int
scoreType; // +0x20
Scalar
damageAmount; // +0x24 scoring basis (see layout table)
int
vitalHit; // +0x28 zone entered BurningState this message
int
zoneIndex; // +0x2c struck damage zone
int
subsysID; // +0x30 inflicting weapon subsystem
EntityID
senderMechID; // +0x34 victim (types 0/2) / inflictor (type 1)
BTPlayer__ScoreMessage(
Receiver::MessageID message_ID,
size_t length,
int score_type,
Scalar score_award,
Scalar damage_amount,
const EntityID &sender_mech_ID,
int vital_hit = 0,
int zone_index = 0,
int subsys_ID = 0
):
Player::ScoreMessage(message_ID, length, score_award),
scoreType(score_type),
damageAmount(damage_amount),
vitalHit(vital_hit),
zoneIndex(zone_index),
subsysID(subsys_ID),
senderMechID(sender_mech_ID)
{}
};
//###########################################################################
//################# BTPlayer::VehicleDeadMessage ######################
//###########################################################################
//
// BT extension of the engine Player__VehicleDeadMessage (PLAYER.h:
// deathCount @0x1c, dropZoneID @0x20). Decoded 2026-08-05 from the death
// tail of Mech::TakeDamageMessageHandler (@0x4a07d4-0x4a0890, raw disasm):
// the binary builds a 0x38-byte message {size 0x38, id 0x17 ==
// Player::VehicleDeadMessageID, pri 1, deathCount -1 (the engine ctor
// default), dropZoneID Null, +0x28 = 0, +0x2c = the KILLER PLAYER's own
// EntityID (shooterPlayer+0x184), +0x34 = the killing zone (msg+0x24)} and
// dispatches it to the dying mech's own player -- the respawn-cycle
// trigger, now carrying the killed-by attribution. Whether the @004c05c4
// handler consumes the two extension fields is still undecoded (the
// function is not in the export); the sender carries them faithfully.
//
class BTPlayer__VehicleDeadMessage:
public Player::VehicleDeadMessage
{
public:
int
reserved28; // +0x28 zeroed by the binary (@0x4a0856)
EntityID
killedByPlayerID; // +0x2c the killer PLAYER's EntityID
int
killZone; // +0x34 zone the killing damage struck
BTPlayer__VehicleDeadMessage(
Receiver::MessageID message_ID,
size_t length,
const EntityID &killed_by_player_ID,
int kill_zone
):
Player::VehicleDeadMessage(message_ID, length), // deathCount -1, dropZone Null
reserved28(0),
killedByPlayerID(killed_by_player_ID),
killZone(kill_zone)
{}
};
//###########################################################################
//##################### BTPlayer::MakeMessage #########################
//###########################################################################
//
// Construction message. BTRegistry::MakePlayer (BTREG.CPP) builds this
// from the mission's role name and team name; the ctor (@004c0bc8) copies
// the team name out of it (+0x50) and resolves the role resource (+0x90).
//
class BTPlayer__MakeMessage:
public Player::MakeMessage
{
public:
//
// ⚠ WIRE FORMAT: MakeMessages are sent RAW over the network when the
// entity replicates (InterestManager -> remote Registry::MakeEntity), so
// string payload must be INLINE at the binary's fixed offsets -- teamName
// @ +0x50, roleName @ +0x90 (0x40 bytes each). The earlier draft stored
// const char* POINTERS: fine in one address space, garbage on the
// receiving pod (the first cross-pod BTPlayer replication crashed in
// CString(roleName)).
//
char
teamName[0x40]; // +0x50 team this player belongs to (inline)
char
roleName[0x40]; // +0x90 scenario-role resource name (inline)
BTPlayer__MakeMessage(
Receiver::MessageID message_ID,
size_t length,
Entity::ClassID class_ID,
const EntityID &owner_ID,
ResourceDescription::ResourceID resource_ID,
LWord instance_flags,
const Origin &origin,
int player_bitmap_index,
const char *role_name,
const char *team_name
):
Player::MakeMessage(
message_ID, length, class_ID, owner_ID,
resource_ID, instance_flags, origin, player_bitmap_index
)
{
teamName[0] = 0;
roleName[0] = 0;
if (team_name)
{
strncpy(teamName, team_name, sizeof(teamName) - 1);
teamName[sizeof(teamName) - 1] = 0;
}
if (role_name)
{
strncpy(roleName, role_name, sizeof(roleName) - 1);
roleName[sizeof(roleName) - 1] = 0;
}
}
};
//###########################################################################
//########################### BTPlayer ################################
//###########################################################################
//
// (vtable @00513300, ctor @004c0bc8, Make @004c0ecc, scalar-deleting
// destructor @004c0efc, TestInstance @004c0f28.)
//
class BTPlayer:
public Player
{
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Shared Data support
//
// WinTesla refactored the static derivation/handler/attribute objects
// behind Get* accessors (avoids static-init ordering bugs).
//
public:
static Derivation* GetClassDerivations();
static SharedData DefaultData;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Scoring / construction message typedefs
//
// (Declared up here so the message-handler signatures below resolve
// ScoreMessage to BTPlayer__ScoreMessage rather than the inherited
// Player::ScoreMessage.)
//
public:
typedef BTPlayer__ScoreMessage ScoreMessage;
typedef BTPlayer__MakeMessage MakeMessage;
typedef BTPlayer__VehicleDeadMessage VehicleDeadMessage; // BT 0x38 ext (killed-by)
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Message Support
//
// Handler table (MESSAGE_ENTRY names recovered from the .data string
// block @005130c0): DropZoneReply, VehicleDead, Score, ScoreUpdate,
// MissionStarting, MissionEnding -- plus the dedicated ScoreInflicted
// handler (assert string @00513110). ScoreInflicted is listed here as
// best-effort; its MESSAGE_ENTRY name was not captured in the dump.
//
public:
enum {
ScoreInflictedMessageID = Player::NextMessageID,
ScoreUpdateMessageID,
NextMessageID
};
private:
static const HandlerEntry MessageHandlerEntries[];
protected:
static MessageHandlerSet& GetMessageHandlers();
void
VehicleDeadMessageHandler(VehicleDeadMessage *message); // @004c05c4 (NOT @004c012c -- that address was a
// misattribution; the real handler is absent from
// functions_index.tsv, Gitea #60 export gap)
void
ScoreInflictedMessageHandler(ScoreMessage *message); // @004c0200
void
ScoreUpdateMessageHandler(ScoreMessage *message); // @004c02a8
void
ScoreMessageHandler(ScoreMessage *message); // @004c02e4
//
// @004bffd0 -- the spawn / respawn handshake. When the drop zone
// replies with our spawn location we create (or reset) the player's
// mech there and bind it to us. BT analog of Blocker/RPPlayer
// DropZoneReplyMessageHandler; reconstructed from FUN_004bffd0 (the
// MESSAGE_ENTRY(BTPlayer,DropZoneReply) handler at table @00512fd8).
//
void
DropZoneReplyMessageHandler(DropZone__ReplyMessage *message); // @004bffd0
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Vehicle creation
//
// CreatePlayerVehicle is the virtual the base Player calls to build the
// player's vehicle; BT overrides it (vtable slot +0x40 -> FUN_004bfcac)
// to instantiate a Mech from the mission's game-model resource.
// InitializePlayerLink (FUN_004bfee0) binds the freshly-made mech back to
// this player. Structural model = RPPlayer::CreatePlayerVehicle /
// ::InitializePlayerLink (same engine, same MakeAndLinkViewpointEntity).
//
protected:
virtual void
CreatePlayerVehicle(Origin mech_location); // @004bfcac (vtable +0x40)
void
InitializePlayerLink(); // @004bfee0
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Scoring Support
//
protected:
//
// @004c052c -- the shared "how many points is this damage worth"
// calculation used by both ScoreInflicted and the kill branch of
// ScoreMessageHandler. Applies the role's damageInflictedModifier,
// damageBias and (for same-team hits) friendlyFirePenalty.
//
Scalar
CalcInflictedScore(
const Scalar &damage_amount,
Mech *other_mech,
Scalar bias
);
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Model Support
//
public:
typedef void
(BTPlayer::*Performance)(Scalar time_slice);
void
SetPerformance(Performance performance)
{
Check(this);
activePerformance = (Simulation::Performance)performance;
}
void
PlayerSimulation(Scalar time_slice); // @004c083c (console update)
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Scoreboard replication (PORT record extension -- Gitea #45)
//
// The two scoreboard counters live ONLY on the owning pod. A kill is
// credited by `BTPostKillScore` on the VICTIM's node, which resolves the
// killer's Player -- a REPLICANT there -- and calls Dispatch() on it;
// `Entity::Dispatch` reroutes a replicant's message to the master
// (ENTITY.cpp:244-251), so the `++killCount` lands on the killer's OWN
// machine and nowhere else. DEATHS is the same shape (the victim's own
// VehicleDead path). Neither counter rode an update record --
// `Player__UpdateRecord` is currentScore + dropZoneLocation only
// (PLAYER.h:73-80) -- so every OTHER node's copy stayed 0 for the whole
// mission, which is exactly why the comm panel never showed a pilot's
// kills. [T0 the reroute + the record contents; T2 the field evidence.]
//
// The field evidence, stated as INVARIANT RATIOS rather than counts. (An
// earlier draft cited "125 of 125 over 255 node-logs"; that was true when
// measured but is not reproducible, because every rig run appends to the same
// corpus -- the verification runs for this very fix grew it from ~255 to 425
// files. Cite ratios that hold at any corpus size.) Over
// `matchlogs/**/*matchlog*.txt` + `content/matchlog*.txt` +
// `content/matchlogs/**`:
// * 0 of 18818 `DMG` rows are `inst=R` -- damage is applied MASTER-SIDE ONLY,
// so no bystander can attribute a kill even in principle;
// * 439 of 439 `DEATH inst=R` rows read `killer=0:0 killdmg=0.000`, against
// 0 of 225 `inst=M` rows -- a replicant victim's `lastInflictingID` is
// never populated locally (sole writer: mech.cpp:746).
// Both ratios are re-derivable with a one-line scan and neither drifts as the
// corpus grows.
//
// The owner's value is already the single authoritative copy -- incremented
// exactly once per kill, through the engine's own reroute -- so completing
// the loop is a one-directional owner->replicant replication with no
// convergence rule to invent. Same pattern as the port's MissileLauncher
// record extension (mislanch.hpp:110-120).
//
public:
struct BTPlayer__UpdateRecord:
public Player::UpdateRecord
{
int killTally; // @0x27c killCount -- the KILLS column
int deathTally; // @0x280 deathTally -- the DEATHS column
};
typedef BTPlayer__UpdateRecord UpdateRecord;
void WriteUpdateRecord(Simulation__UpdateRecord *message, int update_model); // base-typed param (override rule)
void ReadUpdateRecord(Simulation__UpdateRecord *message);
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Construction and Destruction
//
public:
static BTPlayer*
Make(MakeMessage *creation_message); // @004c0ecc
BTPlayer(
MakeMessage *creation_message,
SharedData &shared_data = DefaultData
); // @004c0bc8
~BTPlayer(); // @004c0efc (deleting dtor)
Logical
TestInstance() const; // @004c0f28
//
// gauge scoring wave: public scoreboard accessors so the cockpit gauges
// (PilotList KILLS/DEATHS) read the SAME compiled members the scoring
// handlers write -- the earlier raw-offset reads (pilot+0x27c / +0x200)
// don't match our compiled layout (engine base != the 1995 binary) and
// silently read 0. currentScore/playerRanking are inherited Player fields.
//
int GetKillCount() const { return killCount; } // @0x27c
int GetDeaths() const { return deathTally; } // @0x280 (#45: was the engine's deathCount @0x200)
Scalar GetScore() const { return currentScore; } // @0x278 (Player)
int GetRanking() const { return playerRanking; } // (Player)
Mech *GetObjectiveMech() const { return objectiveMech; } // @0x284 (current target)
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Local data
//
// Offsets are byte offsets into the shipped 0x294-byte object. The
// base/derived boundary is approximate; fields known to be inherited
// from Player/Entity (playerVehicle, deathCount, scenarioRole,
// currentScore, ownerID) are noted as such.
//
protected:
// --- inherited from Player / Entity, referenced here ---
// EntityID ownerID; // @0x18c (Entity)
// Mech *playerVehicle; // @0x1fc (Player)
// int deathCount; // @0x200 (Player)
// ScenarioRole *scenarioRole; // @0x208 (Player) -- scoring role
// Scalar currentScore; // @0x278 (Player) -- running score
// --- BT-specific ---
// (The binary's @0x1f8 mission pointer IS the inherited
// Player::playerMission -- the master ctor leaves the base's value and
// the replicant branch back-fills it from Application::GetCurrentMission.
// The old separate `btMission` member here was a phantom duplicate of
// the base field, never set on the master branch; removed in the
// issue-#2 experience wiring.)
char
teamName[64]; // @0x20c copied from MakeMessage (+0x50)
BTTeam
*teamEntity; // @0x24c resolved from the "Teams" group
Logical
freeForAll; // @0x250 game type == "freeforall" (no team scoring)
Logical
consoleAttached; // @0x254 a console host exists for us
Logical
suppressConsole; // @0x258 skip console notify for this score event
//
// EXPERIENCE-LEVEL game-mode flags (2026-07-18 [T1] — the old "per-role
// display toggles / returnFromDeath" reading was WRONG). The ctor
// @004c0bc8 seeds this block from playerMission(+0x1f8)->experienceLevel
// (+0xe4, the egg's per-pilot "experience" novice/standard/veteran/
// expert) and ->advancedDamageOn(+0xf0) — NOT from the scenario role.
// Renamed to the true semantics by the issue-#2 wiring (old scoring-era
// names in the trailing comments). Binary mapping (nov/std/vet/exp) +
// consumers: context/experience-levels.md.
//
Logical
simLive; // @0x25c 0/1/1/1 "sim live" novice lockout: jams, searchlight, powersub @4b0efc (was showDamageReceived)
Logical
heatModelOn; // @0x260 0/0/1/1 HEAT-MODEL master switch (FUN_004ad7d4) (was showKills)
Logical
advancedDamageOn; // @0x264 = mission advancedDamageOn (copy 1; NOT part of the level switch) (was showDamageInflicted)
Logical
levelFlag270; // @0x270 0/1/1/1 consumer not located (was showScore)
Logical
advancedDamageOn2; // @0x268 = mission advancedDamageOn (copy 2; int in the binary) (was roleReturnDelay, mistyped Scalar)
Logical
levelFlag26c; // @0x26c 0/1/1/0 consumer not located (supercharge-clamp hypothesis [T4]) (was roleReturnDelay2, mistyped Scalar)
int
experienceLevel; // @0x274 raw experience level 0..3 (FUN_004ac9c8 = ==0 novice predicate) (was roleClassIndex)
friend int BTPlayerRoleLocksAdvanced(void *); // the FUN_004ac9c8 bridge (task #12): experienceLevel == 0
friend int BTPlayerExperienceSimLive(void *); // +0x25c reader (issue #2): jams / lights / powersub short path
friend int BTPlayerExperienceHeatModelOn(void *); // +0x260 reader (issue #2): FUN_004ad7d4 heat-model gate
friend int BTPlayerAdvancedDamageOn(void *); // +0x268 reader (#83): FUN_0049ffcc collision-rattle gate
friend Scalar BTPlayerEjectBookkeeping(void *); // +0x258 latch + role killBonus (Mech::EjectPilot @0049f854)
friend int BTPlayerConsoleSuppressed(void *); // +0x258 reader (#89): death-blast eject gate @0x4a0ace
// TARGET DESIGNATION (Gitea #48/#57): the mapper used to write the target
// RAW at `pilot + 0x284` -- the BINARY's objectiveMech offset, which on our
// compiled object is `deathPending` (the respawn latch). These bridges
// replace all three raw sites with named members.
friend void BTPilotSetObjectiveMech(void *, void *);
friend void *BTPilotObjectiveMech(void *);
friend void BTPlayerLayoutSelfCheck(); // the offset static_asserts
int
killCount; // @0x27c kills credited to this player
//
// @0x280 -- the binary's DEATHS column. `PilotList::Execute` @0x4cabd0
// draws `fild [edi+0x27c]` (KILLS) and `fild [edi+0x280]` (DEATHS), and
// image-wide +0x280 has exactly two runtime writers (@0x4c0674/@0x4c067a)
// plus the ctor zero. This was declared `pad_0x280` and never written --
// the port displayed the ENGINE's `Player::deathCount` (+0x200) instead,
// which is really the respawn-handshake identity number (seeded -2,
// PLAYER.cpp:777, compared at :230) and needed a clamp to look like a
// count. Gitea #45 gives it its real job back: it is the deaths tally,
// it is what the gauge reads, and it is what replicates. Named
// `deathTally` deliberately -- `deaths`/`deathCount` would SHADOW the
// engine base (reconstruction-gotchas #1).
//
int
deathTally; // @0x280 deaths (our offset 0x274)
Mech
*objectiveMech; // @0x284 designated target / objective mech
Time
lastPerformance; // @0x288 cached sim clock
Time
lastConsoleUpdate; // @0x28c last time score was sent to console
Logical
deathPending; // @0x290 death notice scheduled / in flight
//
// Name of whoever last killed us (shown on the pilot HUD). Copied from
// the VehicleDead message in the binary; @0x1d0 (CString).
//
CString
killerName; // @0x1d0
};
#endif