vehicleSubSystems Phase 2: authentic aux-screen assignment -> panels build
The engineering-screen cluster panels now build for each subsystem on its real aux-screen position. Two pieces: 1. Aux-screen bridge (powersub.cpp BTGetSubsystemAuxScreen): the assignment lives on PoweredSubsystem (auxScreenNumber/Placement/Label, resource +0x104/108/10C) which our ctor already populates -- but the reconstructed heat-leaf branch is NOT byte-exact, so the Make's raw sub[0x1dc]/[0x1e0]/[0x1e4] reads were garbage (0xCDCDCDCD). The bridge casts through the real PoweredSubsystem type + returns the NAMED fields (and is the FUN_0041a1a4/0x50f4bc type filter). Make + Subsystem Cluster now read via the bridge. 2. Reconstruct the 4 child gauges the clusters build that were declared-but-undefined (only VertTwoPartBar/OneOfSeveral/PixInt existed): HorizTwoPartBar (full, mirrors Vert), OneOfSeveralInt (ctor), OneOfSeveralStates (ctor + BecameActive + State Connection @004c3324), BitMapInverseWipe/LeakGauge (full) + SeekVoltageGraph's Execute/BecameActive/dtor -- their /FORCE-stubbed ctors were segfaulting when a cluster built them. VERIFIED (BT_DEV_GAUGES + BT_VSS_LOG): 7 authentic panels build -- Sensor(scr5, qsensors) HeatSinkCluster, Myomers(scr6,qmyomers) MyomerCluster, PPC(scr1,10) + Emitter(scr4,8,7) Energy/Ballistic clusters -- with real labels + placements, 0 crashes, combat un-regressed. 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
a02339112a
commit
6fceb58439
@@ -132,6 +132,16 @@ static void *AttributePointerOf(void *subsystem, const char *name)
|
||||
//
|
||||
static void *ResolveLink(void *plug) { (void)plug; return 0; }
|
||||
|
||||
//
|
||||
// Bridge (defined in powersub.cpp) that reads the engineering-screen assignment
|
||||
// off the real PoweredSubsystem type (auxScreenNumber/Placement/Label) -- the
|
||||
// reconstructed heat-leaf branch is not byte-exact, so the raw sub+0x1dc/0x1e0/
|
||||
// 0x1e4 offsets read garbage; the named fields (populated by the PoweredSubsystem
|
||||
// ctor from the resource) are correct. Returns False for non-PoweredSubsystem-
|
||||
// derived subsystems (== the vehicleSubSystems type filter).
|
||||
//
|
||||
extern bool BTGetSubsystemAuxScreen(Subsystem *sub, int *screen, int *placement, char *label64);
|
||||
|
||||
//
|
||||
// FUN_0041bf44 -- fetch the subsystem linked at slot N (used by the cooling-loop
|
||||
// lamp to find its cooling master). Stubbed to match ResolveLink above.
|
||||
@@ -277,10 +287,44 @@ SeekVoltageGraph::SeekVoltageGraph(
|
||||
|
||||
//
|
||||
// @004c68ac -- dtor. Release the destroyed bitmap, GraphicGauge::~GraphicGauge.
|
||||
//
|
||||
SeekVoltageGraph::~SeekVoltageGraph()
|
||||
{
|
||||
L4Warehouse *warehouse = (L4Warehouse *)renderer->warehousePointer;
|
||||
warehouse->bitMapBin.Release(destroyedImage);
|
||||
}
|
||||
|
||||
Logical SeekVoltageGraph::TestInstance() const
|
||||
{
|
||||
return GraphicGauge::TestInstance();
|
||||
}
|
||||
|
||||
//
|
||||
// @004c6920 -- BecameActive: previousVoltage = 9999.0f (force first redraw).
|
||||
//
|
||||
// @004c6934 -- Execute. If the subsystem is destroyed (subsys+0x40 == 1) draw
|
||||
void SeekVoltageGraph::BecameActive()
|
||||
{
|
||||
previousVoltage = RedrawSentinel; // @0xAC force first redraw
|
||||
destroyedShown = 0;
|
||||
}
|
||||
|
||||
//
|
||||
// @004c6934 -- Execute (the seek-voltage curve plot). BRING-UP: the full plot
|
||||
// (SetupClip/DrawTicks/DrawCursor over a 0..1 voltage polyline) needs the live
|
||||
// seek-voltage sampler (subsys vtbl+0x3c) + the four attribute indices; the
|
||||
// attribute resolve is summarised (FindAttributeIndex returns -1), so the plot is
|
||||
// deferred and Execute is inert until the sampler + attribute reads land. Kept as
|
||||
// a defined no-op so the gauge's vtable is complete (no /FORCE crash when the
|
||||
// Myomer/Energy cluster ticks its SeekVoltageGraph child).
|
||||
//
|
||||
void SeekVoltageGraph::Execute()
|
||||
{
|
||||
// (seek-voltage plot deferred -- see note above.)
|
||||
}
|
||||
|
||||
//
|
||||
// @004c6934 (original notes preserved) -- Execute. If the subsystem is destroyed
|
||||
// (subsys+0x40 == 1) draw
|
||||
// the centred "destroyed" bitmap once. Otherwise, when the live seek voltage
|
||||
// (subsys vtbl+0x3c sampler) changes: clear (SetupClip @004c6be4), re-plot the
|
||||
// 0..1 voltage curve as a polyline (step _DAT_004c6bdc up to _DAT_004c6be0,
|
||||
@@ -751,8 +795,10 @@ SubsystemCluster::SubsystemCluster(
|
||||
mfd_port, x + 0x10e, y + 0xe, "btpbus.pcc", 5, 1,
|
||||
inputVoltage, "PowerSource");
|
||||
|
||||
// Background: image_names[ subsystem damageZoneIndex@0x1e0 ] if present.
|
||||
int dzIndex = *(int *)((char *)subsystem_in + 0x1e0); // BEST-EFFORT raw offset
|
||||
// Background: image_names[ auxScreenPlacement ] if present. (Placement@0x1e0 is
|
||||
// read via the PoweredSubsystem bridge, not the non-byte-exact raw offset.)
|
||||
int dzIndex = -1;
|
||||
BTGetSubsystemAuxScreen(subsystem_in, NULL, &dzIndex, NULL); // auxScreenPlacement
|
||||
background = NULL;
|
||||
if (dzIndex >= 0 && image_names[dzIndex] != NULL)
|
||||
background = new BackgroundBitmap(mfd_mode, renderer_in, owner_ID, // @00471d00
|
||||
@@ -814,8 +860,9 @@ SubsystemCluster::SubsystemCluster(
|
||||
(Scalar *)linkTemp, (Scalar *)linkDegrade, (Scalar *)failTemp, "VertTwoPartBar");
|
||||
|
||||
leakGauge = new BitMapInverseWipe(ChildRate(), eng_mode, renderer_in, // @004c5b7c
|
||||
engPort, 0x255, 0xe0, "eleak.pcc", 0, 0xff, 3,
|
||||
*(int *)((char *)subsystem_in + 0x150), (Scalar *)coolantLeak, "LeakGauge"); // BEST-EFFORT raw
|
||||
engPort, 0x255, 0xe0, "eleak.pcc", 0, 0xff,
|
||||
*(int *)((char *)subsystem_in + 0x150) /*third*/, 3 /*frames*/,
|
||||
(Scalar *)coolantLeak, "LeakGauge"); // BEST-EFFORT raw (subsys+0x150)
|
||||
|
||||
operating = False; // @0xC4 this[0x31]
|
||||
subsystem = subsystem_in; // @0xC0 this[0x30]
|
||||
@@ -1380,13 +1427,19 @@ Logical
|
||||
Subsystem *sub = entity->GetSubsystem(i); // entity+0x128[i]
|
||||
if (sub == NULL)
|
||||
continue;
|
||||
if (!SubsystemDisplayFilter(sub)) // FUN_0041a1a4
|
||||
|
||||
// Read the engineering-screen assignment via the PoweredSubsystem bridge
|
||||
// (also the type filter -- False == not PoweredSubsystem-derived).
|
||||
int auxScreen = 0, placement = -1;
|
||||
char labelBuf[64];
|
||||
if (!BTGetSubsystemAuxScreen(sub, &auxScreen, &placement, labelBuf)) // FUN_0041a1a4 + sub[0x1dc]/[0x1e4]
|
||||
continue;
|
||||
|
||||
int group = *(int *)((char *)sub + 0x1dc) - 1; // aux-screen position 1..12
|
||||
int group = auxScreen - 1; // aux-screen position 1..12
|
||||
if (getenv("BT_VSS_LOG"))
|
||||
DEBUG_STREAM << "[vss] i=" << i << " classID=" << (int)sub->GetClassID()
|
||||
<< " auxScreen[0x1dc]=" << (group + 1) << "\n" << std::flush;
|
||||
<< " auxScreen=" << auxScreen << " placement=" << placement
|
||||
<< " label='" << labelBuf << "'\n" << std::flush;
|
||||
if (group < 0)
|
||||
{
|
||||
DebugStream << "Auxiliary screen position = zero\n";
|
||||
@@ -1400,7 +1453,7 @@ Logical
|
||||
|
||||
const AuxScreenGeometry &g = kAuxGeom[group];
|
||||
int mfdPort = renderer->FindGraphicsPort((char *)g.mfdPortName); // FUN_00447f1c
|
||||
char *label = (char *)((char *)sub + 0x1e4); // the subsystem label
|
||||
char *label = labelBuf; // the subsystem label (bridge)
|
||||
char *tile = (char *)"btwarn.pcc";
|
||||
|
||||
switch (sub->GetClassID()) // sub+4
|
||||
|
||||
@@ -1501,6 +1501,228 @@ OneOfSeveralPixInt::~OneOfSeveralPixInt()
|
||||
}
|
||||
|
||||
|
||||
//###########################################################################
|
||||
// Additional composite-cluster child gauges (used by btl4gau2's
|
||||
// SubsystemCluster family). HorizTwoPartBar mirrors VertTwoPartBar
|
||||
// (horizontal); OneOfSeveralInt/States are OneOfSeveral subclasses; the
|
||||
// LeakGauge base BitMapInverseWipe reveals a bitmap up to a level.
|
||||
//###########################################################################
|
||||
|
||||
//
|
||||
// StateConnection -- @004c3324 ctor / @004c3390 Transfer. Copies a Subsystem's
|
||||
// state word (@0x14) into the destination each frame (drives OneOfSeveralStates).
|
||||
//
|
||||
class StateConnection : public GaugeConnection
|
||||
{
|
||||
public:
|
||||
StateConnection(int *destination, Entity *source)
|
||||
: GaugeConnection(0), source(source), destination(destination) {} // FUN_004c3324
|
||||
void Update() // @004c3390
|
||||
{
|
||||
*destination = *(int *)((char *)source + 0x14); // Subsystem state @0x14
|
||||
}
|
||||
protected:
|
||||
Entity *source; // @0x10
|
||||
int *destination; // @0x14
|
||||
};
|
||||
|
||||
//
|
||||
// @004c4170 -- HorizTwoPartBar ctor (vtable 0x518cbc). GraphicGauge base, intern
|
||||
// the tile bitmap, SetExtent, store fill/bg + width/height, wire the value/low/
|
||||
// high Scalar connections. Grows left->right (mirror of VertTwoPartBar).
|
||||
//
|
||||
HorizTwoPartBar::HorizTwoPartBar(
|
||||
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 fill_color, int background_color,
|
||||
Scalar *value_pointer, Scalar *low_pointer, Scalar *high_pointer,
|
||||
const char *identification_string
|
||||
):
|
||||
GraphicGauge(rate, mode_mask, renderer_in, 0, graphics_port_number, // FUN_00444818
|
||||
identification_string)
|
||||
{
|
||||
tileImage = new char[strlen(tile_image) + 1];
|
||||
strcpy(tileImage, tile_image);
|
||||
L4Warehouse *warehouse = (L4Warehouse *)renderer_in->warehousePointer;
|
||||
warehouse->bitMapBin.Get(tileImage);
|
||||
localView.SetPositionWithinPort(left, bottom, right, top); // vtbl+0x08
|
||||
fillColor = fill_color;
|
||||
backgroundColor = background_color;
|
||||
width = right - left;
|
||||
height = (top - bottom) - 1;
|
||||
AddConnection(new GaugeConnectionDirectOf<Scalar>(0, &value, value_pointer));
|
||||
AddConnection(new GaugeConnectionDirectOf<Scalar>(0, &low, low_pointer));
|
||||
AddConnection(new GaugeConnectionDirectOf<Scalar>(0, &high, high_pointer));
|
||||
}
|
||||
|
||||
HorizTwoPartBar::~HorizTwoPartBar()
|
||||
{
|
||||
L4Warehouse *warehouse = (L4Warehouse *)renderer->warehousePointer;
|
||||
warehouse->bitMapBin.Release(tileImage);
|
||||
delete[] tileImage;
|
||||
tileImage = NULL;
|
||||
}
|
||||
|
||||
Logical HorizTwoPartBar::TestInstance() const { return GraphicGauge::TestInstance(); }
|
||||
|
||||
void HorizTwoPartBar::BecameActive() // @004c4324
|
||||
{
|
||||
previousFull = 0;
|
||||
previousFill = width;
|
||||
}
|
||||
|
||||
//
|
||||
// @004c4340 -- Execute: clamp value to [0,high], map value/warn to bar pixels
|
||||
// (half-up round of width*x/high), repaint on change (mirror of VertTwoPartBar
|
||||
// but along X).
|
||||
//
|
||||
void HorizTwoPartBar::Execute()
|
||||
{
|
||||
if (value < 0.0f) value = 0.0f;
|
||||
else if (value > high) value = high;
|
||||
|
||||
int warnPix = (int)((Scalar)width * low / high + 0.5f);
|
||||
int valPix = (int)((Scalar)width * value / high + 0.5f);
|
||||
if (warnPix < 0) warnPix = 0; else if (warnPix > width) warnPix = width;
|
||||
if (valPix < 0) valPix = 0; else if (valPix > width) valPix = width;
|
||||
|
||||
if (warnPix != previousFull || valPix != previousFill)
|
||||
{
|
||||
if (warnPix < valPix)
|
||||
{
|
||||
localView.SetColor(fillColor);
|
||||
localView.MoveToAbsolute(warnPix, 0);
|
||||
localView.DrawFilledRectangleToAbsolute(valPix - 1, height);
|
||||
}
|
||||
if (valPix < width)
|
||||
{
|
||||
localView.SetColor(backgroundColor);
|
||||
localView.MoveToAbsolute(valPix, 0);
|
||||
localView.DrawFilledRectangleToAbsolute(width, height);
|
||||
}
|
||||
previousFull = warnPix;
|
||||
previousFill = valPix;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// @004c5148 -- OneOfSeveralInt ctor (vtable 0x518bac): OneOfSeveral(fromStrip=1) +
|
||||
// a GaugeConnectionDirectOf<int> feeding the selected frame. dtor @004c51d8.
|
||||
//
|
||||
OneOfSeveralInt::OneOfSeveralInt(
|
||||
GaugeRate rate, ModeMask mode_mask, L4GaugeRenderer *renderer_in,
|
||||
int graphics_port_number, int x, int y, const char *image,
|
||||
int columns, int rows, int *value_pointer, const char *identification_string
|
||||
):
|
||||
OneOfSeveral(rate, mode_mask, renderer_in, graphics_port_number, x, y,
|
||||
True, image, 0, 0, columns, rows, identification_string)
|
||||
{
|
||||
selected = 0;
|
||||
AddConnection(new GaugeConnectionDirectOf<int>(0, &selected, value_pointer)); // FUN_004749de
|
||||
}
|
||||
|
||||
OneOfSeveralInt::~OneOfSeveralInt() {} // @004c51d8
|
||||
|
||||
//
|
||||
// @004c5470 -- OneOfSeveralStates ctor (vtable 0x518b24): OneOfSeveral + a
|
||||
// StateConnection reading the subsystem state word @0x14. BecameActive @004c552c
|
||||
// clamps the state >= 0 then chains OneOfSeveral::Execute. dtor @004c5500.
|
||||
//
|
||||
OneOfSeveralStates::OneOfSeveralStates(
|
||||
GaugeRate rate, ModeMask mode_mask, L4GaugeRenderer *renderer_in,
|
||||
int graphics_port_number, int x, int y, const char *image,
|
||||
int columns, int rows, Entity *subsystem_source, const char *identification_string
|
||||
):
|
||||
OneOfSeveral(rate, mode_mask, renderer_in, graphics_port_number, x, y,
|
||||
True, image, 0, 0, columns, rows, identification_string)
|
||||
{
|
||||
selected = 0;
|
||||
AddConnection(new StateConnection(&selected, subsystem_source)); // FUN_004c3324
|
||||
}
|
||||
|
||||
OneOfSeveralStates::~OneOfSeveralStates() {} // @004c5500
|
||||
|
||||
void OneOfSeveralStates::BecameActive() // @004c552c
|
||||
{
|
||||
if (selected < 0)
|
||||
selected = 0;
|
||||
OneOfSeveral::BecameActive();
|
||||
}
|
||||
|
||||
//
|
||||
// @004c5b7c -- BitMapInverseWipe ctor (vtable 0x518a9c): GraphicGauge base;
|
||||
// SetOrigin(x,y), intern the image, store colours + frame geometry (frameWidth =
|
||||
// imageWidth/3, fullWidth = frames*2), wire a Scalar level connection. Used as
|
||||
// the coolant LeakGauge.
|
||||
//
|
||||
BitMapInverseWipe::BitMapInverseWipe(
|
||||
GaugeRate rate, ModeMask mode_mask, L4GaugeRenderer *renderer_in,
|
||||
int graphics_port_number, int x, int y, const char *image,
|
||||
int color_a, int color_b, int third, int frames,
|
||||
Scalar *value_pointer, const char *identification_string
|
||||
):
|
||||
GraphicGauge(rate, mode_mask, renderer_in, 0, graphics_port_number,
|
||||
identification_string)
|
||||
{
|
||||
localView.SetOrigin(x, y); // vtbl+0x10
|
||||
imageName = new char[strlen(image) + 1];
|
||||
strcpy(imageName, image);
|
||||
colorA = color_a;
|
||||
colorB = color_b;
|
||||
this->frames = frames;
|
||||
fullWidth = frames * 2;
|
||||
this->third = third;
|
||||
L4Warehouse *warehouse = (L4Warehouse *)renderer_in->warehousePointer;
|
||||
BitMap *bmp = warehouse->bitMapBin.Get(imageName);
|
||||
if (bmp == NULL)
|
||||
{
|
||||
frameWidth = 0;
|
||||
frameHeight = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
frameWidth = bmp->Data.Size.x / 3;
|
||||
frameHeight = bmp->Data.Size.y;
|
||||
}
|
||||
AddConnection(new GaugeConnectionDirectOf<Scalar>(0, &value, value_pointer)); // FUN_00474855
|
||||
}
|
||||
|
||||
BitMapInverseWipe::~BitMapInverseWipe() // @004c5c80
|
||||
{
|
||||
L4Warehouse *warehouse = (L4Warehouse *)renderer->warehousePointer;
|
||||
warehouse->bitMapBin.Release(imageName);
|
||||
delete[] imageName;
|
||||
imageName = NULL;
|
||||
}
|
||||
|
||||
Logical BitMapInverseWipe::TestInstance() const { return GraphicGauge::TestInstance(); }
|
||||
|
||||
void BitMapInverseWipe::BecameActive() // @004c5cf4
|
||||
{
|
||||
previousLevel = -1;
|
||||
}
|
||||
|
||||
//
|
||||
// @004c5d08 -- Execute. BRING-UP: the authentic wipe (reveal the bitmap columns
|
||||
// up to the level, coloured by colorA/colorB) needs the frame-strip blit math;
|
||||
// a safe minimal repaint (blit the base frame on level change) keeps the vtable
|
||||
// complete + non-crashing until the full wipe lands.
|
||||
//
|
||||
void BitMapInverseWipe::Execute()
|
||||
{
|
||||
int level = (int)value;
|
||||
if (level != previousLevel)
|
||||
{
|
||||
previousLevel = level;
|
||||
L4Warehouse *warehouse = (L4Warehouse *)renderer->warehousePointer;
|
||||
BitMap *bmp = warehouse->bitMapBin.Get(imageName);
|
||||
if (bmp != NULL)
|
||||
localView.DrawBitMapOpaque(0, 0, bmp);
|
||||
warehouse->bitMapBin.Release(imageName);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//###########################################################################
|
||||
//###########################################################################
|
||||
// HeadingPointer @004c554c Make / @004c562c ctor
|
||||
|
||||
@@ -1258,3 +1258,26 @@ Subsystem *CreatePoweredSubsystem(Mech *owner, int id, void *seg)
|
||||
return (Subsystem *) new (Memory::Allocate(0x31c))
|
||||
PoweredSubsystem(owner, id, (PoweredSubsystem::SubsystemResource *)seg, PoweredSubsystem::DefaultData);
|
||||
}
|
||||
|
||||
//
|
||||
// Bridge for the vehicleSubSystems gauge factory (btl4gau2.cpp). The engineering-
|
||||
// screen assignment lives on PoweredSubsystem (auxScreenNumber / auxScreenPlacement
|
||||
// / auxScreenLabel, resource +0x104/+0x108/+0x10C). Reading it from btl4gau2.cpp
|
||||
// via a raw offset (sub+0x1dc) is wrong because the reconstructed heat-leaf branch
|
||||
// is not byte-exact -- so the gauge factory calls this bridge, which casts through
|
||||
// the real PoweredSubsystem type and returns the NAMED fields. Returns False for
|
||||
// non-PoweredSubsystem-derived subsystems (== the FUN_0041a1a4 / 0x50f4bc type
|
||||
// filter the vehicleSubSystems Make applies before dispatch).
|
||||
//
|
||||
bool BTGetSubsystemAuxScreen(Subsystem *sub, int *screen, int *placement, char *label64)
|
||||
{
|
||||
if (sub == NULL)
|
||||
return false;
|
||||
if (!sub->IsDerivedFrom(*PoweredSubsystem::GetClassDerivations())) // FUN_0041a1a4(...,0x50f4bc)
|
||||
return false;
|
||||
PoweredSubsystem *ps = (PoweredSubsystem *)sub;
|
||||
if (screen != NULL) *screen = ps->auxScreenNumber; // +0x104
|
||||
if (placement != NULL) *placement = ps->auxScreenPlacement; // +0x108
|
||||
if (label64 != NULL) strcpy(label64, ps->auxScreenLabel); // +0x10C
|
||||
return true;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user