//===========================================================================// // File: btl4gau3.cpp // // Project: BattleTech Brick: Gauge Renderer Manager // // Contents: Cockpit instrument library, part 3 -- multiplayer / tactical HUD // // displays (see btl4gau3.hpp). // //---------------------------------------------------------------------------// // Date Who Modification // // -------- --- ---------------------------------------------------------- // // 02/22/96 CPB Initial coding. // //---------------------------------------------------------------------------// // Copyright (C) 1996, Virtual World Entertainment, Inc. All rights reserved // // PROPRIETARY AND CONFIDENTIAL // //===========================================================================// // // RECONSTRUCTED from the shipped binary (BTL4OPT.EXE, @004c9bd0..@004cbea0) // and re-hosted onto the authentic 1995 engine (MUNGA GAUGE/GAUGREND + // MUNGA_L4 L4GAUGE). This TU links between btl4gau2.obj and btl4grnd.obj // (BTL4.MAK order). Per-method binary addresses cited at each definition. // // Recovered string evidence: // "SectorDisplay: Missing image " 0x51bc90 "Players" 0x51bcba/0x51bd97 // "PlayerStatus: Make player number " 0x51bcd9 // "CreateMutantPixelmap8: couldn't ..." 0x51bd0d/0x51bd40/0x51bd6d // "PilotList" 0x51bb4a "PlayerStatus" 0x51bb54 "MessageBoard" 0x51bb61 // "adpal.pcc" 0x51bd9f "adpal2.pcc" 0x51bda9 // "PlayerStatusMappingGroup" 0x51bdb4 // // DATABINDING RULE (the gauge scoring wave): every player read below goes // through the reconstructed Player/BTPlayer NAMED members (PLAYER.HPP / // BTPLAYER.HPP) -- currentScore, GetDeathCount(), GetPlayerVehicle(), // playerBitmapIndex, killCount -- never the 1995 binary byte offsets // (+0x1C4/+0x1C8/+0x1CC/+0x1E0/+0x1FC), which do not match our compiled // object layout. Feeds with no reconstructed source stay as clearly-marked // INERT DEFAULTS (the widget draws, the value is static). // #include #pragma hdrstop #if !defined(MECHMPPR_HPP) # include #endif #if !defined(MISSION_HPP) # include #endif #if !defined(BTL4GAU3_HPP) # include #endif #if !defined(APP_HPP) # include #endif #if !defined(BTPLAYER_HPP) # include #endif #if !defined(MECHMPPR_HPP) # include #endif #if !defined(NTTMGR_HPP) # include #endif // //############################################################################# // File-private player-roster resolution //############################################################################# // // The binary resolved pilots through the application's "Players" dictionary // node (FindByName @00403ad0) and, for the Comm roster, through the viewpoint // mech's ControlsMapper pilot array. Our reconstructed MechControlsMapper // (MECHMPPR.HPP) does not carry the pilot roster, so the 1995-native // equivalent is the EntityManager "Players" entity group: every non-camera // player joins it in Player::Player (MUNGA PLAYER.CPP), and the engine's own // Player::CalcRanking walks it with exactly this iterator idiom. Group order // is creation order == the mission's player join order. // static Player * ResolveRosterPlayer(int slot) { if (application == NULL) { return NULL; } EntityGroup *players = application->GetEntityManager()->FindGroup("Players"); if (players == NULL) { return NULL; } ChainIteratorOf iterator(players->groupMembers); Player *roster_player; int index = 0; while ((roster_player = (Player *) iterator.ReadAndNext()) != NULL) { if (index == slot) { return roster_player; } ++index; } return NULL; } // //############################################################################# // COMM-PAGE roster gate -- the cockpit's pilot list is NOT the mission's // player list. The binary fed it from the viewpoint mech's ControlsMapper // pilot array (mapper attributes PilotArrayPage / PilotArray, ids 15/16), // which the pod network authors; a mission with no pod roster leaves it // zero and the shipped cockpit draws the page EMPTY. // // A/B EVIDENCE (5.3.32): with the ungated walk our slot 0 resolved the solo // mission player and painted its icon in colour 0xff -- 404 magenta pixels // in the Comm title band where the shipped binary paints the black erase // rect. Gate the ROW SOURCE here, not PilotList::Execute: Execute's empty // branch still has to run, because erasing IS what the shipped page draws. // // PlayerStatus (the observer/camera scoreboard) keeps the ungated walk -- it // is a mission-wide standings page, not the pod roster. //############################################################################# // static Player * ResolveCommRosterPlayer(int slot) { if (application == NULL) { return NULL; } Entity *viewpoint = application->GetViewpointEntity(); if (viewpoint == NULL || viewpoint->GetSubsystemCount() <= 0) { return NULL; } Subsystem *controls = viewpoint->GetSubsystem(0); if (controls == NULL || !controls->IsDerivedFrom(MechControlsMapper::ClassDerivations)) { return NULL; } if (((MechControlsMapper *) controls)->GetPilotArray() == 0) { return NULL; // no pod roster authored -> the rows stay empty } return ResolveRosterPlayer(slot); } // //############################################################################# // KILLS access (the gauge scoring wave): the KILLS column must read the SAME // compiled member the score handlers increment -- BTPlayer::killCount // (BTPLAYER.CPP ScoreMessageHandler / VehicleDead path). The reconstructed // BTPLAYER.HPP keeps killCount protected with no accessor yet, so this thin // derived reader stands in for a GetKillCount() accessor: NAMED-member access // only, no offsets, no data members, never constructed. Retire it the day // BTPLAYER.HPP grows the accessor. //############################################################################# // class BTPlayerKillReader: public BTPlayer { public: static int KillsOf(Player *pilot) { return ((BTPlayerKillReader *) pilot)->killCount; } private: BTPlayerKillReader(); // reader cast only -- never constructed }; // // SELECT-TARGET highlight -- INERT DEFAULT. The binary tinted the roster row // of the local pilot's current target (local player -> objectiveMech -> its // player). BTPlayer::objectiveMech is protected with no accessor in the // reconstruction, so the row tint stays off (rows draw untinted; the KILLS / // DEATHS numerics are unaffected). // static Logical RosterPlayerIsSelected(Player * /*pilot*/, Player * /*local_player*/) { return False; } // // Player-name-bitmap cache -- INERT DEFAULT (the donor's own bring-up stub, // kept per the worksheet). The binary looked prebuilt name rasters up in the // application's name-bitmap cache by the player's name id (@0x1E0 == // Player::playerBitmapIndex). Returning NULL routes every caller to the // binary's own cache-miss branch (the tinted name box), never an AV. // static BitMap * LookupPlayerNameBitmap(int player_bitmap_index, Logical large) { // // The mission owns the name art: the egg's [smallbitmap] index page // lists one notation page of hex rows per pilot, and the authentic // Mission ctor loads them into smallNameBitmapChain (MISSION.CPP:525). // A player's `bitmapindex` egg entry selects its 1-based slot. A miss // still routes the caller to the binary's cache-miss branch (the // tinted name box), never an AV. // if (player_bitmap_index <= 0 || application == NULL) { return NULL; } Mission *mission = application->GetCurrentMission(); if (mission == NULL) { return NULL; } // // SMALL vs LARGE (A/B rig, 5.3.39): Mission carries both rasters. // The Comm page's OWN row is the wide box and takes the large one // -- the shipped page draws 'Aeolus' there in the tall face, and // the small raster left it visibly undersized and low in the box. // The seven strip slots keep the small raster: their boxes are // half the height. // return large ? mission->GetLargeNameBitmap(player_bitmap_index) : mission->GetSmallNameBitmap(player_bitmap_index); } //############################################################################# //############################################################################# // PlayerStatusMappingGroup @004c9bd0 //############################################################################# //############################################################################# // // The 28 named damage zones (PTR_s_dz_door_0051a240); each gets one // ColorMapperArmor tinting that zone of the mech-outline schematic. // static const char *const kPlayerStatusZone[playerStatusZoneCount] = { "dz_door","dz_dtorso","dz_hip","dz_larm","dz_ldleg","dz_lfoot","dz_lgun", "dz_ltorso","dz_luleg","dz_missle","dz_rarm","dz_rdleg","dz_rfoot","dz_rgun", "dz_rtorso","dz_ruleg","dz_searchlight","dz_utorso","dz_reardtorso", "dz_rearltorso","dz_rearrtorso","dz_rearutorso","dz_lmissle","dz_rmissle", "unused1","unused2","unused3","unused4" }; // //############################################################################# // @004c9bd0 -- build the 28 ColorMapperArmor zone gauges. For each name in // the damage-zone table resolve its index on the mech (GetDamageZoneIndex, // binary FUN_0042076c; -1 => inert) and construct a ColorMapperArmor wired to // that zone, incrementing the base colour index each iteration. //############################################################################# // PlayerStatusMappingGroup::PlayerStatusMappingGroup( GaugeRate rate, ModeMask mode_mask, L4GaugeRenderer *renderer, int graphics_port_number, Entity *mech, int base_color_index, const char *palette_a, const char *palette_b, const char * /*identification_string*/ ) { Check_Pointer(this); int i; for (i = 0; i < playerStatusZoneCount; ++i) { // // (BC4.52: CString temporaries are illegal in a ternary -- hoist.) // int zone_index = -1; if (mech != NULL) { zone_index = mech->GetDamageZoneIndex(kPlayerStatusZone[i]); } zone[i] = new ColorMapperArmor( rate, mode_mask, renderer, graphics_port_number, base_color_index + i, mech, palette_a, palette_b, zone_index, "ColorMapperArmor" ); Register_Object(zone[i]); } Check_Fpu(); } // //############################################################################# // @004c9ca8 -- destroy: delete all 28. //############################################################################# // PlayerStatusMappingGroup::~PlayerStatusMappingGroup() { Check(this); int i; for (i = 0; i < playerStatusZoneCount; ++i) { if (zone[i] != NULL) { Unregister_Object(zone[i]); delete zone[i]; zone[i] = NULL; } } Check_Fpu(); } Logical PlayerStatusMappingGroup::TestInstance() const { return True; } // //############################################################################# // @004c9cf4 -- Execute: run each element's Execute. //############################################################################# // void PlayerStatusMappingGroup::Execute() { Check(this); int i; for (i = 0; i < playerStatusZoneCount; ++i) { if (zone[i] != NULL) { zone[i]->Execute(); } } Check_Fpu(); } // //############################################################################# // @004c9d18 -- SetColor: push the state onto all 28. //############################################################################# // void PlayerStatusMappingGroup::SetColor(int state) { Check(this); int i; for (i = 0; i < playerStatusZoneCount; ++i) { if (zone[i] != NULL) { zone[i]->SetColor(state); } } Check_Fpu(); } //############################################################################# //############################################################################# // SectorDisplay @004c9d44 Make / @004c9e10 ctor //############################################################################# //############################################################################# // // @0x51a2b0 -- registered so the interpreter builds the CFG line // sectorDisplay( K, ModeAlwaysActive, helv15.pcc, 0, 3 ) (L4GAUGE.CFG:5146; // the port index + offset=(125,579) arrive via Make's args, not config // tokens). Keyword + parameter list VERBATIM (parse-critical). // MethodDescription SectorDisplay::methodDescription = { "sectorDisplay", SectorDisplay::Make, { { ParameterDescription::typeRate, NULL }, // p[0] rate [row0 type=1] { ParameterDescription::typeModeMask, NULL }, // p[1] modeMask [row1 type=2] { ParameterDescription::typeString, NULL }, // p[2] image [row2 type=9] { ParameterDescription::typeColor, NULL }, // p[3] color [row3 type=4] { ParameterDescription::typeColor, NULL }, // p[4] okColor [row4 type=4] PARAMETER_DESCRIPTION_END } }; // //############################################################################# // @004c9d44 -- Make. Construct, then check the grid image and warn // "SectorDisplay: Missing image " if absent (binary-faithful: the base // ctor self-registers the gauge regardless of the return value). //############################################################################# // Logical SectorDisplay::Make( int display_port_index, Vector2DOf position, Entity * /*entity -- unused*/, GaugeRenderer *gauge_renderer ) { ParameterDescription *parameterList = methodDescription.parameterList; Check(gauge_renderer); SectorDisplay *gauge = new SectorDisplay( parameterList[0].data.rate, parameterList[1].data.modeMask, (L4GaugeRenderer *) gauge_renderer, display_port_index, // graphics_port_number (FIX: was 0) position.x, position.y, // SetOrigin (FIX: was 0,0) parameterList[2].data.string, // grid/font image (helv15.pcc) parameterList[3].data.color, // numericColor parameterList[4].data.color, // gridColor / numeric okColor "SectorDisplay" ); Register_Object(gauge); //-------------------------------------------------------- // Make sure the grid image made it into the warehouse //-------------------------------------------------------- L4Warehouse *warehouse = (L4Warehouse *) gauge_renderer->warehousePointer; Check(warehouse); if (warehouse->bitMapBin.Get(parameterList[2].data.string) == NULL) { DEBUG_STREAM << "SectorDisplay: Missing image " << parameterList[2].data.string << "\n" << flush; Check_Fpu(); return False; } warehouse->bitMapBin.Release(parameterList[2].data.string); Check_Fpu(); return True; } // //############################################################################# // @004c9e10 -- ctor (vtable 0051beec). GraphicGauge base (owner 0); copy the // grid-image name; from the bitmap compute cellWidth=imageW/14, // gridLeft=cellWidth*12, gridRight=cellWidth*13-1, gridHeight=imageH; // SetOrigin; build two 3-digit NumericDisplays (@0 and @cellWidth*4). //############################################################################# // SectorDisplay::SectorDisplay( GaugeRate rate, ModeMask mode_mask, L4GaugeRenderer *renderer, int graphics_port_number, int x, int y, const char *image, int color, int ok_color, const char *identification_string ): GraphicGauge( rate, mode_mask, renderer, 0, // owner folded to 0 (binary base call) graphics_port_number, identification_string ) { Check_Pointer(this); //------------------------------------------------------------ // Execute reads gridImage per frame, so it must outlive the // (transient) config parameterList string -- keep a durable // copy (native nameCopy, cf. NumericDisplay ctor). //------------------------------------------------------------ gridImage = nameCopy(image); numericColor = color; // @0xA8 gridColor = ok_color; // @0xAC subject = NULL; // @0x90 sectorBaseA = 500; // @0xB0 (0x1f4) sectorBaseB = 500; // @0xB4 dirty = 1; // @0xB8 (binary leaves uninit; BecameActive sets it) //------------------------------------------------------------ // Size the grid cells from the image (Get / measure / Release, // the NumericDisplay ctor idiom -- the art stays warehoused). //------------------------------------------------------------ L4Warehouse *warehouse = (L4Warehouse *) renderer->warehousePointer; Check(warehouse); BitMap *grid = warehouse->bitMapBin.Get(gridImage); if (grid == NULL) { cellWidth = gridLeft = gridRight = gridHeight = 0; } else { cellWidth = grid->Data.Size.x / 14; gridLeft = cellWidth * 12; gridRight = cellWidth * 12 + cellWidth - 1; gridHeight = grid->Data.Size.y; warehouse->bitMapBin.Release(gridImage); } localView.SetOrigin(x, y); //------------------------------------------------------------ // NumericDisplay(warehouse, x, y, image, digits=3, // unsignedFormat, color, okColor) //------------------------------------------------------------ numericA = new NumericDisplay( warehouse, 0, 0, image, 3, NumericDisplay::unsignedFormat, color, ok_color); Register_Object(numericA); numericB = new NumericDisplay( warehouse, cellWidth * 4, 0, image, 3, NumericDisplay::unsignedFormat, color, ok_color); Register_Object(numericB); Check_Fpu(); } // //############################################################################# // @004c9f94 -- dtor. Free the copied name + both numerics; the base // GraphicGauge chain runs implicitly. //############################################################################# // SectorDisplay::~SectorDisplay() { Check(this); Unregister_Pointer(gridImage); delete[] gridImage; gridImage = NULL; Unregister_Object(numericA); delete numericA; numericA = NULL; Unregister_Object(numericB); delete numericB; numericB = NULL; Check_Fpu(); } // @004ca020 -- TestInstance (out-of-line forward to the base). Logical SectorDisplay::TestInstance() const { return GraphicGauge::TestInstance(); } // //############################################################################# // @004ca068 -- LinkToEntity (slot 9): cache the viewpoint-bind broadcast's // entity as the subject (APP.CPP MakeViewpointEntity -> GAUGREND.CPP slot-9 // forward arms this on the authentic engine). //############################################################################# // void SectorDisplay::LinkToEntity(Entity *entity) { Check(this); subject = entity; } // //############################################################################# // @004ca038 -- BecameActive (slot 3): mark dirty + reset both numerics. // NON-inactivating (does NOT chain the base). //############################################################################# // void SectorDisplay::BecameActive() { Check(this); dirty = 1; numericB->ForceUpdate(); // B then A -- binary order numericA->ForceUpdate(); } // //############################################################################# // @004ca07c -- Execute. Draw the two sector numerics from the linked mech's // world X/Z (Round @FUN_004dcd94 -- the native SCALAR.HPP Round), then on the // first active frame blit the grid-cell background. The binary gates purely // on subject != 0 (@0x4ca08e). //############################################################################# // void SectorDisplay::Execute() { Check(this); if (subject == NULL) { return; } const Point3D &mech_position = subject->localOrigin.linearPosition; int sector_a = Round(-mech_position.z * 0.01f) + sectorBaseA, // 100 m sectors sector_b = Round( mech_position.x * 0.01f) + sectorBaseB; numericA->Draw(&localView, (Scalar) sector_a); numericB->Draw(&localView, (Scalar) sector_b); if (dirty) { dirty = 0; L4Warehouse *warehouse = (L4Warehouse *) renderer->warehousePointer; BitMap *grid = warehouse->bitMapBin.Get(gridImage); if (grid != NULL) { localView.MoveToAbsolute(cellWidth * 3, 0); localView.SetColor(gridColor); localView.DrawBitMap(0, grid, gridLeft, 0, gridRight, gridHeight); warehouse->bitMapBin.Release(gridImage); } } Check_Fpu(); } //############################################################################# //############################################################################# // TeamStatusDisplay @004ca208 //############################################################################# //############################################################################# // // DEFERRED -- prose-only reconstruction (no recovered bodies, no // methodDescription, no config keyword registered; nothing constructs it). // Documented shape, banked for the implementing pass: // // @004ca208 -- ctor (vtable 0051bea8). GraphicGauge base. rowSpacing = // param_10; originY = param y - 0x20; flags = param_12. Reads the font // bitmap width to size the highlight (cellW = w/14; highlightWidth = // cellW*8 + 0x86, plus an extra column when flags&4). Builds 8 rows, each // with a player-number NumericDisplay and -- when flags&4 -- a kill // NumericDisplay; rows step down by rowSpacing. // @004ca39c -- dtor: delete the 8 numeric pairs, base dtor. // @004ca424 -- SetEnable: this[0x24] = enable. // @004ca438 -- BecameActive: selectedRow = 999; per-row prev = -1, // prevScore = -0.001f (0xba83126f); per-row prevRank = -1. // @004ca478 -- Execute. Resolve the local player's index/team; read the // "Players" roster, slot each player into [0..7] by player index (skipping // spectators); on an occupant change blit the player's name bitmap (or // clear), tinting friendly/self; update each kill numeric (binary +0x1c8 == // the reconstruction's BTPlayer::killCount); when flags&4, also track each // player's rank; finally redraw the selection underline on the local // player's row. // // (When built, the kill / rank / spectator feeds bind through the same named // Player/BTPlayer members this TU already uses -- see the DATABINDING RULE // note at the top of the file.) // //############################################################################# //############################################################################# // PilotList @004ca90c Make / @004ca958 ctor //############################################################################# //############################################################################# // // The Comm KILLS/DEATHS roster. Keyword + parameter list VERBATIM // (parse-critical): pilotList( C, ModeAlwaysActive, bigfont.pcc ). // MethodDescription PilotList::methodDescription = { "pilotList", PilotList::Make, { { ParameterDescription::typeRate, NULL }, // rate (C) { ParameterDescription::typeModeMask, NULL }, // mode (ModeAlwaysActive) { ParameterDescription::typeString, NULL }, // font (bigfont.pcc) PARAMETER_DESCRIPTION_END } }; // //############################################################################# // @004ca90c -- Make. position UNUSED (each entry carries absolute coords // from the DAT_0051af88 layout table). Always returns True (binary). //############################################################################# // Logical PilotList::Make( int display_port_index, Vector2DOf /*position -- unused*/, Entity * /*entity -- unused*/, GaugeRenderer *gauge_renderer ) { ParameterDescription *parameterList = methodDescription.parameterList; Check(gauge_renderer); PilotList *gauge = new PilotList( parameterList[0].data.rate, parameterList[1].data.modeMask, (L4GaugeRenderer *) gauge_renderer, display_port_index, // graphics_port_number parameterList[2].data.string, // font (bigfont.pcc) "PilotList" ); Register_Object(gauge); Check_Fpu(); return True; } // //############################################################################# // @004ca958 -- ctor (vtable 0051be64). GraphicGauge base; build 3 // NumericDisplays per entry from the PE-parsed 8x(x, y, layoutMode) table // (DAT_0051af88 -- exact bytes from BTL4OPT.EXE). //############################################################################# // PilotList::PilotList( GaugeRate rate, ModeMask mode_mask, L4GaugeRenderer *renderer, int graphics_port_number, const char *font_image, const char *identification_string ): GraphicGauge( rate, mode_mask, renderer, 0, // owner folded to 0 (binary base call) graphics_port_number, identification_string ) { Check_Pointer(this); currentSlot = 0; built = False; L4Warehouse *warehouse = (L4Warehouse *) renderer->warehousePointer; Check(warehouse); //------------------------------------------------------------ // DAT_0051af88 -- 8 x (x, y, layoutMode) //------------------------------------------------------------ static const int layout[8][3] = { {180,225,0},{17,443,1},{177,443,1},{337,443,1}, {497,443,1},{17,7,2},{177,7,2},{337,7,2} }; int i; for (i = 0; i < 8; ++i) { int x = layout[i][0], y = layout[i][1], layout_mode = layout[i][2]; int name_x, name_y, mech_x, mech_y; if (layout_mode == 1) { name_x = x - 8; name_y = y - 0x50; mech_x = x + 0x48; mech_y = y - 0x50; } else if (layout_mode == 2) { name_x = x - 8; name_y = y + 0x44; mech_x = x + 0x48; mech_y = y + 0x44; } else // layout_mode == 0 { name_x = x + 0x95; name_y = y - 0x0d; mech_x = x + 0xe8; mech_y = y - 0x0d; } entry[i].x = x; entry[i].y = y; entry[i].resolvedMech = 0; entry[i].selected = 0; entry[i].nameDisplay = new NumericDisplay( // KILLS warehouse, name_x, name_y, font_image, 2, NumericDisplay::signedBlankedZerosFormat, 0, 0xff); Register_Object(entry[i].nameDisplay); entry[i].mechDisplay = new NumericDisplay( // DEATHS warehouse, mech_x, mech_y, font_image, 2, NumericDisplay::signedBlankedZerosFormat, 0, 0xff); Register_Object(entry[i].mechDisplay); entry[i].scoreDisplay = new NumericDisplay( // erase-only warehouse, x, y, font_image, 1, NumericDisplay::signedBlankedZerosFormat, 0, 0xff); Register_Object(entry[i].scoreDisplay); } Check_Fpu(); } // //############################################################################# // @004cab10 -- dtor. Free the 3 numerics per entry; base chain implicit. //############################################################################# // PilotList::~PilotList() { Check(this); int i; for (i = 0; i < 8; ++i) { Unregister_Object(entry[i].nameDisplay); delete entry[i].nameDisplay; entry[i].nameDisplay = NULL; Unregister_Object(entry[i].mechDisplay); delete entry[i].mechDisplay; entry[i].mechDisplay = NULL; Unregister_Object(entry[i].scoreDisplay); delete entry[i].scoreDisplay; entry[i].scoreDisplay = NULL; } Check_Fpu(); } // @004cab8c -- TestInstance (out-of-line forward to the base). Logical PilotList::TestInstance() const { return GraphicGauge::TestInstance(); } // //############################################################################# // @004caba4 -- BecameActive. Invalidate every entry so the first cycle // repaints; currentSlot = 9 forces the wrap-reset on the first Execute. //############################################################################# // void PilotList::BecameActive() { Check(this); int i; for (i = 0; i < 8; ++i) { entry[i].resolvedMech = -1; } currentSlot = 9; } // //############################################################################# // @004cad70 -- DrawMechIcon. Blit the pilot's name bitmap (or, on a cache // miss -- always, while the name-bitmap cache is the inert default -- a // tinted 0x80 x 0x20 box), tinted by the selected flag. The binary's name // id read (@0x1E0) is the reconstruction's Player::playerBitmapIndex. //############################################################################# // void PilotList::DrawMechIcon(Player *pilot, int selected) { Check(this); Check(pilot); BitMap *name_bitmap = LookupPlayerNameBitmap(pilot->playerBitmapIndex, (currentSlot == 0) ? True : False); // slot 0 == the own row int box_color = selected ? 0xff : 0, blit_color = selected ? 0 : 0xff; if (name_bitmap == NULL) { localView.SetColor(box_color); localView.DrawFilledRectangleToRelative(0x80, 0x20); } else { localView.SetColor(blit_color); localView.DrawBitMapOpaque(box_color, 0, name_bitmap, 0, 0, name_bitmap->Data.Size.x - 1, name_bitmap->Data.Size.y - 1); } } // //############################################################################# // @004cabd0 -- Execute (one roster slot per frame). Look up the current slot // in the shared player roster; erase an empty slot, or draw the pilot's name // icon + KILLS + DEATHS for an occupied one. // // The KILLS/DEATHS numerics read the COMPILED named members the score // handlers write (BTPlayer::killCount / Player::deathCount) -- the binary's // raw pilot+0x27c / +0x200 offsets read a stale/garbage slot on our compiled // layout (the silent-zero scoreboard bug on record). //############################################################################# // void PilotList::Execute() { Check(this); // // The local pilot gates the whole body (binary: roster slot 0 of the // viewpoint mech's mapper roster; native = the mission player). // Player *local_player = application->GetMissionPlayer(); if (local_player == NULL) { return; } if (currentSlot > 7) // wrap the round-robin { currentSlot = 0; built = False; } Entry &e = entry[currentSlot]; Player *pilot; if (built == False) { // // SLOT 0 IS THE OWN ROW. Its layout entry is the odd one out // ({180,225} at layout mode 0, KILLS/DEATHS at +0x95/+0xe8 // instead of the strip boxes' +0x48), and the shipped Comm page // fills it with the LOCAL pilot -- name, kills, deaths -- while // the seven surrounding boxes stay empty in a solo mission. So // slot 0 takes the mission player directly; only slots 1..7 come // from the pod roster. // pilot = (currentSlot == 0) ? local_player : ResolveCommRosterPlayer(currentSlot); if (pilot == NULL && currentSlot > 0) { built = True; // past the first empty roster slot -> rest empty } } else { pilot = NULL; } if (pilot == NULL) { if (e.resolvedMech != 0) // (-1 after BecameActive) -> erase { localView.MoveToAbsolute(e.x, e.y); localView.SetColor(0); localView.DrawFilledRectangleToRelative(0x80, 0x20); e.nameDisplay->Erase(&localView); e.mechDisplay->Erase(&localView); e.scoreDisplay->Erase(&localView); } } else { int selected = RosterPlayerIsSelected(pilot, local_player); if ((int) pilot != e.resolvedMech || e.selected != selected) { localView.MoveToAbsolute(e.x, e.y); DrawMechIcon(pilot, selected); e.selected = selected; // (byte-faithful: the binary does NOT latch e.resolvedMech here, // so the icon redraws each occupied frame.) } e.nameDisplay->Draw(&localView, // KILLS (killCount) (Scalar) BTPlayerKillReader::KillsOf(pilot)); e.mechDisplay->Draw(&localView, // DEATHS (deathCount) (Scalar) pilot->GetDeathCount()); } ++currentSlot; Check_Fpu(); } //############################################################################# //############################################################################# // PlayerStatus @004cae90 Make / @004cb1a8 ctor //############################################################################# //############################################################################# // // @004cafac -- per-player pixmap recolour. INERT DEFAULT (the donor's own // bring-up stub, kept per the worksheet). The faithful body resolves the // mech's type-0x20 pixmap sub-resource from the application resource file // (warns "CreateMutantPixelmap8: couldn't find ..." on a miss), opens it as a // memory stream, reads the embedded image name (ReadStreamString @004caf50, // 0x50 chars -- deferred with this stub), looks the source PixMap8 up in the // renderer warehouse, and copies it biasing every palette index >= 0x20 by // (palette_base - 0x20) so each player's mech outline draws in its own colour // ramp. Deferred: the memory-stream read API + pixel copy are unmapped. // With this stub recoloredMech stays NULL -> Execute skips the outline blit; // the score, name box and alive/dead status box still render. // static Pixmap * CreateMutantPixelmap8( Entity * /*mech*/, L4Warehouse * /*warehouse*/, int /*palette_base*/, Pixmap *destination ) { return destination; } // // Keyword + parameter list VERBATIM (parse-critical). CFG (L4GAUGE.CFG:29): // PlayerStatus( D, ModeAlwaysActive, 1, helv15.pcc, 0, 11, 3, 1 ) // MethodDescription PlayerStatus::methodDescription = { "PlayerStatus", PlayerStatus::Make, { { ParameterDescription::typeRate, NULL }, // p[0] rate { ParameterDescription::typeModeMask, NULL }, // p[1] mode mask { ParameterDescription::typeInteger, NULL }, // p[2] player number 1..8 { ParameterDescription::typeString, NULL }, // p[3] font image { ParameterDescription::typeColor, NULL }, // p[4] color { ParameterDescription::typeColor, NULL }, // p[5] okColor { ParameterDescription::typeColor, NULL }, // p[6] aliveColor { ParameterDescription::typeColor, NULL }, // p[7] deadColor PARAMETER_DESCRIPTION_END } }; // //############################################################################# // @004cae90 -- Make. The player number must be 1..8 or "PlayerStatus: Make // player number " is warned (string 0x51bcd9). Reads the parameterList // like every registered widget (the binary read raw DAT_ pools) + the // port/x/y bug fixed on record (the binary-side Make passed entity/renderer // as x/y and dropped the port). //############################################################################# // Logical PlayerStatus::Make( int display_port_index, Vector2DOf position, Entity * /*entity -- unused by PlayerStatus*/, GaugeRenderer *gauge_renderer ) { ParameterDescription *parameterList = methodDescription.parameterList; Check(gauge_renderer); int player_number = parameterList[2].data.integer; if (player_number < 1 || player_number > 8) { DEBUG_STREAM << "PlayerStatus: Make player number " << player_number << " out of range\n" << flush; return False; } PlayerStatus *gauge = new PlayerStatus( parameterList[0].data.rate, parameterList[1].data.modeMask, (L4GaugeRenderer *) gauge_renderer, display_port_index, // graphics_port_number <-- FIX (was dropped) position.x, position.y, // x, y <-- FIX (was entity/renderer) player_number, parameterList[3].data.string, parameterList[4].data.color, parameterList[5].data.color, parameterList[6].data.color, parameterList[7].data.color, "PlayerStatus" ); Register_Object(gauge); Check_Fpu(); return True; } // //############################################################################# // @004cb1a8 -- ctor (vtable 0051be20). GraphicGauge base (owner folded to // 0); stash the four state colours + port; set the port origin; build the // score NumericDisplay (5 digits @ (0x72, 0xD8)). //############################################################################# // PlayerStatus::PlayerStatus( GaugeRate rate, ModeMask mode_mask, L4GaugeRenderer *renderer, int graphics_port_number, int x, int y, int player_number, const char *font_image, int color_in, int ok_color, int alive_color, int dead_color, const char *identification_string ): GraphicGauge( rate, mode_mask, renderer, 0, // owner folded to 0 (binary base call) graphics_port_number, identification_string ) { Check_Pointer(this); color = color_in; // @0xA4 okColor = ok_color; // @0xA8 aliveColor = alive_color; // @0xAC deadColor = dead_color; // @0xB0 port = graphics_port_number; // @0x90 localView.SetOrigin(x, y); playerIndex = player_number - 1; // @0x94 player = NULL; // @0x98 nameImage = NULL; // @0x9C recoloredMech = NULL; // @0xA0 mappingGroup = NULL; // @0xB4 L4Warehouse *warehouse = (L4Warehouse *) renderer->warehousePointer; Check(warehouse); scoreDisplay = new NumericDisplay( // @0xB8 warehouse, 0x72, 0xD8, font_image, 5, NumericDisplay::signedBlankedZerosFormat, color, okColor); Register_Object(scoreDisplay); // dirty/previousStatus/previousMappingState set by BecameActive // (byte-faithful). Check_Fpu(); } // //############################################################################# // @004cb28c -- dtor. Free the score numeric + the recoloured mech pixmap; // base chain implicit. (nameImage is cache-owned; mappingGroup deleted here // as a correctness fix on record -- the binary leaks it.) //############################################################################# // PlayerStatus::~PlayerStatus() { Check(this); player = NULL; Unregister_Object(scoreDisplay); delete scoreDisplay; scoreDisplay = NULL; if (recoloredMech != NULL) { delete recoloredMech; recoloredMech = NULL; } if (mappingGroup != NULL) { delete mappingGroup; // (the binary leaks this) mappingGroup = NULL; } Check_Fpu(); } // @004cb2f8 -- TestInstance (out-of-line forward to the base). Logical PlayerStatus::TestInstance() const { return GraphicGauge::TestInstance(); } // //############################################################################# // @004cb310 -- BecameActive: reset the score readout, run the mapping group // once, and force a full redraw next Execute. //############################################################################# // void PlayerStatus::BecameActive() { Check(this); scoreDisplay->ForceUpdate(); if (mappingGroup != NULL) { mappingGroup->Execute(); // @004c9cf4 } dirty = 1; previousStatus = -1; previousMappingState = -1; } // //############################################################################# // @004cb358 -- Execute. Resolve player N from the shared roster (the binary // indexed the application's "Players" node; native = the "Players" entity // group, creation order); on a change rebuild the recoloured mech pixmap + // the 28-zone mapping group; each frame draw the score + mech outline + name // box + alive/dead status box. //############################################################################# // void PlayerStatus::Execute() { Check(this); //--- PART 1: resolve / change-detect the player ------------------------ Player *resolved = ResolveRosterPlayer(playerIndex); if (resolved != player) { player = resolved; if (player != NULL) { Entity *mech = player->GetPlayerVehicle(); // named accessor (was raw +0x1FC) if (mech == NULL) { player = NULL; // no vehicle yet -> retry next frame } else { L4Warehouse *warehouse = (L4Warehouse *) renderer->warehousePointer; int palette_base = playerIndex * 0x1C + 0x10; recoloredMech = CreateMutantPixelmap8( mech, warehouse, palette_base, recoloredMech); if (mappingGroup != NULL) { delete mappingGroup; } mappingGroup = new PlayerStatusMappingGroup( rate, modeMask, (L4GaugeRenderer *) renderer, port, mech, palette_base, "adpal.pcc", "adpal2.pcc", "PlayerStatusMappingGroup"); // INERT DEFAULT: the application name-bitmap cache is not // reconstructed -> the filled name-box branch below draws. nameImage = NULL; BecameActive(); // force a full redraw } } } //--- PART 2: per-frame draw (only with a bound player) ----------------- if (player == NULL) { return; } Entity *mech = player->GetPlayerVehicle(); if (mech == NULL) { return; } // // (a) score readout -- the published Player::currentScore (the same // member the score handlers write; was raw +0x1C8-as-Scalar). // scoreDisplay->Draw(&localView, player->currentScore); // // (b) mapping-group disabled-state refresh -- INERT DEFAULT: the binary // keyed this off the mech's damage-mapping derivation; the recon has // no bound source in this TU (Mech::IsDisabled needs mech.hpp, which // collides here), so the group holds the enabled tint. // int disabled = 0; if (disabled != previousMappingState) { previousMappingState = disabled; if (mappingGroup != NULL) { mappingGroup->SetColor(disabled); // @004c9d18 } } // // (c) mech outline + name box, dirty-gated // if (dirty) { dirty = 0; if (recoloredMech != NULL) { localView.MoveToAbsolute(1, 1); localView.DrawPixelMap8(True, 0, recoloredMech, 0, 0, recoloredMech->Data.Size.x - 1, recoloredMech->Data.Size.y - 1); } localView.MoveToAbsolute(1, 0xCF); if (nameImage == NULL) // no name bitmap -> filled box in `color` { localView.SetColor(color); localView.DrawFilledRectangleToRelative(0x80, 0x20); } else { localView.SetColor(0xFF); localView.DrawBitMapOpaque(color, 0, nameImage, 0, 0, nameImage->Data.Size.x - 1, nameImage->Data.Size.y - 1); } } // // (d) alive/dead status box, state-change gated. INERT DEFAULT: the // binary keyed this off the player record's status word (@0x1C4); no // alive/dead member is published on the reconstructed Player/BTPlayer // yet, so the box paints once in aliveColor and holds. // int state = 0; if (state != previousStatus) { previousStatus = state; localView.SetColor(state == 0 ? aliveColor : deadColor); localView.MoveToAbsolute(0, 0); localView.DrawFilledRectangleToAbsolute(0x9F, 0xEF); } Check_Fpu(); } //############################################################################# //############################################################################# // MessageBoard @004cb678 Make / @004cb704 ctor //############################################################################# //############################################################################# // // Keyword + parameter list VERBATIM (parse-critical). CFG shape // (L4GAUGE.CFG:4913, port=sec, offset=(113,607)): // messageBoard( E, ModeAlwaysActive, btsmsgs.pcx, 0 ) // MethodDescription MessageBoard::methodDescription = { "messageBoard", MessageBoard::Make, { { ParameterDescription::typeRate, NULL }, // p[0] rate (E) { ParameterDescription::typeModeMask, NULL }, // p[1] mode (ModeAlwaysActive) { ParameterDescription::typeString, NULL }, // p[2] strip bitmap (btsmsgs.pcx) { ParameterDescription::typeColor, NULL }, // p[3] draw/erase color (0) PARAMETER_DESCRIPTION_END } }; // //############################################################################# // @004cb678 -- Make. Construct + presence-check the strip bitmap (the binary // returns whether it exists). The `entity` param is ignored by the binary. //############################################################################# // Logical MessageBoard::Make( int display_port_index, Vector2DOf position, Entity * /*entity -- ignored by the binary*/, GaugeRenderer *gauge_renderer ) { ParameterDescription *parameterList = methodDescription.parameterList; Check(gauge_renderer); MessageBoard *gauge = new MessageBoard( parameterList[0].data.rate, parameterList[1].data.modeMask, (L4GaugeRenderer *) gauge_renderer, display_port_index, // graphics_port_number position.x, position.y, // offset=(113,607) -> SetOrigin parameterList[2].data.string, // strip image (btsmsgs.pcx) parameterList[3].data.color, // color (0) "MessageBoard" ); Register_Object(gauge); L4Warehouse *warehouse = (L4Warehouse *) gauge_renderer->warehousePointer; Check(warehouse); if (warehouse->bitMapBin.Get(parameterList[2].data.string) == NULL) { Check_Fpu(); return False; } warehouse->bitMapBin.Release(parameterList[2].data.string); Check_Fpu(); return True; } // //############################################################################# // @004cb704 -- ctor (vtable 0051bddc). GraphicGauge base (owner 0); copy + // hold the strip bitmap; SetOrigin; color = param. trackedMech starts NULL. //############################################################################# // MessageBoard::MessageBoard( GaugeRate rate, ModeMask mode_mask, L4GaugeRenderer *renderer, int graphics_port_number, int x, int y, const char *strip_image, int color_in, const char *identification_string ): GraphicGauge( rate, mode_mask, renderer, 0, // owner folded to 0 (binary base call) graphics_port_number, identification_string ) { Check_Pointer(this); stripImage = nameCopy(strip_image); L4Warehouse *warehouse = (L4Warehouse *) renderer->warehousePointer; Check(warehouse); warehouse->bitMapBin.Get(stripImage); // hold a reference for life localView.SetOrigin(x, y); color = color_in; // @0x98 trackedMech = NULL; // @0x90 // previousNameId / previousMessageId set by BecameActive (byte-faithful). Check_Fpu(); } // //############################################################################# // @004cb788 -- dtor. Release + free the strip; base chain implicit. //############################################################################# // MessageBoard::~MessageBoard() { Check(this); L4Warehouse *warehouse = (L4Warehouse *) renderer->warehousePointer; warehouse->bitMapBin.Release(stripImage); Unregister_Pointer(stripImage); delete[] stripImage; stripImage = NULL; Check_Fpu(); } // @004cb7e4 -- TestInstance (out-of-line forward to the base). Logical MessageBoard::TestInstance() const { return GraphicGauge::TestInstance(); } // //############################################################################# // @004cb818 -- SetSource: bind the source mech. (No recovered caller in the // shipped binary -> the board is authentically dormant; see Execute.) //############################################################################# // void MessageBoard::SetSource(Entity *mech) { Check(this); trackedMech = mech; } // //############################################################################# // @004cb7fc -- BecameActive: reset the change-sentinels. NON-inactivating. //############################################################################# // void MessageBoard::BecameActive() { Check(this); previousNameId = -1; // @0x9C previousMessageId = -2; // @0xA0 } // //############################################################################# // @004cb82c -- Execute. Draw the message strip cell + sender name when they // change. // // FAITHFUL / DEFERRED: the board only draws while a source mech is bound; the // binder (SetSource) has no recovered caller AND the per-player status feed // (the pool that fills Player::statusMessagePointer, PLAYER.HPP) is not // reconstructed, so trackedMech stays NULL and Execute is a safe no-op == the // authentically empty board. The resolve below is an INERT DEFAULT ("no // message"); the cell math is byte-verified and goes live the day the feed // and the SetSource caller are built. //############################################################################# // void MessageBoard::Execute() { Check(this); if (trackedMech == NULL) // binary gate { return; } // // INERT DEFAULT message resolve: the native record is the tracked mech's // player's statusMessagePointer (messageType -> strip cell id, // playerInvolved -> sender name bitmap), but its producer pool is // unbuilt -- report "no message" until it lands. // int messageId = -1; BitMap *nameBitmap = NULL; //--- message strip cell ------------------------------------------------- if (messageId != previousMessageId) { previousMessageId = messageId; localView.MoveToAbsolute(0, 0); if (messageId == -1) { localView.SetColor(color); localView.DrawFilledRectangleToRelative(0x80, 0x20); } else { int cellX = (messageId & 3) << 7, // 0x80-wide cells, 4 across cellY = (messageId >> 2) << 5; // 0x20-tall rows localView.SetColor(7); // FUN_004c2f88() == 7 L4Warehouse *warehouse = (L4Warehouse *) renderer->warehousePointer; BitMap *strip = warehouse->bitMapBin.Get(stripImage); if (strip != NULL) { localView.DrawBitMapOpaque(color, 0, strip, cellX, cellY, cellX + 0x7f, cellY + 0x1f); warehouse->bitMapBin.Release(stripImage); } } } //--- sender name cell (x=128). The binary tracks the previous name // ENTITY pointer @0x9C; the reconstruction tracks the resolved BITMAP // pointer -- the same change-detection granularity (a new sender = a // different prebuilt raster), no entity re-deref. int nameState = (int) nameBitmap; if (nameState != previousNameId) { localView.MoveToAbsolute(0x80, 0); if (nameBitmap == NULL) { localView.SetColor(color); localView.DrawFilledRectangleToRelative(0x80, 0x20); } else { localView.SetColor(7); localView.DrawBitMapOpaque(color, 0, nameBitmap, 0, 0, nameBitmap->Data.Size.x - 1, nameBitmap->Data.Size.y - 1); } previousNameId = nameState; } Check_Fpu(); } // === btl4grnd.cpp begins at @004cbea0 (BTL4GaugeRenderer ctor) -- not this TU.