The name art was never missing. The egg carries it as notation pages of hex rows ([BitMap::Small::Aeolus]); the engine BitMap has a ctor that reads exactly that format; and the AUTHENTIC Mission ctor already loads them into smallNameBitmapChain (MISSION.CPP:525 -- implemented, called, working). The break was ours: btl4gau3's LookupPlayerNameBitmap was a bring-up stub returning NULL. It now uses the authentic API (GetCurrentMission()-> GetSmallNameBitmap(playerBitmapIndex)) and resolves live -- traced index=1 to a real BitMap, with the fallback-box branch provably never firing. Coverage 91% -> 92%. DIAGNOSIS CORRECTED for the stray magenta blocks over the top-left title: they are NOT a missing-name placeholder. They are the REAL name bitmap drawn at the wrong slot coordinates -- PilotList lays its 8 roster slots out from the PE-recovered DAT_0051af88 (x,y,mode) table, and ours puts them top-left instead of the shipped exe's centre row. Recovering that table is the next cockpit thread; the egg-hex -> live-BitMap pipeline is verified end to end, so what remains there is layout, not art or lookup. (A duplicate loader written before finding the authentic one was removed -- CODE/RP/MUNGA/MISSION.CPP already does this job.) Gauge fight 11/11, smoke and novice clean, zero faults. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1583 lines
48 KiB
C++
1583 lines
48 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, @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 <btl4.hpp>
|
|
#pragma hdrstop
|
|
|
|
#if !defined(MISSION_HPP)
|
|
# include <mission.hpp>
|
|
#endif
|
|
|
|
#if !defined(BTL4GAU3_HPP)
|
|
# include <btl4gau3.hpp>
|
|
#endif
|
|
|
|
#if !defined(APP_HPP)
|
|
# include <app.hpp>
|
|
#endif
|
|
|
|
#if !defined(BTPLAYER_HPP)
|
|
# include <btplayer.hpp>
|
|
#endif
|
|
|
|
#if !defined(NTTMGR_HPP)
|
|
# include <nttmgr.hpp>
|
|
#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<Node*>
|
|
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;
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// 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)
|
|
{
|
|
//
|
|
// 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;
|
|
}
|
|
return 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 <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
|
|
*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<int> /*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);
|
|
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)
|
|
{
|
|
pilot = ResolveRosterPlayer(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
|
|
{
|
|
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 <n>" 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<int> 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<int> 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.
|