gauge wave P2b: build the Comm KILLS/DEATHS pilot roster (PilotList)
PilotList (keyword "pilotList") was PROSE-ONLY, so the Comm surface showed only
the baked btcomm.pcx labels. Reconstructed all 7 functions from part_014.c:
3156-3434 (Make/ctor/dtor/BecameActive/TestInstance/Execute/DrawMechIcon), with
the 8x(x,y,layoutMode) layout table DAT_0051af88 PE-parsed exact from BTL4OPT.EXE.
Draws ONE roster slot/frame (round-robin) from the viewpoint mech's cockpit-mapper
pilot roster.
- Roster FEED: new BTResolveRosterPilot(slot) bridge in mechmppr.cpp (a complete-Mech
TU) resolves the viewpoint mech's ControlsMapper (subsystemArray[0]) -> GetPilot;
btl4gau3.cpp reads the returned pilot at raw BTPlayer offsets. The mapper's
FillPilotArray already fills the roster (pilotArray[0]=GetMissionPlayer,
[1..]=FindGroup("Players")).
- KILLS = killCount@0x27c (real; ScoreMessageHandler increments it in combat).
- DEATHS: the binary reads pad_0x280 which has NO writer anywhere (a shipped dead
field -> perpetual 0). FIX per "if it doesn't work, fix it": read the real
deaths counter Player::deathCount@0x200 (VehicleDeadMessageHandler increments it)
so DEATHS is meaningful in MP.
- DrawMechIcon: App+0xC8 name-bitmap cache is unwired (same deferral PlayerStatus
uses) -> LookupPlayerNameBitmap returns NULL -> the tinted name box (never an AV).
Dropped the bogus x,y from the header ctor (positions come from DAT_0051af88).
/FORCE-safe (all vtable slots real; link log clean, no unresolved PilotList/
BTResolveRosterPilot). Verified DBASE+dev gauges: the Comm surface now renders the
live local pilot row (KILLS 0 DEATHS 0 + name box; 0/0 authentic in solo -- the
combat scoring feed that moves them is Phase 3), combat un-regressed (DESTROYED),
0 crashes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
d68284ede2
commit
0060a3e1ca
+217
-15
@@ -176,24 +176,226 @@ Logical
|
||||
|
||||
|
||||
//###########################################################################
|
||||
// PilotList @004ca90c Make / @004ca958 ctor
|
||||
// PilotList (config keyword "pilotList" -- the Comm KILLS/DEATHS roster).
|
||||
// Reconstructed from part_014.c:3156-3434; vtable PTR_FUN_0051be64, sizeof 0x178.
|
||||
// Draws ONE roster slot per frame (round-robin over 8) from the viewpoint mech's
|
||||
// cockpit-mapper pilot roster. Was PROSE-ONLY -> the "pilotList" line was
|
||||
// parse-skipped and the Comm surface showed only the baked btcomm.pcx labels.
|
||||
//###########################################################################
|
||||
|
||||
// The roster lives on the viewpoint mech's ControlsMapper (subsystemArray[0]);
|
||||
// the bridge (mechmppr.cpp, a complete-Mech TU) resolves it -- this TU only sees
|
||||
// the pilot as an opaque pointer read at BTPlayer raw offsets.
|
||||
extern void *BTResolveRosterPilot(int slot);
|
||||
|
||||
// App+0xC8 player-name-bitmap cache is not wired in the port (same deferral
|
||||
// PlayerStatus uses for its nameImage) -> NULL routes DrawMechIcon to the binary's
|
||||
// own cache-miss branch (the tinted name box), never an AV.
|
||||
static BitMap *LookupPlayerNameBitmap(int /*name_id*/) { return NULL; }
|
||||
|
||||
MethodDescription
|
||||
PilotList::methodDescription =
|
||||
{
|
||||
"pilotList",
|
||||
PilotList::Make,
|
||||
{
|
||||
{ ParameterDescription::typeRate, NULL }, // rate (C)
|
||||
{ ParameterDescription::typeModeMask, NULL }, // mode (ModeAlwaysActive)
|
||||
{ ParameterDescription::typeString, NULL }, // font (bigfont.pcc)
|
||||
PARAMETER_DESCRIPTION_END
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
// @004ca90c -- Make: allocate (0x178) and construct "PilotList" from
|
||||
// DAT_0051ab4c..0051abd4.
|
||||
// @004ca958 -- ctor (vtable PTR_FUN_0051be64). GraphicGauge base. For each of
|
||||
// 8 entries, read its (x,y,layoutMode) from DAT_0051af88 (stride 3); compute the
|
||||
// three NumericDisplay positions from layoutMode (0/1/2) and build them
|
||||
// (this entry +0x10/+0x14/+0x18). resolvedMech = 0; selected = 0.
|
||||
// @004cab10 -- dtor: delete the 3 numerics per entry (8 entries), base dtor.
|
||||
// @004caba4 -- BecameActive: per-entry resolvedMech (+0x08) = -1; currentSlot = 9.
|
||||
// @004cabd0 -- Execute (one slot per frame). Look up the lance roster
|
||||
// (App+0x6c mech +0x128 -> FUN_004b0898 slot lookup). For the current slot:
|
||||
// if empty, erase the three numerics; else compute the player-name bitmap and
|
||||
// blit it (DrawMechIcon @004cad70 tints by team via FUN_004c2f88), then DrawAt
|
||||
// the mech (+0x27c) and score (+0x280) numerics. Advance currentSlot (wraps 8).
|
||||
// helpers DrawMechIcon @004cad70, DrawAmmoCount @004cae20.
|
||||
// @004ca90c -- Make. Allocate 0x178 + placement-construct. position UNUSED
|
||||
// (each entry carries absolute coords from DAT_0051af88). Always returns True.
|
||||
//
|
||||
Logical
|
||||
PilotList::Make(
|
||||
int display_port_index,
|
||||
Vector2DOf<int> /*position -- unused*/,
|
||||
Entity * /*entity -- unused*/,
|
||||
GaugeRenderer *gauge_renderer
|
||||
)
|
||||
{
|
||||
ParameterDescription *p = methodDescription.parameterList;
|
||||
|
||||
PilotList *g = (PilotList *)operator new(0x178); // FUN_00402298(0x178)
|
||||
if (g != NULL)
|
||||
{
|
||||
new (g) PilotList(
|
||||
p[0].data.rate, p[1].data.modeMask,
|
||||
(L4GaugeRenderer *)gauge_renderer,
|
||||
display_port_index, // graphics_port_number
|
||||
p[2].data.string, // font (bigfont.pcc)
|
||||
"PilotList");
|
||||
}
|
||||
return True;
|
||||
}
|
||||
|
||||
//
|
||||
// @004ca958 -- ctor. GraphicGauge base; build 3 NumericDisplays/entry from the
|
||||
// PE-parsed 8x(x,y,layoutMode) layout table (DAT_0051af88).
|
||||
//
|
||||
PilotList::PilotList(
|
||||
GaugeRate rate,
|
||||
ModeMask mode_mask,
|
||||
L4GaugeRenderer *renderer_in,
|
||||
int graphics_port_number,
|
||||
const char *font_image,
|
||||
const char *id_string
|
||||
):
|
||||
GraphicGauge(rate, mode_mask, renderer_in, 0, graphics_port_number, id_string)
|
||||
{
|
||||
currentSlot = 0;
|
||||
built = False;
|
||||
L4Warehouse *wh = (L4Warehouse *)renderer_in->warehousePointer; // renderer+0x4c
|
||||
|
||||
// DAT_0051af88 -- 8 x (x, y, layoutMode); PE-parsed exact from BTL4OPT.EXE.
|
||||
static const int L[8][3] =
|
||||
{
|
||||
{180,225,0},{17,443,1},{177,443,1},{337,443,1},
|
||||
{497,443,1},{17,7,2},{177,7,2},{337,7,2}
|
||||
};
|
||||
for (int i = 0; i < 8; ++i)
|
||||
{
|
||||
int x = L[i][0], y = L[i][1], m = L[i][2];
|
||||
int nx, ny, mx, my;
|
||||
if (m == 1) { nx = x-8; ny = y-0x50; mx = x+0x48; my = y-0x50; }
|
||||
else if (m == 2) { nx = x-8; ny = y+0x44; mx = x+0x48; my = y+0x44; }
|
||||
else { nx = x+0x95; ny = y-0x0d; mx = x+0xe8; my = y-0x0d; } // m==0
|
||||
|
||||
entry[i].x = x; entry[i].y = y;
|
||||
entry[i].resolvedMech = 0; entry[i].selected = 0;
|
||||
entry[i].nameDisplay = new NumericDisplay(wh, nx, ny, font_image, 2,
|
||||
NumericDisplay::signedBlankedZerosFormat, 0, 0xff); // KILLS
|
||||
entry[i].mechDisplay = new NumericDisplay(wh, mx, my, font_image, 2,
|
||||
NumericDisplay::signedBlankedZerosFormat, 0, 0xff); // DEATHS
|
||||
entry[i].scoreDisplay = new NumericDisplay(wh, x, y, font_image, 1,
|
||||
NumericDisplay::signedBlankedZerosFormat, 0, 0xff); // erase-only
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// @004cab10 -- dtor. Free the 3 numerics per entry; the base chain runs implicitly.
|
||||
//
|
||||
PilotList::~PilotList()
|
||||
{
|
||||
for (int i = 0; i < 8; ++i)
|
||||
{
|
||||
delete entry[i].nameDisplay; entry[i].nameDisplay = NULL;
|
||||
delete entry[i].mechDisplay; entry[i].mechDisplay = NULL;
|
||||
delete entry[i].scoreDisplay; entry[i].scoreDisplay = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// @004caba4 -- BecameActive. Invalidate every entry so the first cycle repaints.
|
||||
//
|
||||
void
|
||||
PilotList::BecameActive()
|
||||
{
|
||||
for (int i = 0; i < 8; ++i)
|
||||
entry[i].resolvedMech = -1;
|
||||
currentSlot = 9; // forces the wrap-reset on the first Execute
|
||||
}
|
||||
|
||||
//
|
||||
// @004cab8c -- TestInstance (out-of-line forward to the base; body required so
|
||||
// the declared method is not /FORCE-stubbed).
|
||||
//
|
||||
Logical
|
||||
PilotList::TestInstance() const
|
||||
{
|
||||
return GraphicGauge::TestInstance();
|
||||
}
|
||||
|
||||
//
|
||||
// @004cad70 -- DrawMechIcon. Blit the pilot's name bitmap (or, on a cache miss --
|
||||
// always, in the port -- a tinted 0x80 x 0x20 box), tinted by the selected flag.
|
||||
//
|
||||
void
|
||||
PilotList::DrawMechIcon(void *pilot, int selected)
|
||||
{
|
||||
int nameId = *(int *)((char *)pilot + 0x1e0); // pilot name/id key
|
||||
BitMap *bmp = LookupPlayerNameBitmap(nameId);
|
||||
int boxColor = selected ? 0xff : 0;
|
||||
int blitColor = selected ? 0 : 0xff;
|
||||
if (bmp == NULL)
|
||||
{
|
||||
localView.SetColor(boxColor); // vtbl+0x18
|
||||
localView.DrawFilledRectangleToRelative(0x80, 0x20); // vtbl+0x4c
|
||||
}
|
||||
else
|
||||
{
|
||||
localView.SetColor(blitColor);
|
||||
localView.DrawBitMapOpaque(boxColor, 0, bmp, 0, 0, // vtbl+0x58
|
||||
bmp->Data.Size.x - 1, bmp->Data.Size.y - 1);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// @004cabd0 -- Execute (one roster slot per frame). Look up the current slot from
|
||||
// the viewpoint mech's ControlsMapper roster; erase an empty slot, or draw the
|
||||
// pilot's name icon + KILLS + DEATHS for an occupied one, tinting the row when it
|
||||
// is the local pilot's current target.
|
||||
//
|
||||
void
|
||||
PilotList::Execute()
|
||||
{
|
||||
void *local = BTResolveRosterPilot(0); // slot-0 (local) pilot gates the body
|
||||
if (local == NULL)
|
||||
return;
|
||||
|
||||
if (currentSlot > 7) { currentSlot = 0; built = False; } // wrap the round-robin
|
||||
Entry &e = entry[currentSlot];
|
||||
|
||||
void *pilot;
|
||||
if (built == False)
|
||||
{
|
||||
pilot = BTResolveRosterPilot(currentSlot);
|
||||
if (pilot == NULL) built = True; // past the first empty slot -> rest empty this cycle
|
||||
}
|
||||
else
|
||||
{
|
||||
pilot = NULL;
|
||||
}
|
||||
|
||||
if (pilot == NULL)
|
||||
{
|
||||
if (e.resolvedMech != 0) // (-1 after BecameActive) -> erase the box + numerics
|
||||
{
|
||||
localView.MoveToAbsolute(e.x, e.y); // vtbl+0x24
|
||||
localView.SetColor(0); // vtbl+0x18
|
||||
localView.DrawFilledRectangleToRelative(0x80, 0x20); // vtbl+0x4c
|
||||
e.nameDisplay->Erase(&localView);
|
||||
e.mechDisplay->Erase(&localView);
|
||||
e.scoreDisplay->Erase(&localView);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
void *tgt = *(void **)((char *)local + 0x284); // local pilot's target (objectiveMech)
|
||||
int selected = (tgt != NULL) && (pilot == *(void **)((char *)tgt + 0x190)); // target's owningPlayer
|
||||
if ((int)(size_t)pilot != e.resolvedMech || e.selected != selected)
|
||||
{
|
||||
localView.MoveToAbsolute(e.x, e.y); // vtbl+0x24
|
||||
DrawMechIcon(pilot, selected);
|
||||
e.selected = selected;
|
||||
// (byte-faithful: the binary does NOT latch e.resolvedMech here, so the
|
||||
// icon redraws each occupied frame.)
|
||||
}
|
||||
// KILLS = killCount@0x27c (real; ScoreMessageHandler kill branch increments it).
|
||||
e.nameDisplay->Draw(&localView, (Scalar)(*(int *)((char *)pilot + 0x27c)));
|
||||
// DEATHS: the binary reads pad_0x280 (NO writer anywhere -> perpetual 0 = a
|
||||
// shipped dead field). FIX (correctness deviation, per "if it doesn't work,
|
||||
// fix it"): read the real deaths counter Player::deathCount@0x200
|
||||
// (VehicleDeadMessageHandler increments it), so DEATHS is meaningful in MP.
|
||||
e.mechDisplay->Draw(&localView, (Scalar)(*(int *)((char *)pilot + 0x200)));
|
||||
}
|
||||
|
||||
++currentSlot;
|
||||
}
|
||||
|
||||
|
||||
//###########################################################################
|
||||
|
||||
@@ -175,16 +175,19 @@
|
||||
public GraphicGauge
|
||||
{
|
||||
public:
|
||||
// gauge wave P2: registered as "pilotList" -- the Comm KILLS/DEATHS roster.
|
||||
static MethodDescription methodDescription;
|
||||
static Logical Make(int, Vector2DOf<int>, Entity *, GaugeRenderer *); // @004ca90c
|
||||
PilotList( // @004ca958
|
||||
PilotList( // @004ca958 (NO x,y: positions come from DAT_0051af88)
|
||||
GaugeRate, ModeMask, L4GaugeRenderer *, int,
|
||||
int x, int y, const char *font_image,
|
||||
const char *font_image,
|
||||
const char *identification_string);
|
||||
~PilotList(); // @004cab10
|
||||
Logical TestInstance() const;
|
||||
void BecameActive(); // @004caba4
|
||||
void Execute(); // @004cabd0
|
||||
protected:
|
||||
void DrawMechIcon(void *pilot, int selected); // @004cad70
|
||||
int currentSlot; // @0x90 this[0x24] (round-robin, =9 init)
|
||||
Logical built; // @0x94 this[0x25]
|
||||
struct Entry { // 8 entries (stride 0x1C) from this[0x26]
|
||||
|
||||
@@ -145,6 +145,7 @@ MethodDescription
|
||||
&VehicleSubSystems::methodDescription, // "vehicleSubSystems" -- engineering-screen subsystem cluster panels
|
||||
&BitMapInverseWipe::methodDescription, // "LeakGauge" -- coolant-leak inverse-wipe (Condenser/CoolantMassLeakRate)
|
||||
&VertNormalSlider::methodDescription, // "vertNormalSlider" -- condenser valve-setting slider (heat MFD)
|
||||
&PilotList::methodDescription, // "pilotList" -- Comm KILLS/DEATHS pilot roster
|
||||
&BTL4ChainToPrevious
|
||||
};
|
||||
|
||||
|
||||
@@ -1177,3 +1177,29 @@ void
|
||||
MechControlsMapper::NotifyOfDisplayModeChange(int /*new_mode*/)
|
||||
{
|
||||
}
|
||||
|
||||
//
|
||||
// gauge wave P2: BTResolveRosterPilot -- the bridge PilotList (btl4gau3.cpp, the
|
||||
// Comm KILLS/DEATHS roster) uses to read the viewpoint mech's pilot roster. The
|
||||
// roster lives on the mech's ControlsMapper (subsystemArray[0]); this complete-Mech
|
||||
// TU resolves it, PilotList reads the returned pilot at raw BTPlayer offsets.
|
||||
// Faithful to the binary's Execute: **(App+0x6c mech +0x128)[slot] -> GetPilot.
|
||||
//
|
||||
void *BTResolveRosterPilot(int slot)
|
||||
{
|
||||
if (application == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
Mech *mech = (Mech *)application->GetViewpointEntity(); // App+0x6c local mech
|
||||
if (mech == 0 || mech->GetSubsystemCount() < 1)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
MechControlsMapper *mapper = (MechControlsMapper *)mech->GetSubsystem(0); // subsystemArray[0]
|
||||
if (mapper == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return (void *)mapper->GetPilot(slot); // FUN_004b0898
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user