gauges: reconstruct + register vertBar (VertTwoPartBar) -- cockpit coolant bars

VertTwoPartBar was declared in btl4gaug.hpp (ctor/field offsets/decomp addrs)
but never defined.  Reconstruct Make (@004c462c) / ctor (@004c4724) /
BecameActive (@004c48e0) / Execute (@004c48fc) / dtor + the "vertBar"
methodDescription, and register it in BTL4MethodDescription[].

The Execute pixel math is x87 that Ghidra dropped (FUN_004dcd94 is just
(int)ROUND(ST0); the operands are on the FPU stack) -- recovered by disassembling
@004c4940/@004c4960:
  warnPix = clamp(round(height*low/high  + 0.5), 0, height)
  valPix  = clamp(round(height*value/high + 0.5), 0, height)
value is first clamped to [0,high]; on a change it tile-blits the bitmap over
[0,warnPix), fills [warnPix,valPix) with fillColor, clears [valPix,height) with
extraColor (reusing the existing DrawTiledBitmap helper = FUN_004c2ff8; view
methods SetPositionWithinPort/SetColor/MoveToAbsolute/DrawFilledRectangleTo
Absolute = vtbl +0x08/+0x18/+0x24/+0x48).  methodDescription param types
(rate,modeMask,vector,string,color,color,color,attribute,attribute,attribute)
match the config vertBar(rate,mode,(w,h),tile,bg,fill,extra,current,warn,max).

For the coolant bars the config binds current=CoolantMass, warn=max=Coolant
Capacity, so the warn line sits at full and the bar is a simple level gauge that
empties as CoolantMass drops -- now driven by the HeatSink attribute table
committed earlier.

Verified live (BT_DEV_GAUGES): the Heat surface COOLANT/RES bars now render the
btwarn.pcc hatch fill (was empty), reading the real coolant attributes (full
hatch = coolantLevel~=capacity).  No /FORCE unresolved externals on the new
symbols.  Combat un-regressed (TARGET DESTROYED after 8 hits), 0 crashes, and
the gauge build + mission load are heap-clean under BT_HEAPCHECK.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-06 18:42:04 -05:00
co-authored by Claude Opus 4.8
parent 9e31408ba2
commit 18d58f38f6
3 changed files with 213 additions and 7 deletions
+205 -3
View File
@@ -1022,11 +1022,213 @@ static void
// background fill for the remainder). Uses _DAT_004c452c (0.0) as the floor.
//
//###########################################################################
// VertTwoPartBar @004c462c Make / @004c4724 ctor / @004c48e0 BecameActive
// @004c48fc Execute (vtable PTR_FUN_00518c78)
//
// @004c462c Make / @004c4724 ctor / @004c48e0 BecameActive / @004c48fc Execute
// -- VertTwoPartBar: identical structure to HorizTwoPartBar but grows
// bottom->top (vtable PTR_FUN_00518c78). Floor constant _DAT_004c4b00 (0.0).
// A vertical two-part fill bar (config keyword "vertBar"). Three Scalar
// connections drive it: value (current), low (warn threshold), high (max). The
// bar grows bottom->top; pixels are value/high and low/high fractions of the bar
// height. Used for the cockpit COOLANT bars (config binds current=CoolantMass,
// warn=CoolantCapacity, max=CoolantCapacity -- so the warn line sits at full and
// the bar simply empties as CoolantMass drops during firing).
//###########################################################################
MethodDescription
VertTwoPartBar::methodDescription =
{
"vertBar",
VertTwoPartBar::Make,
{
//
// CFG shape (L4GAUGE.CFG:4521):
// vertBar( rate, mode, (w,h), tile.pcc, bg,fill,extra,
// currentAttr, warnAttr, maxAttr )
// e.g. vertBar(C,ModeAlwaysActive,(31,160),btwarn.pcc,255,255,0,
// HeatSink/CoolantMass, HeatSink/CoolantCapacity,
// HeatSink/CoolantCapacity);
//
{ ParameterDescription::typeRate, NULL }, // rate ID
{ ParameterDescription::typeModeMask, NULL }, // mode mask
{ ParameterDescription::typeVector, NULL }, // (width,height)
{ ParameterDescription::typeString, NULL }, // tile bitmap name
{ ParameterDescription::typeColor, NULL }, // background colour -> @0x94
{ ParameterDescription::typeColor, NULL }, // fill colour -> @0x98
{ ParameterDescription::typeColor, NULL }, // extra/empty colour-> @0x9C
{ ParameterDescription::typeAttribute, NULL }, // current value -> @0xB0
{ ParameterDescription::typeAttribute, NULL }, // warn threshold -> @0xB4
{ ParameterDescription::typeAttribute, NULL }, // max -> @0xB8
PARAMETER_DESCRIPTION_END
}
};
//
// @004c462c -- Make. Allocate + construct, then verify the tile bitmap exists
// (== the binary's "VertTwoPartBarNormalized: Missing image" warning path).
//
Logical
VertTwoPartBar::Make(
int display_port_index,
Vector2DOf<int> position,
Entity * /*entity -- unused*/,
GaugeRenderer *gauge_renderer
)
{
ParameterDescription *p = methodDescription.parameterList;
VertTwoPartBar *gauge = (VertTwoPartBar *)operator new(0xbc); // FUN_00402298(0xbc)
if (gauge != NULL)
{
new (gauge) VertTwoPartBar( // FUN_004c4724
p[0].data.rate, p[1].data.modeMask,
(L4GaugeRenderer *)gauge_renderer,
display_port_index, // graphics_port_number
position.x, position.y, // left, bottom
position.x + p[2].data.vector.x, // right = x + width
position.y + p[2].data.vector.y, // top = y + height
p[3].data.string, // tile bitmap
p[4].data.color, // backgroundColor
p[5].data.color, // fillColor
p[6].data.color, // extraColor
(Scalar *)p[7].data.attributePointer, // current value source
(Scalar *)p[8].data.attributePointer, // warn threshold source
(Scalar *)p[9].data.attributePointer, // max source
"VertTwoPartBar");
}
L4Warehouse *warehouse = (L4Warehouse *)gauge_renderer->warehousePointer;
if (warehouse->bitMapBin.Get(p[3].data.string) == NULL) // FUN_00442aec
{
DebugStream << "VertTwoPartBarNormalized: Missing image '"
<< p[3].data.string << "'\n";
return False;
}
warehouse->bitMapBin.Release(p[3].data.string); // FUN_00442c12
return True;
}
//
// @004c4724 -- ctor. GraphicGauge base; intern + ref-count the tile bitmap; set
// the graphics-port extent (bottom->top); store the three colours and the bar
// size; wire three GaugeConnectionDirectOf<Scalar> feeds (value/low/high).
//
VertTwoPartBar::VertTwoPartBar(
GaugeRate rate,
ModeMask mode_mask,
L4GaugeRenderer *renderer_in,
int graphics_port_number,
int left,
int bottom,
int right,
int top,
const char *tile_image,
int background_color,
int fill_color,
int extra_color,
Scalar *value_pointer,
Scalar *low_pointer,
Scalar *high_pointer,
const char *identification_string
):
GraphicGauge(rate, mode_mask, renderer_in, 0, // FUN_00444818 (owner_ID 0)
graphics_port_number, identification_string)
{
// Own a copy of the tile-bitmap name (Make passes the transient interpreter
// scratch buffer) and hold a ref for the gauge's life.
tileImage = new char[strlen(tile_image) + 1]; // @0x90 FUN_004700ac
strcpy(tileImage, tile_image);
L4Warehouse *warehouse = (L4Warehouse *)renderer_in->warehousePointer;
warehouse->bitMapBin.Get(tileImage); // FUN_00442aec (held)
localView.SetPositionWithinPort(left, bottom, right, top); // this+0x48 vtbl+0x08
backgroundColor = background_color; // @0x94 this[0x25]
fillColor = fill_color; // @0x98 this[0x26]
extraColor = extra_color; // @0x9C this[0x27]
width = (right - left) - 1; // @0xA0 this[0x28]
height = top - bottom; // @0xA4 this[0x29]
AddConnection(new GaugeConnectionDirectOf<Scalar>(0, &value, value_pointer)); // @0xB0
AddConnection(new GaugeConnectionDirectOf<Scalar>(0, &low, low_pointer)); // @0xB4
AddConnection(new GaugeConnectionDirectOf<Scalar>(0, &high, high_pointer)); // @0xB8
}
//
// @004c486c -- dtor. Release the tile ref (keyed on tileImage, before the free),
// free the interned name; the base chain runs implicitly.
//
VertTwoPartBar::~VertTwoPartBar()
{
L4Warehouse *warehouse = (L4Warehouse *)renderer->warehousePointer;
warehouse->bitMapBin.Release(tileImage); // FUN_00442c12
delete[] tileImage; // FUN_004022e8
tileImage = NULL;
}
Logical
VertTwoPartBar::TestInstance() const
{
return GraphicGauge::TestInstance();
}
//
// @004c48e0 -- BecameActive: force a full redraw on the first Execute
// (previousFull=0, previousFill=width -- neither can match a real pixel pair).
//
void
VertTwoPartBar::BecameActive()
{
previousFull = 0; // @0xAC this[0x2B]
previousFill = width; // @0xA8 this[0x2A]
}
//
// @004c48fc -- Execute. Clamp value to [0,high]; map value and warn to bar
// pixels (half-up round of height*x/high, clamped to [0,height]); if either
// changed, repaint: tile the bitmap over [0,warnPix), fill [warnPix,valPix) with
// fillColor, and clear [valPix,height) with extraColor. (x87 pixel math recovered
// by disassembly @004c4940/@004c4960 -- Ghidra dropped the FPU args.)
//
void
VertTwoPartBar::Execute()
{
// clamp value to [0, high] (@004c4908 fcomp 0.0 ; @004c4929 fcomp high)
if (value < 0.0f)
value = 0.0f;
else if (value > high)
value = high;
int warnPix = (int)((Scalar)height * low / high + 0.5f); // warn threshold pixel
int valPix = (int)((Scalar)height * value / high + 0.5f); // current value pixel
if (warnPix < 0) warnPix = 0; else if (warnPix > height) warnPix = height;
if (valPix < 0) valPix = 0; else if (valPix > height) valPix = height;
if (warnPix != previousFull || valPix != previousFill)
{
L4Warehouse *warehouse = (L4Warehouse *)renderer->warehousePointer;
BitMap *tile = warehouse->bitMapBin.Get(tileImage); // FUN_00442aec
localView.SetColor(backgroundColor); // vtbl+0x18
DrawTiledBitmap(&localView, 0, 0, width, warnPix - 1, extraColor, tile);
warehouse->bitMapBin.Release(tileImage); // FUN_00442c12
if (warnPix < valPix) // normal fill above the warn line
{
localView.SetColor(fillColor);
localView.MoveToAbsolute(0, warnPix); // vtbl+0x24
localView.DrawFilledRectangleToAbsolute(width, valPix - 1); // vtbl+0x48
}
if (valPix < height) // empty region above the value
{
localView.SetColor(extraColor);
localView.MoveToAbsolute(0, valPix);
localView.DrawFilledRectangleToAbsolute(width, height);
}
previousFull = warnPix;
previousFill = valPix;
}
}
//
// @004c4b84 ctor / @004c4cac BecameActive / @004c4cc0 Execute
+7 -4
View File
@@ -264,12 +264,15 @@
public GraphicGauge
{
public:
// Registered in BTL4MethodDescription[] (btl4grnd.cpp) as "vertBar".
static MethodDescription methodDescription;
static Logical Make(int, Vector2DOf<int>, Entity *, GaugeRenderer *); // @004c462c
VertTwoPartBar( // @004c4724
GaugeRate, ModeMask, L4GaugeRenderer *, int,
VertTwoPartBar( // @004c4724 (ctor arg order = binary param_12..17)
GaugeRate, ModeMask, L4GaugeRenderer *, int graphics_port_number,
int left, int bottom, int right, int top,
const char *tile_image, int fill_color, int background_color,
int /*extra_color*/,
const char *tile_image,
int background_color, int fill_color, int extra_color,
Scalar *value_pointer, Scalar *low_pointer, Scalar *high_pointer,
const char *identification_string);
~VertTwoPartBar();
+1
View File
@@ -134,6 +134,7 @@ MethodDescription
&ColorMapperArmor::methodDescription, // "cmArmor" -- per-zone armor-damage tint
&ColorMapperMultiArmor::methodDescription, // "colorMapperMultiArmor" -- worst-of-8-zones tint
&ColorMapperCritical::methodDescription, // "cmCrit" -- subsystem-critical-state tint
&VertTwoPartBar::methodDescription, // "vertBar" -- vertical fill bar (coolant/heat)
&BTL4ChainToPrevious
};