//===========================================================================// // File: btl4mssn.cpp // // Project: BattleTech Brick: // // Contents: Implementation for the L4 (game-level) mission // //---------------------------------------------------------------------------// // Date Who Modification // // -------- --- ---------------------------------------------------------- // // 04/07/95 ECH Initial coding. // // 09/12/95 JM Derived from L4Mission // //---------------------------------------------------------------------------// // Copyright (C) 1995, Virtual World Entertainment, Inc. // // All Rights reserved worldwide // // This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL // //===========================================================================// // // RECONSTRUCTED from the shipped binary BTL4OPT.EXE. The only non-trivial // method, SetPlayerData, is recovered in full from the Ghidra pseudo-C at // @004d2c30 (the lone function the linker attributed to bt_l4/btl4mssn.cpp // via its embedded assert path "d:\tesla_bt\bt_l4\BTL4MSSN.CPP"). The // constructor / destructor were recovered from the adjacent code cluster // (@004d2be0 ctor, @004d2c04 deleting-dtor) and from the BTL4Mission vtable // @0051e870 = { dtor@004d2c04, SetPlayerData@004d2c30 }. TestInstance is a // debug-only consistency check; it is absent from the OPT-build vtable // (inlined to nothing) and is reconstructed best-effort to mirror the // surviving Red-Planet analog RPL4Mission::TestInstance. // // Ground-truth interfaces consulted (all survived in source): // BTL4MSSN.HPP - this class (BTL4Mission : public BTMission) // BTMSSN.HPP/CPP - the BTMission base (member layout + SetPlayerData stub) // SCNROLE.HPP - ScenarioRole (per-role damage modifiers) // Structural analog (Red Planet): RP_L4/RPL4MSSN.cpp / .h. // // Member-offset map (this == BTMission*, observed byte offsets): // +0x20 colorName char* "color" (Mission base) // +0x24 badgeName char* "badge" (Mission base) // +0x28 dropZoneName char* "dropzone" (Mission base) // +0x2c gameModel char* "vehicle" (Mission base) // +0x3c dropZoneHost Logical "loadzones" (Mission base) // +0x4c playerBitmapIndex int "bitmapindex" (Mission base) // +0x50 scenarioRoles locked dictionary ScenarioRole*> // +0x68 (name-bitmap table; GetLargeNameBitmap) // +0xe4 experienceLevel Enumeration (BTMission) // +0xe8 roleName CString "role" (BTMission) // +0xec teamName CString (see note) (BTMission) // +0xf0 advancedDamageOn Logical "advancedDamage" (BTMission) // +0xf4 missionTemperature Scalar "temperature" (BTMission, set in ctor) // +0xf8 patchName CString "patch" (BTMission) // // Engine-helper name mapping (FUN_ -> intended symbol): // FUN_0049b54c BTMission::BTMission(notation_file, resources) [base ctor] // FUN_0049b648 BTMission::~BTMission (deleting thunk) // FUN_0049b6bc BTMission::SetPlayerData (Fail stub - "should never reach") // DAT_004efc94 the global Application* // FUN_00404088 NotationFile::GetEntry(node, key, const char**) -> Logical // FUN_004040d8 NotationFile::GetEntry(node, key, int*/Logical*) -> Logical // FUN_00404118 NotationFile::GetEntry(node, key, Scalar*) -> Logical // FUN_00402300 strdup (+ Register_Pointer) // FUN_00402a98 CString::operator=(const char*) // FUN_004023f4 / FUN_00402460 CString constructors // FUN_004024d8 CString release (ref-count) // FUN_00402298 operator new (Make_Pointer) // FUN_00429a9c ScenarioRole::ScenarioRole(role_name, model_file) // FUN_004323c7 / FUN_00432405 dictionary Locker acquire / release // FUN_004d4b58 strcmp // FUN_004dbb24 DEBUG_STREAM << (stream insert) // FUN_004d9c38 std::flush / endl on DEBUG_STREAM // FUN_004036a0 PostQuitMessage / Exit(AbortExitCodeID) // FUN_0040385c Verify()/Fail(msg, file, line) // DAT_00524e20 / DAT_00524e68 the two debug streams (info / error) // #include #pragma hdrstop #if !defined(BTL4MSSN_HPP) # include #endif #if !defined(BTL4APP_HPP) # include #endif #if !defined(L4GREND_HPP) # include #endif #if !defined(L4HOST_HPP) # include #endif #if !defined(HOSTMGR_HPP) # include #endif #if !defined(NOTATION_HPP) # include #endif #if !defined(SCNROLE_HPP) # include #endif //########################################################################## //########################### BTL4Mission ############################ //########################################################################## // //############################################################################# // @004d2be0 -- chains to BTMission(notation_file, resources) (FUN_0049b54c), // then stamps the BTL4Mission vtable (&0051e870). Adds no data members of // its own (the surviving header has an empty class body); the base ctor reads // "mission"/"temperature" and seeds experienceLevel / advancedDamageOn. //############################################################################# // BTL4Mission::BTL4Mission( NotationFile *notation_file, ResourceFile *resources ): BTMission(notation_file, resources) { Check_Fpu(); } // //############################################################################# // @004d2c04 (deleting-destructor thunk; chains to ~BTMission @0049b648). // The author-level body is the trivial debug check, exactly as in the // Red-Planet analog ~RPL4Mission. //############################################################################# // BTL4Mission::~BTL4Mission() { Check(this); Check_Fpu(); } // //############################################################################# // Best-effort: not present in the OPT-build vtable @0051e870 (only the dtor // and SetPlayerData slots survive), so the body was inlined away. Mirrors // RPL4Mission::TestInstance -- defer to the base and report healthy. //############################################################################# // Logical BTL4Mission::TestInstance() const { BTMission::TestInstance(); return True; } // //############################################################################# // @004d2c30 -- the override of the BTMission::SetPlayerData "should never // reach" stub (BTMSSN.CPP / FUN_0049b6bc). Pulls this host's player page // out of the notation file and populates the mission's player description: // model, dropzone, color, badge, patch, experience level, advanced-damage // flag, scenario role (+ its damage modifiers), and the plasma-display badge // bitmap. Occupies vtable slot 1 (it is virtual via BTMission, though the // surviving headers do not repeat the `virtual` keyword). //############################################################################# // void BTL4Mission::SetPlayerData(NotationFile *notation_file) { Check(this); // //-------------------------------------------------------------- // Figure out which page we should be using to create the player //-------------------------------------------------------------- // Check(application); HostManager *host_manager = application->GetHostManager(); Check(host_manager); L4Host *host = (L4Host*)host_manager->GetLocalHost(); Check(host); CString player_node = host->GetSymbolicName(); // //---------------------------------------------------------- // Copy the game model and dropzone strings into the mission //---------------------------------------------------------- // gameModel = NULL; // @+0x2c const char *player_model = NULL; notation_file->GetEntry(player_node, "vehicle", &player_model); // DIAGNOSTIC: force a specific player mech model (e.g. BT_FORCE_MODEL=madcat) so we can // bring up / verify a given cockpit shell in-game with its own authentic eyepoint. { const char *force = getenv("BT_FORCE_MODEL"); if (force && force[0]) { player_model = force; } } if (!player_model) { DEBUG_STREAM << "Error: vehicle not specified!\n" << std::flush; PostQuitMessage(AbortExitCodeID); } Check_Pointer(player_model); gameModel = strdup(player_model); Register_Pointer(gameModel); dropZoneName = NULL; // @+0x28 const char *drop_zone = NULL; notation_file->GetEntry(player_node, "dropzone", &drop_zone); if (!drop_zone) { DEBUG_STREAM << "Error: dropzone not specified!\n" << std::flush; PostQuitMessage(AbortExitCodeID); } Check_Pointer(drop_zone); dropZoneName = strdup(drop_zone); Register_Pointer(dropZoneName); dropZoneHost = False; // @+0x3c notation_file->GetEntry(player_node, "loadzones", &dropZoneHost); // //---------------------------------------------------------- // Color //---------------------------------------------------------- // colorName = NULL; // @+0x20 const char *color_name = NULL; notation_file->GetEntry(player_node, "color", &color_name); if (!color_name) { DEBUG_STREAM << "Error: color not specified!\n" << std::flush; PostQuitMessage(AbortExitCodeID); } Check_Pointer(color_name); colorName = strdup(color_name); Register_Pointer(colorName); // //---------------------------------------------------------- // Patch (CString member, @+0xf8) //---------------------------------------------------------- // const char *patch_name = NULL; notation_file->GetEntry(player_node, "patch", &patch_name); if (!patch_name) { DEBUG_STREAM << "Error: patch not specified!\n" << std::flush; PostQuitMessage(AbortExitCodeID); } Check_Pointer(patch_name); patchName = patch_name; // //---------------------------------------------------------- // Badge (legacy char* member, @+0x24) //---------------------------------------------------------- // badgeName = NULL; // @+0x24 const char *badge_name = NULL; notation_file->GetEntry(player_node, "badge", &badge_name); if (!badge_name) { DEBUG_STREAM << "Error: badge not specified!\n" << std::flush; PostQuitMessage(AbortExitCodeID); } Check_Pointer(badge_name); badgeName = strdup(badge_name); Register_Pointer(badgeName); // //---------------------------------------------------------- // teamName (CString, @+0xec). NOTE: the shipped binary reads the // "badge" key here as well (a second, distinct "badge" string // literal is present in .rdata) and defaults to "DEFAULT" when the // page omits it. Faithful to BTL4OPT.EXE @004d2c30. //---------------------------------------------------------- // const char *team_name = NULL; if (!notation_file->GetEntry(player_node, "badge", &team_name)) { teamName = "DEFAULT"; } else { Check_Pointer(team_name); teamName = team_name; } // //---------------------------------------------------------- // Experience level (@experienceLevel +0xe4). Absence is tolerated // only for the "camera" page; "Unspecified" leaves the default. //---------------------------------------------------------- // const char *experience = "Unspecified"; if (!notation_file->GetEntry(player_node, "experience", &experience)) { if (strcmp(gameModel, "camera") != 0) { DEBUG_STREAM << player_node << " page missing expereince!" << std::endl; Fail("Exiting", "d:\\tesla_bt\\bt_l4\\BTL4MSSN.CPP", 240); } } if (strcmp("Unspecified", experience) != 0) { if (strcmp(experience, "novice") == 0) { experienceLevel = BTMission::NoviceMode; } else if (strcmp(experience, "standard") == 0) { experienceLevel = BTMission::StandardMode; } else if (strcmp(experience, "veteran") == 0) { experienceLevel = BTMission::VeteranMode; } else if (strcmp(experience, "expert") == 0) { experienceLevel = BTMission::ExpertMode; } else { DEBUG_STREAM << player_node << " has an unknown experience field of " << experience; Fail("Exiting", "d:\\tesla_bt\\bt_l4\\BTL4MSSN.CPP", 264); } } // //---------------------------------------------------------- // Advanced damage flag (@advancedDamageOn +0xf0) //---------------------------------------------------------- // Logical advanced_damage = False; if (notation_file->GetEntry(player_node, "advancedDamage", &advanced_damage)) { advancedDamageOn = advanced_damage ? True : False; } // //---------------------------------------------------------- // Scenario role (@roleName +0xe8) -- every player page must name a // role, and the role page must name a model. The ScenarioRole is // looked up (and created on first use) in the mission's role // dictionary (this+0x50), then its per-page score modifiers applied. //---------------------------------------------------------- // const char *role_node = NULL; if (!notation_file->GetEntry(player_node, "role", &role_node)) { DEBUG_STREAM << "Each Player page must have a role field !" << std::endl; Fail("Exiting", "d:\\tesla_bt\\bt_l4\\BTL4MSSN.CPP", 451); } else { Check_Pointer(role_node); roleName = role_node; const char *role_model = NULL; if (!notation_file->GetEntry(role_node, "model", &role_model)) { DEBUG_STREAM << role_node << " page or model field missing !" << std::endl; Fail("Exiting", "d:\\tesla_bt\\bt_l4\\BTL4MSSN.CPP", 445); } else { // // Find-or-create the ScenarioRole keyed by roleName. The DOS // binary kept the per-mission role dictionary inline (this+0x50) // and called dictionary.Find()/.Add() directly; the WinTesla // Mission base exposes that dictionary (scenarioRoleChain) through // GetScenarioRole()/AddScenarioRole() -- the latter already guards // against duplicate role names, so the inner Find() is folded in. // ScenarioRole *role = GetScenarioRole(roleName); if (role == NULL) { role = new ScenarioRole(roleName, CString(role_model)); Register_Object(role); AddScenarioRole(role); } // // Optional per-page score modifiers. // Scalar value; if (notation_file->GetEntry(role_node, "dmgInflctdMdfr", &value)) { role->SetDamageInflictedModifier(value); // ScenarioRole +0x0c } if (notation_file->GetEntry(role_node, "dmgRcvdMdfr", &value)) { role->SetDamageReceivedModifier(value); // ScenarioRole +0x10 } if (notation_file->GetEntry(role_node, "dmgBias", &value)) { role->SetDamageBias(value); // ScenarioRole +0x18 } if (notation_file->GetEntry(role_node, "friendlyFire", &value)) { role->SetFriendlyFirePenalty(value); // ScenarioRole +0x14 } int return_from_death; if (notation_file->GetEntry(role_node, "return", &return_from_death)) { role->SetReturnFromDeath(return_from_death); // ScenarioRole +0x28 } if (notation_file->GetEntry(role_node, "killBonus", &value)) { role->SetKillBonus(value); // ScenarioRole +0x1c } } } // //~~~~~~~~~~~~~~~~~~~~~~ // Get playerBitmapIndex (@+0x4c) and, if present, blit the badge // bitmap onto the external (plasma) display. //~~~~~~~~~~~~~~~~~~~~~~ // if (!notation_file->GetEntry(player_node, "bitmapindex", &playerBitmapIndex)) { DEBUG_STREAM << "No Bitmap for player" << player_node; DEBUG_STREAM << std::flush; } else { BitMap *bitmap = GetLargeNameBitmap(playerBitmapIndex); if (bitmap != NULL) { Check(application); L4GaugeRenderer *gauge_renderer = ((BTL4Application*)application)-> GetGaugeRenderer(); if (gauge_renderer != NULL) { Check(gauge_renderer); GraphicsDisplay *graphics_display = gauge_renderer->GetExternalDisplay(); if (graphics_display != NULL) { graphics_display->DrawBitMapOpaque( 1, // foreground color 0, // background color 0xFF, // bitmask GraphicsDisplay::Replace, // operation 0, // rotation 0, 0, // position bitmap, 0, // sx1 0, // sy1 bitmap->Data.Size.x - 1, // sx2 bitmap->Data.Size.y - 1 // sy2 ); graphics_display->Update(True); graphics_display->WaitForUpdate(); } } } } Check_Fpu(); }