Files
BT412/game/reconstructed/btl4gau3.cpp
T
arcattackandClaude Opus 4.8 b6918632fc gauges: reconstruct + register PlayerStatus (mission-review scoreboard)
PlayerStatus (btl4gau3) had only a BUGGY Make defined (it read raw DAT_ pools,
passed (int)entity/(int)gauge_renderer as x/y, and DROPPED the graphics_port_number
param -- the header ctor was one int short); the ctor/Execute/dtor/BecameActive and
the PlayerStatusMappingGroup + CreateMutantPixelmap8 helpers were comment-only
stubs.  Reconstructed the whole family (mapped by the playerstatus-decomp-map
workflow, 6 agents):

* methodDescription (8 params: rate,modeMask,integer player#,string font,4x color)
  + rewired Make reading parameterList[] with the port/x/y bug fixed.
* ctor (@004cb1a8): GraphicGauge base (owner folded to 0), store colours+port,
  SetOrigin, playerIndex=player_number-1, build a score NumericDisplay (fmt 2 =
  signedBlankedZeros).  Fixed the header layout: nameImage is a BitMap* (not a
  Pixmap), and two missing members (dirty@0x2F, previousStatus@0x30).
* dtor / BecameActive / TestInstance.
* PlayerStatusMappingGroup (@004c9bd0): 28 ColorMapperArmor zone tints over the
  mech-outline schematic, one per dz_* damage zone (mech->GetDamageZoneIndex),
  using the EXISTING 10-arg ColorMapperArmor ctor (per the workflow's correction --
  changing it would break cmArmor); added ColorMapper/Armor::SetColor (FUN_004c3c38,
  stores @0x6C).
* Execute (@004cb358): resolve the player (WinTesla-clean via GetMissionPlayer for
  the local player, instead of the binary's raw App+0x24 "Players"-node dictionary
  walk), then draw the score + name box + alive/dead status box; null-guarded.

KEY FINDING: PlayerStatus lives in the config's `cameraInit` block (the MISSION-
REVIEW / spectator camera cockpit), NOT `MechInit` -- so it is NOT part of the mech
cockpit and does not build during normal mech play (which is why the mech test shows
it un-regressed and its Execute never runs).  It is the post-mission scoreboard
(all 8 players' name/score/mech-status), rendered by the BTCameraDirector game
model.  Registered in BTL4MethodDescription[] so `cameraInit` builds it instead of
parse-skipping.

STATE: complete reconstruction; compiles, links (no new /FORCE unresolved), and the
mech cockpit is un-regressed (TARGET DESTROYED, 0 crashes).  NOT runtime-verified --
cameraInit is only built by the mission-review camera model, which a normal
`vehicle=<mech>` egg does not trigger.  BRING-UP STUB (marked): CreateMutantPixelmap8
returns NULL (the mech-outline recolor needs the DynamicMemoryStream read API +
Pixmap pixel-copy mapped) -> the score/name-box/status-box render but not the
recoloured mech schematic.  BT_PS_LOG traces the resolve.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 23:13:32 -05:00

576 lines
24 KiB
C++

//===========================================================================//
// 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). Behaviour follows the
// Ghidra pseudo-C in recovered/all/part_014.c (@004c9bd0..@004cbea0). Class /
// member names from the CODE identification strings; per-method @ADDR cited.
// This TU links between btl4gau2.obj and btl4grnd.obj.
//
// Engine-internal helper map (consistent with btl4gau2.cpp):
// FUN_00444818 GraphicGauge ctor FUN_00444870 GraphicGauge dtor
// FUN_004700bc NumericDisplay ctor FUN_0047018c ::dtor
// FUN_00470430 ::DrawAt FUN_00470758 ::Erase FUN_004703f4 ::Reset
// FUN_00442aec/00442c12 BitMapCache AddRef/Release
// FUN_00442d2b/00442e51 PixMapCache AddRef/Release
// FUN_004700ac InternName FUN_004022e8 FreeName
// FUN_00403ad0 NamedNode::Find(dictionary, name) ("Players")
// FUN_00421414 ChainOf<Player*>::Iterator ctor FUN_00421452 ::dtor
// FUN_0042076c Subsystem::FindDamageZone -> index
// FUN_004c3b98 ColorMapperArmor ctor (btl4gaug)
// FUN_004c2f88 -> 7 (selection/blip colour) FUN_004c2f94 entity colour
// FUN_0044fc10 score/rank lookup FUN_004b0898 lance-slot mech lookup
// FUN_0049fb54 / 0x50bdb4 damage-mapping-group derivation
// FUN_00449c88 Pixmap ctor FUN_00449d0c Pixmap dtor
// FUN_00407064 Warehouse::FindResource (App+0x68) FUN_004032dc MemoryStream
// FUN_004dcd94 Round FUN_004dbb24 DebugStream<<char* FUN_004db78c <<int
// DAT_004efc94 the Application (App+0x24 dictionary, +0x68 warehouse,
// +0xc8 palette cache, +0x6c local mech)
//
// 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
//
// See btl4gaug.cpp: suppress heat.hpp's optional DebugStream so the shared
// mechrecon ReconStream definition is the only one active in this TU.
#define BT_DEBUGSTREAM_DEFINED
#include <bt.hpp>
#pragma hdrstop
#if !defined(BTL4GAU3_HPP)
# include <btl4gau3.hpp>
#endif
#if !defined(APP_HPP)
# include <app.hpp>
#endif
//
// Unrecovered .data resource pools used by the two Make() factories below. The
// concrete bytes (rate/mode/colours/image names) were not recovered from the
// optimised image; declared here as typed placeholders so the construction
// paths compile. (BEST-EFFORT.) DebugStream is provided by the shared recon
// headers.
//
static int DAT_0051a2bc = 0; // SectorDisplay GaugeRate
static int DAT_0051a300 = 0; // SectorDisplay ModeMask
static char DAT_0051a344[] = ""; // SectorDisplay grid image name
// (PlayerStatus's DAT_ placeholder pool removed -- its rewired Make reads
// methodDescription.parameterList[] like the other registered widgets.)
//###########################################################################
// PlayerStatusMappingGroup @004c9bd0
//###########################################################################
//
// @004c9bd0 -- build the 28 ColorMapperArmor zone gauges. For each name in the
// damage-zone name table (PTR_s_dz_door_0051a240, 28 entries) build a transient
// damage-zone descriptor, resolve its index on the mech (FindDamageZone
// @0042076c), allocate (0x70) a ColorMapperArmor and construct it (@004c3b98)
// wired to that zone (incrementing the next base colour index each iteration).
// @004c9ca8 -- destroy: delete all 28, optionally free the array.
// @004c9cf4 -- Execute: run each element's Execute (vtbl+0xc).
// @004c9d18 -- SetColor: recolour all 28 (FUN_004c3c38 = ColorMapper SetColor).
//
//###########################################################################
// SectorDisplay @004c9d44 Make / @004c9e10 ctor
//###########################################################################
//
// @004c9d44 -- Make. Allocate (0xc4) and construct "SectorDisplay" from the
// resource strings (DAT_0051a2bc..0051a3cc). Then verify the grid image exists
// (BitMapCache); if missing, warn "SectorDisplay: Missing image <name>".
//
Logical
SectorDisplay::Make(
int /*display_port_index*/,
Vector2DOf<int> /*position*/,
Entity *entity,
GaugeRenderer *gauge_renderer
)
{
SectorDisplay *gauge = (SectorDisplay *)operator new(0xc4);
if (gauge != NULL)
{
//
// Construct with the recovered grid-image name; the remaining geometry /
// colour / font-image arguments were not recovered, so are typed
// placeholders.
//
new (gauge) SectorDisplay(
(GaugeRate)DAT_0051a2bc, // rate
(ModeMask)DAT_0051a300, // mode mask
(L4GaugeRenderer *)gauge_renderer,
0, // graphics port number
0, 0, // x, y
(char *)&DAT_0051a344, // grid image
(char *)0, // font image
0, // color
"SectorDisplay");
}
BitMap *grid = gauge_renderer->warehousePointer->bitMapBin.GetIfAlreadyExists(
(char *)&DAT_0051a344); // FUN_00442aec (peek, no AddRef)
if (grid == NULL)
{
DebugStream << "SectorDisplay: Missing image "
<< (char *)&DAT_0051a344 << "\n"; // FUN_004dbb24 x3
}
return (grid != NULL);
}
//
// @004c9e10 -- ctor (vtable PTR_FUN_0051beec). GraphicGauge base, interns the
// grid image; computes cellWidth = imageWidth/14, gridLeft = cellWidth*12,
// gridRight = cellWidth*12 + cellWidth - 1, gridHeight = imageHeight; sets the
// port origin; builds two NumericDisplays (this[0x2F] at origin, this[0x30] at
// +cellWidth*4). baseLine = dirty = 500.
// @004c9f94 -- dtor: release grid, delete both numerics, GraphicGauge::~GraphicGauge.
// @004ca038 -- BecameActive: dirty=1; reset both numerics.
// @004ca068 -- SetEnable: this[0x24] = enable.
// @004ca07c -- Execute: when enabled, DrawAt the two numerics at the rounded
// cursor position (+offsets), and on the first pass blit the grid image.
//
//###########################################################################
// TeamStatusDisplay @004ca208
//###########################################################################
//
// @004ca208 -- ctor (vtable PTR_FUN_0051bea8). GraphicGauge base. rowSpacing =
// param_10; originY = param_8 - 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 number
// NumericDisplay (this[0x3B+i]) and -- when flags&4 -- a kill NumericDisplay
// (this[0x4C+i]); 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
// (App+0x6c mech -> +0x190 -> +0x1cc index, +0x204 team via FUN_0044fc10).
// Read the "Players" node (FindByName @00403ad0), slot each player into [0..7]
// by entity+0x1cc (skipping spectators, +0x29 bit 0x40). For each row: if the
// occupant changed, blit the player's interned name bitmap (App+0xc8 palette
// cache lookup) or clear, tinting friendly/self with FUN_004c2f88 (=7). Then
// update each kill numeric from entity+0x1c8. When flags&4, also track each
// player's rank (entity+0x204 - rankBase via FUN_0044fc10). Finally redraw the
// selection underline on the local player's row.
//
//###########################################################################
// PilotList @004ca90c Make / @004ca958 ctor
//###########################################################################
//
// @004ca90c -- Make: allocate (0x178) and construct "PilotList" from
// DAT_0051ab4c..0051abd4.
// @004ca958 -- ctor (vtable PTR_FUN_0051be64). GraphicGauge base. For each of
// 8 entries, read its (x,y,layoutMode) from DAT_0051af88 (stride 3); compute the
// three NumericDisplay positions from layoutMode (0/1/2) and build them
// (this entry +0x10/+0x14/+0x18). resolvedMech = 0; selected = 0.
// @004cab10 -- dtor: delete the 3 numerics per entry (8 entries), base dtor.
// @004caba4 -- BecameActive: per-entry resolvedMech (+0x08) = -1; currentSlot = 9.
// @004cabd0 -- Execute (one slot per frame). Look up the lance roster
// (App+0x6c mech +0x128 -> FUN_004b0898 slot lookup). For the current slot:
// if empty, erase the three numerics; else compute the player-name bitmap and
// blit it (DrawMechIcon @004cad70 tints by team via FUN_004c2f88), then DrawAt
// the mech (+0x27c) and score (+0x280) numerics. Advance currentSlot (wraps 8).
// helpers DrawMechIcon @004cad70, DrawAmmoCount @004cae20.
//
//###########################################################################
// PlayerStatus @004cae90 Make / @004cb1a8 ctor
//###########################################################################
//
// @004cae90 -- Make. DAT_0051b07c is the player number; must be 1..8 or
// "PlayerStatus: Make player number <n>" is warned. Allocates (200) and
// constructs "PlayerStatus" from DAT_0051aff4..0051b1d0.
//
MethodDescription
PlayerStatus::methodDescription =
{
"PlayerStatus",
PlayerStatus::Make,
{
// CFG (L4GAUGE.CFG:29): PlayerStatus(D,ModeAlwaysActive,1,helv15.pcc,0,11,3,1)
{ 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. Rewired to read methodDescription.parameterList[] (the
// binary read raw DAT_ pools) + the port/x/y bug fixed (it passed entity/renderer
// as x/y and dropped the port). Returns True even on a NULL alloc (binary-faithful).
//
Logical
PlayerStatus::Make(
int display_port_index,
Vector2DOf<int> position,
Entity * /*entity -- unused by PlayerStatus*/,
GaugeRenderer *gauge_renderer
)
{
ParameterDescription *p = methodDescription.parameterList;
int playerNumber = p[2].data.integer;
if (playerNumber < 1 || playerNumber > 8)
{
DebugStream << "PlayerStatus: Make player number "
<< playerNumber << " out of range\n";
return False;
}
PlayerStatus *gauge = (PlayerStatus *)operator new(200);
if (gauge != NULL)
{
new (gauge) PlayerStatus(
p[0].data.rate, p[1].data.modeMask,
(L4GaugeRenderer *)gauge_renderer,
display_port_index, // graphics_port_number <-- FIX (was dropped)
position.x, position.y, // x, y <-- FIX (was (int)entity/(int)renderer)
playerNumber, p[3].data.string,
p[4].data.color, p[5].data.color, p[6].data.color, p[7].data.color,
"PlayerStatus");
}
return True;
}
//###########################################################################
// file-private helpers
//###########################################################################
// (ReadStreamString @004caf50 -- the length-prefixed string reader used by the
// faithful CreateMutantPixelmap8 -- deferred with the pixmap-recolor stub above;
// its stream read is a vtbl+0x1c indirect whose method name needs mapping.)
//
// @004cafac -- per-player pixmap recolour. Find the mech's type-0x20 pixmap
// resource (App resource file), open it as a MemoryStream, read the embedded
// image name, look the source pixmap up in the renderer's PixMap8 cache, and copy
// it while biasing every palette index >= 0x20 by (paletteBase-0x20) so each
// player's mech outline draws in its own colour ramp. Returns the (reused) Pixmap.
//
static Pixmap *
CreateMutantPixelmap8(Entity * /*mech*/, L4Warehouse * /*warehouse*/,
int /*paletteBase*/, Pixmap *dest)
{
// ⚠ BRING-UP STUB (marked; leaves the mech-outline schematic unrendered).
// The faithful reconstruction (decomp @004cafac) resolves the mech's type-0x20
// pixmap sub-resource from the App ResourceFile, opens it as a memory stream,
// reads the embedded image name (the stream read is a vtbl+0x1c indirect --
// ReadStreamString @004caf50), looks the source PixMap8 up in the renderer
// warehouse, and copies it biasing every palette index >= 0x20 by
// (paletteBase-0x20) so each player's mech draws in its own colour ramp.
// Deferred: the DynamicMemoryStream read API + Pixmap pixel-copy need mapping.
// With this stub recoloredMech stays NULL -> Execute skips the outline blit;
// the score, name box, and alive/dead status box still render.
return dest;
}
//###########################################################################
// 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"
};
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*/)
{
for (int i = 0; i < playerStatusZoneCount; ++i)
{
int zoneIndex = (mech != NULL)
? mech->GetDamageZoneIndex(CString(kPlayerStatusZone[i])) // FUN_0042076c (-1 => inert)
: -1;
zone[i] = (ColorMapperArmor *)operator new(0x70);
if (zone[i] != NULL)
new (zone[i]) ColorMapperArmor( // EXISTING 10-arg ctor (btl4gaug)
rate, mode_mask, renderer, graphics_port_number,
base_color_index + i, mech, palette_a, palette_b,
zoneIndex, "ColorMapperArmor");
}
}
PlayerStatusMappingGroup::~PlayerStatusMappingGroup()
{
for (int i = 0; i < playerStatusZoneCount; ++i)
{
delete zone[i];
zone[i] = NULL;
}
}
void
PlayerStatusMappingGroup::Execute() // @004c9cf4 run all 28
{
for (int i = 0; i < playerStatusZoneCount; ++i)
if (zone[i] != NULL) zone[i]->Execute();
}
void
PlayerStatusMappingGroup::SetColor(int state) // @004c9d18 push state onto all 28
{
for (int i = 0; i < playerStatusZoneCount; ++i)
if (zone[i] != NULL) zone[i]->SetColor(state);
}
//###########################################################################
// PlayerStatus @004cb1a8 ctor / @004cb28c dtor / @004cb310 BecameActive /
// @004cb358 Execute
//###########################################################################
//
// @004cb1a8 -- ctor (vtable PTR_FUN_0051be20). GraphicGauge base (owner folded
// to 0); stash the four state colours + port; set the port origin; build the
// score NumericDisplay.
//
PlayerStatus::PlayerStatus(
GaugeRate rate, ModeMask mode_mask, L4GaugeRenderer *renderer_in,
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_in, 0, // FUN_00444818 (owner 0)
graphics_port_number, identification_string)
{
color = color_in; okColor = ok_color; aliveColor = alive_color; deadColor = dead_color;
port = graphics_port_number;
localView.SetOrigin(x, y); // this+0x48 vtbl+0x10
playerIndex = player_number - 1;
player = NULL; nameImage = NULL; recoloredMech = NULL; mappingGroup = NULL;
L4Warehouse *warehouse = (L4Warehouse *)renderer_in->warehousePointer;
scoreDisplay = new NumericDisplay( // FUN_004700bc @0xB8
warehouse, 0x72, 0xD8, font_image, 5,
(NumericDisplay::NumericFormat)2 /*signedBlankedZeros*/, color, okColor);
// dirty/previousStatus/previousMappingState set by BecameActive (byte-faithful).
}
//
// @004cb28c -- dtor. Free the score numeric + the recoloured mech pixmap; the
// base chain runs implicitly. (nameImage is cache-owned; mappingGroup deleted
// here as a correctness fix over the binary's leak.)
//
PlayerStatus::~PlayerStatus()
{
player = NULL;
delete scoreDisplay; scoreDisplay = NULL; // FUN_0047018c
if (recoloredMech) { delete recoloredMech; recoloredMech = NULL; } // FUN_00449d0c
if (mappingGroup) { delete mappingGroup; mappingGroup = NULL; } // (binary leaks this)
}
Logical
PlayerStatus::TestInstance() const // @004cb2f8
{
return GraphicGauge::TestInstance();
}
//
// @004cb310 -- BecameActive: reset the score readout, run the mapping group once,
// and force a full redraw next Execute.
//
void
PlayerStatus::BecameActive()
{
scoreDisplay->ForceUpdate(); // FUN_004703f4
if (mappingGroup != NULL) mappingGroup->Execute(); // FUN_004c9cf4
dirty = 1;
previousStatus = -1;
previousMappingState = -1;
}
//
// @004cb358 -- Execute. Resolve player N (the mission player for the local
// player; WinTesla-clean via GetMissionPlayer instead of the binary's raw
// App+0x24 "Players"-node dictionary walk); 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. Runs under the gauge's guarded
// Execute (BT_DEV_GAUGES), so a bad player-field offset degrades to a disabled
// gauge, never a game crash.
//
void
PlayerStatus::Execute()
{
// --- PART 1: resolve / change-detect the player ---
Entity *resolved = NULL;
if (playerIndex == 0 && application != NULL)
resolved = (Entity *)application->GetMissionPlayer(); // the local player (player 1)
if (getenv("BT_PS_LOG"))
{
static int s_c = 0;
if ((s_c++ % 60) == 0)
DEBUG_STREAM << "[ps] idx=" << playerIndex << " resolved=" << (void*)resolved
<< " player=" << (void*)player << " port=" << port << "\n" << std::flush;
}
if (resolved != player)
{
player = resolved;
if (player != NULL)
{
Entity *mech = *(Entity **)((char *)player + 0x1FC); // player's vehicle
if (mech == NULL)
{
player = NULL; // no vehicle yet -> retry next frame
}
else
{
L4Warehouse *wh = (L4Warehouse *)renderer->warehousePointer;
int base = playerIndex * 0x1C + 0x10;
recoloredMech = CreateMutantPixelmap8(mech, wh, base, recoloredMech);
if (mappingGroup) delete mappingGroup;
mappingGroup = new PlayerStatusMappingGroup(
rate, modeMask, (L4GaugeRenderer *)renderer, port, mech, base,
"adpal.pcc", "adpal2.pcc", "PlayerStatusMappingGroup");
nameImage = NULL; // BRING-UP: App+0xC8 name cache not wired -> clear-box branch
BecameActive(); // force a full redraw
}
}
}
// --- PART 2: per-frame draw (only with a bound player) ---
if (player == NULL)
return;
Entity *mech = *(Entity **)((char *)player + 0x1FC);
if (mech == NULL)
return;
// (a) score readout
scoreDisplay->Draw(&localView, *(Scalar *)((char *)player + 0x1C8)); // FUN_00470430
// (b) mapping-group disabled-state refresh
int disabled = 0; // BRING-UP: Mech::IsDisabled needs a Mech* cast (mech.hpp collides in this TU)
if (disabled != previousMappingState)
{
previousMappingState = disabled;
if (mappingGroup) mappingGroup->SetColor(disabled); // FUN_004c9d18
}
// (c) mech outline + name box, dirty-gated
if (dirty)
{
dirty = 0;
if (recoloredMech != NULL)
{
localView.MoveToAbsolute(1, 1); // vtbl+0x24
localView.DrawPixelMap8(True, 0, recoloredMech, 0, 0, // vtbl+0x5C
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); // vtbl+0x18
localView.DrawFilledRectangleToRelative(0x80, 0x20); // vtbl+0x4C
}
else
{
localView.SetColor(0xFF);
localView.DrawBitMapOpaque(color, 0, nameImage, 0, 0, // vtbl+0x58
nameImage->Data.Size.x - 1, nameImage->Data.Size.y - 1);
}
}
// (d) alive/dead status box, state-change gated
int state = *(int *)((char *)player + 0x1C4);
if (state != previousStatus)
{
previousStatus = state;
localView.SetColor(state == 0 ? aliveColor : deadColor);
localView.MoveToAbsolute(0, 0);
localView.DrawFilledRectangleToAbsolute(0x9F, 0xEF); // vtbl+0x44
}
}
//
// @004cb1a8 -- ctor (vtable PTR_FUN_0051be20). GraphicGauge base; stashes the
// four state colours (this[0x29..0x2C]); playerIndex = player_number - 1; sets
// the port origin; builds the score NumericDisplay (this[0x2E], helv font,
// color/okColor).
// @004cb28c -- dtor: delete the score numeric; free the mutant pixmap
// (FUN_00449d0c); base dtor.
// @004cb310 -- BecameActive: reset score numeric; run mappingGroup BecameActive
// (FUN_004c9cf4); dirty flags = 1 / -1.
// @004cb358 -- Execute. Find player N in the "Players" node (@00403ad0,
// "Players" 0x51bd97, indexed lookup vtbl+0x34). When the player changes and
// has a mech (+0x1fc): build a recoloured name pixmap (CreateMutantPixelmap8
// @004cafac) and a fresh PlayerStatusMappingGroup (@004c9bd0, adpal.pcc/
// adpal2.pcc) tinting the mech outline; cache the interned name bitmap. Each
// frame: DrawAt the score (+0x1c8); update the armour mapping group when the
// damage-mapping derivation (FUN_0049fb54 / 0x50bdb4) changes; on dirty, blit
// the name bitmap then the recoloured mech outline; finally pick the frame
// colour (aliveColor/deadColor by subsys+0x1c4) and draw the status box.
//
//###########################################################################
// CreateMutantPixelmap8 @004cafac
//###########################################################################
//
// @004cafac -- per-player pixmap recolour. Find the source pixmap resource
// (Warehouse @00407064 by id mech+0x1bc); on failure warn "CreateMutantPixelmap8:
// couldn't find ...". Open it as a MemoryStream, read the embedded image name
// (ReadStreamString @004caf50, 0x50 chars), look it up in the PixMapCache. Copy
// the source 8-bit pixels into a (possibly reused) Pixmap, biasing every palette
// index >= 0x20 by (palette_base - 0x20) so each player's mech draws in their
// own colour ramp. Returns the recoloured Pixmap.
//
//###########################################################################
// MessageBoard @004cb678 Make / @004cb704 ctor
//###########################################################################
//
// @004cb678 -- Make: allocate (0xa4) and construct "MessageBoard" from
// DAT_0051b43c..0051b508; verify the strip image exists (BitMapCache).
// @004cb704 -- ctor (vtable PTR_FUN_0051bddc). GraphicGauge base; interns +
// AddRefs the message strip bitmap (this[0x25]); sets the port origin; color =
// param_10 (this[0x26]).
// @004cb788 -- dtor: release the strip, free the name, base dtor.
// @004cb7fc -- BecameActive: previousNameId = -1; previousMessageId = -2.
// @004cb82c -- Execute. Track the local player's mech (this[0x24]). Read its
// controller message ids (mech+0x190 controller, +0xc message id, +0x10 name
// id). When the message id changes, blit the corresponding 0x80x0x20 cell of
// the strip ((id&3)<<7, (id>>2)<<5) tinted by FUN_004c2f88 (=7); -1 clears.
// When the name id changes, blit the player's name bitmap (App+0xc8 palette
// cache lookup) similarly.
//
// === btl4grnd.cpp begins at @004cbea0 (BTL4GaugeRenderer ctor) -- not this TU.