//===========================================================================// // 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 #endif #if !defined(SCNROLE_HPP) # include #endif #if !defined(CSTR_HPP) # include #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. // // Observed message layout (param_2 in the handlers): // +0x1c scoreAward (Scalar, base Player::ScoreMessage) // +0x20 scoreType (int) // +0x24 damageAmount (Scalar) raw damage / point quantity // +0x28 pointSenderHi (EntityID word) // +0x2c pointSenderLo (EntityID word) // +0x30 auxID (EntityID / host word) // +0x34 senderMechID (EntityID -- resolved to 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) and the dedicated // inflicted-damage handler at @004c0200. // enum ScoreType { DamageInflictedScore = 0, // to ScoreInflictedMessageHandler DamageReceivedScore = 1, // I took damage KillScore = 2 // I destroyed / was destroyed }; int scoreType; // +0x20 Scalar damageAmount; // +0x24 // // Point-source / auxiliary handles carried alongside the damage record // (read by ScoreMessageHandler when building the console feed message). // int pointSenderHi; // +0x28 int pointSenderLo; // +0x2c int auxID; // +0x30 EntityID senderMechID; // +0x34 inflicting mech (point sender) BTPlayer__ScoreMessage( Receiver::MessageID message_ID, size_t length, int score_type, Scalar score_award, Scalar damage_amount, const EntityID &sender_mech_ID ): Player::ScoreMessage(message_ID, length, score_award), scoreType(score_type), damageAmount(damage_amount), senderMechID(sender_mech_ID) {} }; //########################################################################### //##################### 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; //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // 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); // @004c012c 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) //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // 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 deathCount; } // @0x200 (Player) 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 --- Mission *btMission; // @0x1f8 cached mission / role registry source 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 // // Per-role display toggles, selected from the role's "returnFromDeath" // class index (scenarioRole resource +0xe4, values 0..3) at @004c0bc8. // Logical showDamageReceived; // @0x25c Logical showKills; // @0x260 Logical showDamageInflicted;// @0x264 Logical showScore; // @0x270 Scalar roleReturnDelay; // @0x268 (role resource +0xf0) Scalar roleReturnDelay2; // @0x26c int roleClassIndex; // @0x274 (role resource +0xe4) friend int BTPlayerRoleLocksAdvanced(void *); // the FUN_004ac9c8 bridge (task #12) friend void BTPlayerCountObservedDeath(void *); // observed-death tally (MP DEATHS fix) int killCount; // @0x27c kills credited to this player int pad_0x280; // @0x280 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