Two things, both read off the shipped framebuffer.
SLOT 0 IS THE OWN ROW. Its layout entry is the odd one out -- {180,225} at
layout mode 0, KILLS/DEATHS at +0x95/+0xe8 instead of the strip boxes'
+0x48 -- and the shipped Comm page fills it with the LOCAL pilot while the
seven surrounding boxes stay empty in a solo mission. So slot 0 takes the
mission player directly and only slots 1..7 come from the pod roster. That
also explains the artifact this session opened with: the ungated walk was
feeding the mission player into the STRIP slots, which is why gating the
roster removed wrong pixels but left the centre row missing.
THE OWN ROW TAKES THE LARGE NAME RASTER. Mission carries both; we asked for
the small one, so 'Aeolus' drew visibly undersized and low in the box. The
strip slots keep the small raster -- their boxes are half the height.
Comm missing 2099 -> 733 -> 0 extra 0 -> 152 -> 0
The Comm head is now EXACT: 15656 pixels against the shipped binary's 15656.
Four heads exact (Eng1/2/3, Comm), the three MFDs within ~150px, and the
whole cockpit at 98.9% identical / 99% coverage -- from 89.7% / 92% when the
session started.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1663 lines
50 KiB
C++
1663 lines
50 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(MECHMPPR_HPP)
|
|
# include <mechmppr.hpp>
|
|
#endif
|
|
|
|
#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(MECHMPPR_HPP)
|
|
# include <mechmppr.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;
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// COMM-PAGE roster gate -- the cockpit's pilot list is NOT the mission's
|
|
// player list. The binary fed it from the viewpoint mech's ControlsMapper
|
|
// pilot array (mapper attributes PilotArrayPage / PilotArray, ids 15/16),
|
|
// which the pod network authors; a mission with no pod roster leaves it
|
|
// zero and the shipped cockpit draws the page EMPTY.
|
|
//
|
|
// A/B EVIDENCE (5.3.32): with the ungated walk our slot 0 resolved the solo
|
|
// mission player and painted its icon in colour 0xff -- 404 magenta pixels
|
|
// in the Comm title band where the shipped binary paints the black erase
|
|
// rect. Gate the ROW SOURCE here, not PilotList::Execute: Execute's empty
|
|
// branch still has to run, because erasing IS what the shipped page draws.
|
|
//
|
|
// PlayerStatus (the observer/camera scoreboard) keeps the ungated walk -- it
|
|
// is a mission-wide standings page, not the pod roster.
|
|
//#############################################################################
|
|
//
|
|
static Player *
|
|
ResolveCommRosterPlayer(int slot)
|
|
{
|
|
if (application == NULL)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
Entity
|
|
*viewpoint = application->GetViewpointEntity();
|
|
if (viewpoint == NULL || viewpoint->GetSubsystemCount() <= 0)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
Subsystem
|
|
*controls = viewpoint->GetSubsystem(0);
|
|
if (controls == NULL
|
|
|| !controls->IsDerivedFrom(MechControlsMapper::ClassDerivations))
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
if (((MechControlsMapper *) controls)->GetPilotArray() == 0)
|
|
{
|
|
return NULL; // no pod roster authored -> the rows stay empty
|
|
}
|
|
|
|
return ResolveRosterPlayer(slot);
|
|
}
|
|
|
|
|
|
//
|
|
//#############################################################################
|
|
// KILLS access (the gauge scoring wave): the KILLS column must read the SAME
|
|
// compiled member the score handlers increment -- BTPlayer::killCount
|
|
// (BTPLAYER.CPP ScoreMessageHandler / VehicleDead path). The reconstructed
|
|
// BTPLAYER.HPP keeps killCount protected with no accessor yet, so this thin
|
|
// derived reader stands in for a GetKillCount() accessor: NAMED-member access
|
|
// only, no offsets, no data members, never constructed. Retire it the day
|
|
// BTPLAYER.HPP grows the accessor.
|
|
//#############################################################################
|
|
//
|
|
class BTPlayerKillReader:
|
|
public BTPlayer
|
|
{
|
|
public:
|
|
static int
|
|
KillsOf(Player *pilot)
|
|
{
|
|
return ((BTPlayerKillReader *) pilot)->killCount;
|
|
}
|
|
private:
|
|
BTPlayerKillReader(); // reader cast only -- never constructed
|
|
};
|
|
|
|
//
|
|
// SELECT-TARGET highlight -- INERT DEFAULT. The binary tinted the roster row
|
|
// of the local pilot's current target (local player -> objectiveMech -> its
|
|
// player). BTPlayer::objectiveMech is protected with no accessor in the
|
|
// reconstruction, so the row tint stays off (rows draw untinted; the KILLS /
|
|
// DEATHS numerics are unaffected).
|
|
//
|
|
static Logical
|
|
RosterPlayerIsSelected(Player * /*pilot*/, Player * /*local_player*/)
|
|
{
|
|
return False;
|
|
}
|
|
|
|
//
|
|
// Player-name-bitmap cache -- INERT DEFAULT (the donor's own bring-up stub,
|
|
// kept per the worksheet). The binary looked prebuilt name rasters up in the
|
|
// application's name-bitmap cache by the player's name id (@0x1E0 ==
|
|
// Player::playerBitmapIndex). Returning NULL routes every caller to the
|
|
// binary's own cache-miss branch (the tinted name box), never an AV.
|
|
//
|
|
static BitMap *
|
|
LookupPlayerNameBitmap(int player_bitmap_index, Logical large)
|
|
{
|
|
//
|
|
// The mission owns the name art: the egg's [smallbitmap] index page
|
|
// lists one notation page of hex rows per pilot, and the authentic
|
|
// Mission ctor loads them into smallNameBitmapChain (MISSION.CPP:525).
|
|
// A player's `bitmapindex` egg entry selects its 1-based slot. A miss
|
|
// still routes the caller to the binary's cache-miss branch (the
|
|
// tinted name box), never an AV.
|
|
//
|
|
if (player_bitmap_index <= 0 || application == NULL)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
Mission
|
|
*mission = application->GetCurrentMission();
|
|
if (mission == NULL)
|
|
{
|
|
return NULL;
|
|
}
|
|
//
|
|
// SMALL vs LARGE (A/B rig, 5.3.39): Mission carries both rasters.
|
|
// The Comm page's OWN row is the wide box and takes the large one
|
|
// -- the shipped page draws 'Aeolus' there in the tall face, and
|
|
// the small raster left it visibly undersized and low in the box.
|
|
// The seven strip slots keep the small raster: their boxes are
|
|
// half the height.
|
|
//
|
|
return large
|
|
? mission->GetLargeNameBitmap(player_bitmap_index)
|
|
: mission->GetSmallNameBitmap(player_bitmap_index);
|
|
}
|
|
|
|
|
|
//#############################################################################
|
|
//#############################################################################
|
|
// PlayerStatusMappingGroup @004c9bd0
|
|
//#############################################################################
|
|
//#############################################################################
|
|
|
|
//
|
|
// The 28 named damage zones (PTR_s_dz_door_0051a240); each gets one
|
|
// ColorMapperArmor tinting that zone of the mech-outline schematic.
|
|
//
|
|
static const char *const
|
|
kPlayerStatusZone[playerStatusZoneCount] =
|
|
{
|
|
"dz_door","dz_dtorso","dz_hip","dz_larm","dz_ldleg","dz_lfoot","dz_lgun",
|
|
"dz_ltorso","dz_luleg","dz_missle","dz_rarm","dz_rdleg","dz_rfoot","dz_rgun",
|
|
"dz_rtorso","dz_ruleg","dz_searchlight","dz_utorso","dz_reardtorso",
|
|
"dz_rearltorso","dz_rearrtorso","dz_rearutorso","dz_lmissle","dz_rmissle",
|
|
"unused1","unused2","unused3","unused4"
|
|
};
|
|
|
|
//
|
|
//#############################################################################
|
|
// @004c9bd0 -- build the 28 ColorMapperArmor zone gauges. For each name in
|
|
// the damage-zone table resolve its index on the mech (GetDamageZoneIndex,
|
|
// binary FUN_0042076c; -1 => inert) and construct a ColorMapperArmor wired to
|
|
// that zone, incrementing the base colour index each iteration.
|
|
//#############################################################################
|
|
//
|
|
PlayerStatusMappingGroup::PlayerStatusMappingGroup(
|
|
GaugeRate rate,
|
|
ModeMask mode_mask,
|
|
L4GaugeRenderer *renderer,
|
|
int graphics_port_number,
|
|
Entity *mech,
|
|
int base_color_index,
|
|
const char *palette_a,
|
|
const char *palette_b,
|
|
const char * /*identification_string*/
|
|
)
|
|
{
|
|
Check_Pointer(this);
|
|
|
|
int
|
|
i;
|
|
|
|
for (i = 0; i < playerStatusZoneCount; ++i)
|
|
{
|
|
//
|
|
// (BC4.52: CString temporaries are illegal in a ternary -- hoist.)
|
|
//
|
|
int
|
|
zone_index = -1;
|
|
if (mech != NULL)
|
|
{
|
|
zone_index = mech->GetDamageZoneIndex(kPlayerStatusZone[i]);
|
|
}
|
|
|
|
zone[i] = new ColorMapperArmor(
|
|
rate,
|
|
mode_mask,
|
|
renderer,
|
|
graphics_port_number,
|
|
base_color_index + i,
|
|
mech,
|
|
palette_a,
|
|
palette_b,
|
|
zone_index,
|
|
"ColorMapperArmor"
|
|
);
|
|
Register_Object(zone[i]);
|
|
}
|
|
Check_Fpu();
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// @004c9ca8 -- destroy: delete all 28.
|
|
//#############################################################################
|
|
//
|
|
PlayerStatusMappingGroup::~PlayerStatusMappingGroup()
|
|
{
|
|
Check(this);
|
|
|
|
int
|
|
i;
|
|
|
|
for (i = 0; i < playerStatusZoneCount; ++i)
|
|
{
|
|
if (zone[i] != NULL)
|
|
{
|
|
Unregister_Object(zone[i]);
|
|
delete zone[i];
|
|
zone[i] = NULL;
|
|
}
|
|
}
|
|
Check_Fpu();
|
|
}
|
|
|
|
Logical
|
|
PlayerStatusMappingGroup::TestInstance() const
|
|
{
|
|
return True;
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// @004c9cf4 -- Execute: run each element's Execute.
|
|
//#############################################################################
|
|
//
|
|
void
|
|
PlayerStatusMappingGroup::Execute()
|
|
{
|
|
Check(this);
|
|
|
|
int
|
|
i;
|
|
|
|
for (i = 0; i < playerStatusZoneCount; ++i)
|
|
{
|
|
if (zone[i] != NULL)
|
|
{
|
|
zone[i]->Execute();
|
|
}
|
|
}
|
|
Check_Fpu();
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// @004c9d18 -- SetColor: push the state onto all 28.
|
|
//#############################################################################
|
|
//
|
|
void
|
|
PlayerStatusMappingGroup::SetColor(int state)
|
|
{
|
|
Check(this);
|
|
|
|
int
|
|
i;
|
|
|
|
for (i = 0; i < playerStatusZoneCount; ++i)
|
|
{
|
|
if (zone[i] != NULL)
|
|
{
|
|
zone[i]->SetColor(state);
|
|
}
|
|
}
|
|
Check_Fpu();
|
|
}
|
|
|
|
|
|
//#############################################################################
|
|
//#############################################################################
|
|
// SectorDisplay @004c9d44 Make / @004c9e10 ctor
|
|
//#############################################################################
|
|
//#############################################################################
|
|
|
|
//
|
|
// @0x51a2b0 -- registered so the interpreter builds the CFG line
|
|
// sectorDisplay( K, ModeAlwaysActive, helv15.pcc, 0, 3 ) (L4GAUGE.CFG:5146;
|
|
// the port index + offset=(125,579) arrive via Make's args, not config
|
|
// tokens). Keyword + parameter list VERBATIM (parse-critical).
|
|
//
|
|
MethodDescription
|
|
SectorDisplay::methodDescription =
|
|
{
|
|
"sectorDisplay",
|
|
SectorDisplay::Make,
|
|
{
|
|
{ ParameterDescription::typeRate, NULL }, // p[0] rate [row0 type=1]
|
|
{ ParameterDescription::typeModeMask, NULL }, // p[1] modeMask [row1 type=2]
|
|
{ ParameterDescription::typeString, NULL }, // p[2] image [row2 type=9]
|
|
{ ParameterDescription::typeColor, NULL }, // p[3] color [row3 type=4]
|
|
{ ParameterDescription::typeColor, NULL }, // p[4] okColor [row4 type=4]
|
|
PARAMETER_DESCRIPTION_END
|
|
}
|
|
};
|
|
|
|
//
|
|
//#############################################################################
|
|
// @004c9d44 -- Make. Construct, then check the grid image and warn
|
|
// "SectorDisplay: Missing image <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,
|
|
(currentSlot == 0) ? True : False); // slot 0 == the own row
|
|
int
|
|
box_color = selected ? 0xff : 0,
|
|
blit_color = selected ? 0 : 0xff;
|
|
|
|
if (name_bitmap == NULL)
|
|
{
|
|
localView.SetColor(box_color);
|
|
localView.DrawFilledRectangleToRelative(0x80, 0x20);
|
|
}
|
|
else
|
|
{
|
|
localView.SetColor(blit_color);
|
|
localView.DrawBitMapOpaque(box_color, 0, name_bitmap, 0, 0,
|
|
name_bitmap->Data.Size.x - 1, name_bitmap->Data.Size.y - 1);
|
|
}
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// @004cabd0 -- Execute (one roster slot per frame). Look up the current slot
|
|
// in the shared player roster; erase an empty slot, or draw the pilot's name
|
|
// icon + KILLS + DEATHS for an occupied one.
|
|
//
|
|
// The KILLS/DEATHS numerics read the COMPILED named members the score
|
|
// handlers write (BTPlayer::killCount / Player::deathCount) -- the binary's
|
|
// raw pilot+0x27c / +0x200 offsets read a stale/garbage slot on our compiled
|
|
// layout (the silent-zero scoreboard bug on record).
|
|
//#############################################################################
|
|
//
|
|
void
|
|
PilotList::Execute()
|
|
{
|
|
Check(this);
|
|
|
|
//
|
|
// The local pilot gates the whole body (binary: roster slot 0 of the
|
|
// viewpoint mech's mapper roster; native = the mission player).
|
|
//
|
|
Player
|
|
*local_player = application->GetMissionPlayer();
|
|
if (local_player == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (currentSlot > 7) // wrap the round-robin
|
|
{
|
|
currentSlot = 0;
|
|
built = False;
|
|
}
|
|
|
|
Entry
|
|
&e = entry[currentSlot];
|
|
|
|
Player
|
|
*pilot;
|
|
if (built == False)
|
|
{
|
|
//
|
|
// SLOT 0 IS THE OWN ROW. Its layout entry is the odd one out
|
|
// ({180,225} at layout mode 0, KILLS/DEATHS at +0x95/+0xe8
|
|
// instead of the strip boxes' +0x48), and the shipped Comm page
|
|
// fills it with the LOCAL pilot -- name, kills, deaths -- while
|
|
// the seven surrounding boxes stay empty in a solo mission. So
|
|
// slot 0 takes the mission player directly; only slots 1..7 come
|
|
// from the pod roster.
|
|
//
|
|
pilot = (currentSlot == 0)
|
|
? local_player
|
|
: ResolveCommRosterPlayer(currentSlot);
|
|
if (pilot == NULL && currentSlot > 0)
|
|
{
|
|
built = True; // past the first empty roster slot -> rest empty
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pilot = NULL;
|
|
}
|
|
|
|
if (pilot == NULL)
|
|
{
|
|
if (e.resolvedMech != 0) // (-1 after BecameActive) -> erase
|
|
{
|
|
localView.MoveToAbsolute(e.x, e.y);
|
|
localView.SetColor(0);
|
|
localView.DrawFilledRectangleToRelative(0x80, 0x20);
|
|
e.nameDisplay->Erase(&localView);
|
|
e.mechDisplay->Erase(&localView);
|
|
e.scoreDisplay->Erase(&localView);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int
|
|
selected = RosterPlayerIsSelected(pilot, local_player);
|
|
|
|
if ((int) pilot != e.resolvedMech || e.selected != selected)
|
|
{
|
|
localView.MoveToAbsolute(e.x, e.y);
|
|
DrawMechIcon(pilot, selected);
|
|
e.selected = selected;
|
|
// (byte-faithful: the binary does NOT latch e.resolvedMech here,
|
|
// so the icon redraws each occupied frame.)
|
|
}
|
|
|
|
e.nameDisplay->Draw(&localView, // KILLS (killCount)
|
|
(Scalar) BTPlayerKillReader::KillsOf(pilot));
|
|
e.mechDisplay->Draw(&localView, // DEATHS (deathCount)
|
|
(Scalar) pilot->GetDeathCount());
|
|
}
|
|
|
|
++currentSlot;
|
|
Check_Fpu();
|
|
}
|
|
|
|
|
|
//#############################################################################
|
|
//#############################################################################
|
|
// PlayerStatus @004cae90 Make / @004cb1a8 ctor
|
|
//#############################################################################
|
|
//#############################################################################
|
|
|
|
//
|
|
// @004cafac -- per-player pixmap recolour. INERT DEFAULT (the donor's own
|
|
// bring-up stub, kept per the worksheet). The faithful body resolves the
|
|
// mech's type-0x20 pixmap sub-resource from the application resource file
|
|
// (warns "CreateMutantPixelmap8: couldn't find ..." on a miss), opens it as a
|
|
// memory stream, reads the embedded image name (ReadStreamString @004caf50,
|
|
// 0x50 chars -- deferred with this stub), looks the source PixMap8 up in the
|
|
// renderer warehouse, and copies it biasing every palette index >= 0x20 by
|
|
// (palette_base - 0x20) so each player's mech outline draws in its own colour
|
|
// ramp. Deferred: the memory-stream read API + pixel copy are unmapped.
|
|
// With this stub recoloredMech stays NULL -> Execute skips the outline blit;
|
|
// the score, name box and alive/dead status box still render.
|
|
//
|
|
static Pixmap *
|
|
CreateMutantPixelmap8(
|
|
Entity * /*mech*/,
|
|
L4Warehouse * /*warehouse*/,
|
|
int /*palette_base*/,
|
|
Pixmap *destination
|
|
)
|
|
{
|
|
return destination;
|
|
}
|
|
|
|
//
|
|
// Keyword + parameter list VERBATIM (parse-critical). CFG (L4GAUGE.CFG:29):
|
|
// PlayerStatus( D, ModeAlwaysActive, 1, helv15.pcc, 0, 11, 3, 1 )
|
|
//
|
|
MethodDescription
|
|
PlayerStatus::methodDescription =
|
|
{
|
|
"PlayerStatus",
|
|
PlayerStatus::Make,
|
|
{
|
|
{ ParameterDescription::typeRate, NULL }, // p[0] rate
|
|
{ ParameterDescription::typeModeMask, NULL }, // p[1] mode mask
|
|
{ ParameterDescription::typeInteger, NULL }, // p[2] player number 1..8
|
|
{ ParameterDescription::typeString, NULL }, // p[3] font image
|
|
{ ParameterDescription::typeColor, NULL }, // p[4] color
|
|
{ ParameterDescription::typeColor, NULL }, // p[5] okColor
|
|
{ ParameterDescription::typeColor, NULL }, // p[6] aliveColor
|
|
{ ParameterDescription::typeColor, NULL }, // p[7] deadColor
|
|
PARAMETER_DESCRIPTION_END
|
|
}
|
|
};
|
|
|
|
//
|
|
//#############################################################################
|
|
// @004cae90 -- Make. The player number must be 1..8 or "PlayerStatus: Make
|
|
// player number <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.
|