The "sectorDisplay" cockpit primitive (Secondary overlay, the radar SECTOR X/Z coordinate read-out) was PROSE-ONLY in btl4gau3 (placeholder Make, no ctor/ methodDescription/registration) -> the config line was parse-SKIPPED and never built. Reconstructed byte-verified from the disassembly (ctor @4c9e10, Execute @4ca07c, methodDescription PE-parse): SectorDisplay : GraphicGauge, sizeof 0xC4. Its Execute reads the linked mech's world position and shows two 100-unit sector numerics: numericA = Round(-localOrigin.z * 0.01) + 500 numericB = Round( localOrigin.x * 0.01) + 500 (rounding = round-to-nearest == FUN_004dcd94, corrected from the reviewer's wrong "truncate" claim; 0.01 const PE-verified; -Z/+X axis + fchs confirmed from asm). Overridden slots: LinkToEntity(9) caches the subject, BecameActive(3, non-inactivating), Execute(16) -> satisfies the container-Execute rule. Layout overflow-locked (static_assert sizeof<=0xC4). Make/ctor/dtor mirror the registered PilotList sibling; the config image name is copied (nameCopy) since Execute reads it per-frame. Registered in BTL4MethodDescription[]. VERIFIED LIVE (BT_SECTOR_LOG): Make port=1 pos=(125,579) image=helv15.pcc gridCached=1; Execute -Z=960.4 X=361.6 -> sectorA=510 sectorB=504 (Round(9.6)+500=510, Round(3.6)+500=504 -- authentic 100-unit sectors from live mech position). Gauge composite renders full, no crash. The skip list is now exactly the two remaining widgets (prepEngr x12, messageBoard). Also: a permanent BT_GAUGE_SKIP_LOG diagnostic (GAUGREND.cpp, gated) that logs each unregistered gauge primitive the dev-parse skips -- the tool that pinned this down (earlier "not built" runs were killed before the lazy gauge-renderer init). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
901 lines
36 KiB
C++
901 lines
36 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
|
|
|
|
// (SectorDisplay's + PlayerStatus's DAT_ placeholder pools removed -- their rewired
|
|
// Make()s read 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
|
|
//###########################################################################
|
|
|
|
// @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).
|
|
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. Alloc 0xc4 + construct; then peek the grid image and warn
|
|
// "SectorDisplay: Missing image <name>" if absent (binary-faithful: the base ctor
|
|
// self-registers the gauge regardless of the return value).
|
|
//
|
|
Logical
|
|
SectorDisplay::Make(
|
|
int display_port_index,
|
|
Vector2DOf<int> position,
|
|
Entity * /*entity -- unused*/,
|
|
GaugeRenderer *gauge_renderer
|
|
)
|
|
{
|
|
ParameterDescription *p = methodDescription.parameterList;
|
|
|
|
SectorDisplay *gauge = (SectorDisplay *)operator new(0xc4); // FUN_00402298(0xc4)
|
|
if (gauge != NULL)
|
|
new (gauge) SectorDisplay(
|
|
p[0].data.rate, p[1].data.modeMask,
|
|
(L4GaugeRenderer *)gauge_renderer,
|
|
display_port_index, // graphics_port_number (FIX: was 0)
|
|
position.x, position.y, // SetOrigin (FIX: was 0,0)
|
|
p[2].data.string, // grid/font image (helv15.pcc)
|
|
p[3].data.color, // numericColor
|
|
p[4].data.color, // gridColor / numeric okColor
|
|
"SectorDisplay");
|
|
|
|
BitMap *grid = gauge_renderer->warehousePointer->bitMapBin.GetIfAlreadyExists(
|
|
p[2].data.string); // FUN_00442aec (peek, no AddRef)
|
|
if (grid == NULL)
|
|
DebugStream << "SectorDisplay: Missing image " << p[2].data.string << "\n";
|
|
if (getenv("BT_SECTOR_LOG"))
|
|
DEBUG_STREAM << "[sector] Make port=" << display_port_index << " pos=("
|
|
<< position.x << "," << position.y << ") image=" << p[2].data.string
|
|
<< " gridCached=" << (int)(grid != NULL) << "\n" << std::flush;
|
|
return (grid != NULL);
|
|
}
|
|
|
|
//
|
|
// @004c9e10 -- ctor (vtable PTR_FUN_0051beec). GraphicGauge base (owner 0); copy
|
|
// the grid-image name; from the cached 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_in,
|
|
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_in, 0, // FUN_00444818 (owner 0)
|
|
graphics_port_number, identification_string)
|
|
{
|
|
// nameCopy(image) -- Execute reads gridImage per-frame, so it must outlive the
|
|
// (transient) config parameterList string; store a durable copy (FUN_004700ac).
|
|
gridImage = NULL;
|
|
if (image != NULL) {
|
|
gridImage = new char[strlen(image) + 1];
|
|
strcpy(gridImage, image);
|
|
}
|
|
numericColor = color; // @0xA8
|
|
gridColor = ok_color; // @0xAC
|
|
subject = NULL; // @0x90
|
|
sectorBaseB = 500; // @0xB4 (0x1f4)
|
|
sectorBaseA = 500; // @0xB0
|
|
dirty = 1; // @0xB8 (binary leaves uninit; BecameActive sets it)
|
|
|
|
L4Warehouse *wh = (L4Warehouse *)renderer_in->warehousePointer; // renderer+0x4c
|
|
BitMap *img = wh->bitMapBin.GetIfAlreadyExists(gridImage); // FUN_00442aec (peek)
|
|
if (img == NULL) {
|
|
cellWidth = gridLeft = gridRight = gridHeight = 0;
|
|
} else {
|
|
cellWidth = img->Data.Size.x / 14; // img+0xc width
|
|
gridLeft = cellWidth * 12;
|
|
gridRight = cellWidth * 12 + cellWidth - 1;
|
|
gridHeight = img->Data.Size.y; // img+0x10 height
|
|
}
|
|
localView.SetOrigin(x, y); // this+0x48 vtbl+0x10
|
|
|
|
// NumericDisplay(warehouse, x, y, image, fieldWidth=3, format=unsignedFormat(0), color, okColor)
|
|
numericA = new NumericDisplay(wh, 0, 0, image, 3,
|
|
NumericDisplay::unsignedFormat, color, ok_color);
|
|
numericB = new NumericDisplay(wh, cellWidth * 4, 0, image, 3,
|
|
NumericDisplay::unsignedFormat, color, ok_color);
|
|
}
|
|
|
|
//
|
|
// @004c9f94 -- dtor. Free the copied name + both numerics; the base
|
|
// GraphicGauge::~GraphicGauge runs implicitly (do NOT call it).
|
|
//
|
|
SectorDisplay::~SectorDisplay()
|
|
{
|
|
delete[] gridImage; gridImage = NULL;
|
|
delete numericA; numericA = NULL; // FUN_0047018c
|
|
delete numericB; numericB = NULL;
|
|
}
|
|
|
|
// @004ca020 -- TestInstance: out-of-line forward to the base (so it isn't /FORCE-stubbed).
|
|
Logical
|
|
SectorDisplay::TestInstance() const { return GraphicGauge::TestInstance(); }
|
|
|
|
// @004ca068 -- LinkToEntity (slot 9): cache the renderer's linked entity as the subject.
|
|
void
|
|
SectorDisplay::LinkToEntity(Entity *entity) { subject = entity; }
|
|
|
|
// @004ca038 -- BecameActive (slot 3): mark dirty + reset both numerics. NON-inactivating
|
|
// (does NOT chain the base) -> satisfies the container-Execute rule.
|
|
void
|
|
SectorDisplay::BecameActive()
|
|
{
|
|
dirty = 1;
|
|
numericB->ForceUpdate(); // FUN_004703f4 (B then A, binary order)
|
|
numericA->ForceUpdate();
|
|
}
|
|
|
|
// @004ca07c -- Execute (slot 16): draw the two sector numerics from the linked mech's
|
|
// world X/Z (Round via the codebase +0.5 idiom == FUN_004dcd94), then on the first
|
|
// active frame blit the grid-cell background.
|
|
void
|
|
SectorDisplay::Execute()
|
|
{
|
|
Entity *s = subject; // this+0x90
|
|
// PORT accommodation (marked): the LinkToEntity broadcast may be unwired on
|
|
// WinTesla -> fall back to the viewpoint mech (same as btl4rdr ResolveOperatorEntity).
|
|
// The binary gates purely on `subject != 0` (@0x4ca08e).
|
|
if (s == NULL && application != NULL)
|
|
s = (Entity *)application->GetViewpointEntity();
|
|
if (s == NULL)
|
|
return;
|
|
|
|
const Point3D &pos = s->localOrigin.linearPosition; // s+0x100 (x@+0x100, z@+0x108)
|
|
Scalar za = -pos.z * 0.01f; // @0x4ca194 = 0.01 (100-unit sectors)
|
|
Scalar xb = pos.x * 0.01f;
|
|
int vA = (int)(za + (za < 0.0f ? -0.5f : 0.5f)) + sectorBaseA; // Round(-Z*0.01)+500
|
|
int vB = (int)(xb + (xb < 0.0f ? -0.5f : 0.5f)) + sectorBaseB; // Round( X*0.01)+500
|
|
numericA->Draw(&localView, (Scalar)vA); // FUN_00470430
|
|
numericB->Draw(&localView, (Scalar)vB);
|
|
|
|
if (getenv("BT_SECTOR_LOG")) {
|
|
static int s_n = 0;
|
|
if ((s_n++ % 120) == 0)
|
|
DEBUG_STREAM << "[sector] Execute -Z=" << (-pos.z) << " X=" << pos.x
|
|
<< " -> sectorA=" << vA << " sectorB=" << vB << "\n" << std::flush;
|
|
}
|
|
|
|
if (dirty) { // this+0xB8
|
|
dirty = 0;
|
|
BitMap *grid = ((L4Warehouse *)renderer->warehousePointer)
|
|
->bitMapBin.GetIfAlreadyExists(gridImage); // FUN_00442aec
|
|
if (grid != NULL) {
|
|
localView.MoveToAbsolute(cellWidth * 3, 0); // vtbl+0x24
|
|
localView.SetColor(gridColor); // vtbl+0x18 (this+0xAC)
|
|
localView.DrawBitMap(0, grid, gridLeft, 0, gridRight, gridHeight); // vtbl+0x54
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//###########################################################################
|
|
// 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 (config keyword "pilotList" -- the Comm KILLS/DEATHS roster).
|
|
// Reconstructed from part_014.c:3156-3434; vtable PTR_FUN_0051be64, sizeof 0x178.
|
|
// Draws ONE roster slot per frame (round-robin over 8) from the viewpoint mech's
|
|
// cockpit-mapper pilot roster. Was PROSE-ONLY -> the "pilotList" line was
|
|
// parse-skipped and the Comm surface showed only the baked btcomm.pcx labels.
|
|
//###########################################################################
|
|
|
|
// The roster lives on the viewpoint mech's ControlsMapper (subsystemArray[0]);
|
|
// the bridge (mechmppr.cpp, a complete-Mech TU) resolves it -- this TU only sees
|
|
// the pilot as an opaque pointer read at BTPlayer raw offsets.
|
|
extern void *BTResolveRosterPilot(int slot);
|
|
// gauge scoring wave: read a roster pilot's scoreboard via its compiled BTPlayer
|
|
// members (btplayer.cpp), not raw offsets (which don't match our layout).
|
|
extern int BTPilotKills(void *pilot);
|
|
extern int BTPilotDeaths(void *pilot);
|
|
extern int BTPilotIsSelected(void *pilot, void *local_player); // SELECT-TARGET highlight (safe)
|
|
|
|
// App+0xC8 player-name-bitmap cache is not wired in the port (same deferral
|
|
// PlayerStatus uses for its nameImage) -> NULL routes DrawMechIcon to the binary's
|
|
// own cache-miss branch (the tinted name box), never an AV.
|
|
static BitMap *LookupPlayerNameBitmap(int /*name_id*/) { return NULL; }
|
|
|
|
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. Allocate 0x178 + placement-construct. position UNUSED
|
|
// (each entry carries absolute coords from DAT_0051af88). Always returns True.
|
|
//
|
|
Logical
|
|
PilotList::Make(
|
|
int display_port_index,
|
|
Vector2DOf<int> /*position -- unused*/,
|
|
Entity * /*entity -- unused*/,
|
|
GaugeRenderer *gauge_renderer
|
|
)
|
|
{
|
|
ParameterDescription *p = methodDescription.parameterList;
|
|
|
|
PilotList *g = (PilotList *)operator new(0x178); // FUN_00402298(0x178)
|
|
if (g != NULL)
|
|
{
|
|
new (g) PilotList(
|
|
p[0].data.rate, p[1].data.modeMask,
|
|
(L4GaugeRenderer *)gauge_renderer,
|
|
display_port_index, // graphics_port_number
|
|
p[2].data.string, // font (bigfont.pcc)
|
|
"PilotList");
|
|
}
|
|
return True;
|
|
}
|
|
|
|
//
|
|
// @004ca958 -- ctor. GraphicGauge base; build 3 NumericDisplays/entry from the
|
|
// PE-parsed 8x(x,y,layoutMode) layout table (DAT_0051af88).
|
|
//
|
|
PilotList::PilotList(
|
|
GaugeRate rate,
|
|
ModeMask mode_mask,
|
|
L4GaugeRenderer *renderer_in,
|
|
int graphics_port_number,
|
|
const char *font_image,
|
|
const char *id_string
|
|
):
|
|
GraphicGauge(rate, mode_mask, renderer_in, 0, graphics_port_number, id_string)
|
|
{
|
|
currentSlot = 0;
|
|
built = False;
|
|
L4Warehouse *wh = (L4Warehouse *)renderer_in->warehousePointer; // renderer+0x4c
|
|
|
|
// DAT_0051af88 -- 8 x (x, y, layoutMode); PE-parsed exact from BTL4OPT.EXE.
|
|
static const int L[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}
|
|
};
|
|
for (int i = 0; i < 8; ++i)
|
|
{
|
|
int x = L[i][0], y = L[i][1], m = L[i][2];
|
|
int nx, ny, mx, my;
|
|
if (m == 1) { nx = x-8; ny = y-0x50; mx = x+0x48; my = y-0x50; }
|
|
else if (m == 2) { nx = x-8; ny = y+0x44; mx = x+0x48; my = y+0x44; }
|
|
else { nx = x+0x95; ny = y-0x0d; mx = x+0xe8; my = y-0x0d; } // m==0
|
|
|
|
entry[i].x = x; entry[i].y = y;
|
|
entry[i].resolvedMech = 0; entry[i].selected = 0;
|
|
entry[i].nameDisplay = new NumericDisplay(wh, nx, ny, font_image, 2,
|
|
NumericDisplay::signedBlankedZerosFormat, 0, 0xff); // KILLS
|
|
entry[i].mechDisplay = new NumericDisplay(wh, mx, my, font_image, 2,
|
|
NumericDisplay::signedBlankedZerosFormat, 0, 0xff); // DEATHS
|
|
entry[i].scoreDisplay = new NumericDisplay(wh, x, y, font_image, 1,
|
|
NumericDisplay::signedBlankedZerosFormat, 0, 0xff); // erase-only
|
|
}
|
|
}
|
|
|
|
//
|
|
// @004cab10 -- dtor. Free the 3 numerics per entry; the base chain runs implicitly.
|
|
//
|
|
PilotList::~PilotList()
|
|
{
|
|
for (int i = 0; i < 8; ++i)
|
|
{
|
|
delete entry[i].nameDisplay; entry[i].nameDisplay = NULL;
|
|
delete entry[i].mechDisplay; entry[i].mechDisplay = NULL;
|
|
delete entry[i].scoreDisplay; entry[i].scoreDisplay = NULL;
|
|
}
|
|
}
|
|
|
|
//
|
|
// @004caba4 -- BecameActive. Invalidate every entry so the first cycle repaints.
|
|
//
|
|
void
|
|
PilotList::BecameActive()
|
|
{
|
|
for (int i = 0; i < 8; ++i)
|
|
entry[i].resolvedMech = -1;
|
|
currentSlot = 9; // forces the wrap-reset on the first Execute
|
|
}
|
|
|
|
//
|
|
// @004cab8c -- TestInstance (out-of-line forward to the base; body required so
|
|
// the declared method is not /FORCE-stubbed).
|
|
//
|
|
Logical
|
|
PilotList::TestInstance() const
|
|
{
|
|
return GraphicGauge::TestInstance();
|
|
}
|
|
|
|
//
|
|
// @004cad70 -- DrawMechIcon. Blit the pilot's name bitmap (or, on a cache miss --
|
|
// always, in the port -- a tinted 0x80 x 0x20 box), tinted by the selected flag.
|
|
//
|
|
void
|
|
PilotList::DrawMechIcon(void *pilot, int selected)
|
|
{
|
|
int nameId = *(int *)((char *)pilot + 0x1e0); // pilot name/id key
|
|
BitMap *bmp = LookupPlayerNameBitmap(nameId);
|
|
int boxColor = selected ? 0xff : 0;
|
|
int blitColor = selected ? 0 : 0xff;
|
|
if (bmp == NULL)
|
|
{
|
|
localView.SetColor(boxColor); // vtbl+0x18
|
|
localView.DrawFilledRectangleToRelative(0x80, 0x20); // vtbl+0x4c
|
|
}
|
|
else
|
|
{
|
|
localView.SetColor(blitColor);
|
|
localView.DrawBitMapOpaque(boxColor, 0, bmp, 0, 0, // vtbl+0x58
|
|
bmp->Data.Size.x - 1, bmp->Data.Size.y - 1);
|
|
}
|
|
}
|
|
|
|
//
|
|
// @004cabd0 -- Execute (one roster slot per frame). Look up the current slot from
|
|
// the viewpoint mech's ControlsMapper roster; erase an empty slot, or draw the
|
|
// pilot's name icon + KILLS + DEATHS for an occupied one, tinting the row when it
|
|
// is the local pilot's current target.
|
|
//
|
|
void
|
|
PilotList::Execute()
|
|
{
|
|
void *local = BTResolveRosterPilot(0); // slot-0 (local) pilot gates the body
|
|
{
|
|
static int s_pl = 0;
|
|
if (getenv("BT_SCORE_LOG") && (s_pl++ % 120) == 0)
|
|
DEBUG_STREAM << "[score] PilotList::Execute local=" << local << "\n" << std::flush;
|
|
}
|
|
if (local == NULL)
|
|
return;
|
|
|
|
if (currentSlot > 7) { currentSlot = 0; built = False; } // wrap the round-robin
|
|
Entry &e = entry[currentSlot];
|
|
|
|
void *pilot;
|
|
if (built == False)
|
|
{
|
|
pilot = BTResolveRosterPilot(currentSlot);
|
|
if (pilot == NULL) built = True; // past the first empty slot -> rest empty this cycle
|
|
}
|
|
else
|
|
{
|
|
pilot = NULL;
|
|
}
|
|
|
|
if (pilot == NULL)
|
|
{
|
|
if (e.resolvedMech != 0) // (-1 after BecameActive) -> erase the box + numerics
|
|
{
|
|
localView.MoveToAbsolute(e.x, e.y); // vtbl+0x24
|
|
localView.SetColor(0); // vtbl+0x18
|
|
localView.DrawFilledRectangleToRelative(0x80, 0x20); // vtbl+0x4c
|
|
e.nameDisplay->Erase(&localView);
|
|
e.mechDisplay->Erase(&localView);
|
|
e.scoreDisplay->Erase(&localView);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// SELECT-TARGET highlight -- via the safe bridge (accessors), NOT raw offsets:
|
|
// the old `*(local+0x284)` objectiveMech + deref `*(tgt+0x190)` read garbage in
|
|
// our compiled layout and AV'd inside the SEH-guarded Execute, silently aborting
|
|
// it before the KILLS/DEATHS draw below (= the empty scoreboard).
|
|
int selected = BTPilotIsSelected(pilot, local);
|
|
if ((int)(size_t)pilot != e.resolvedMech || e.selected != selected)
|
|
{
|
|
localView.MoveToAbsolute(e.x, e.y); // vtbl+0x24
|
|
DrawMechIcon(pilot, selected);
|
|
e.selected = selected;
|
|
// (byte-faithful: the binary does NOT latch e.resolvedMech here, so the
|
|
// icon redraws each occupied frame.)
|
|
}
|
|
// KILLS / DEATHS -- gauge scoring wave (Step 1, the databinding fix): read the
|
|
// pilot's COMPILED named members via the BTPlayer bridge, NOT raw binary offsets.
|
|
// The earlier pilot+0x27c / +0x200 reads assumed the 1995 layout; our compiled
|
|
// BTPlayer/Player layout differs (engine base != binary), so those offsets read a
|
|
// stale/garbage slot -> a silent 0 even after the handlers increment the members.
|
|
// The bridge reads the SAME members the ScoreMessageHandler/VehicleDead handlers
|
|
// write (killCount / Player::deathCount). (DEATHS uses the real deathCount, not
|
|
// the binary's dead pad_0x280.)
|
|
e.nameDisplay->Draw(&localView, (Scalar)BTPilotKills(pilot)); // KILLS (killCount)
|
|
e.mechDisplay->Draw(&localView, (Scalar)BTPilotDeaths(pilot)); // DEATHS (Player::deathCount)
|
|
}
|
|
|
|
++currentSlot;
|
|
}
|
|
|
|
|
|
//###########################################################################
|
|
// 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.
|