diff --git a/game/reconstructed/btl4gau3.cpp b/game/reconstructed/btl4gau3.cpp index d14ce3f..6708921 100644 --- a/game/reconstructed/btl4gau3.cpp +++ b/game/reconstructed/btl4gau3.cpp @@ -882,19 +882,172 @@ void // MessageBoard @004cb678 Make / @004cb704 ctor //########################################################################### // -// @004cb678 -- Make: allocate (0xa4) and construct "MessageBoard" from -// DAT_0051b43c..0051b508; verify the strip image exists (BitMapCache). -// @004cb704 -- ctor (vtable PTR_FUN_0051bddc). GraphicGauge base; interns + -// AddRefs the message strip bitmap (this[0x25]); sets the port origin; color = -// param_10 (this[0x26]). -// @004cb788 -- dtor: release the strip, free the name, base dtor. -// @004cb7fc -- BecameActive: previousNameId = -1; previousMessageId = -2. -// @004cb82c -- Execute. Track the local player's mech (this[0x24]). Read its -// controller message ids (mech+0x190 controller, +0xc message id, +0x10 name -// id). When the message id changes, blit the corresponding 0x80x0x20 cell of -// the strip ((id&3)<<7, (id>>2)<<5) tinted by FUN_004c2f88 (=7); -1 clears. -// When the name id changes, blit the player's name bitmap (App+0xc8 palette -// cache lookup) similarly. +// Resolve the tracked mech's current HUD status message (msg strip cell + sender +// name bitmap). DEFERRED: StatusMessagePool (btstubs.cpp:62) is a NULL stub + +// BTPlayer+0x1dc is never populated, so this returns False today -> the board stays +// empty. Lives in btplayer.cpp (a complete-BTPlayer TU) to dodge the databinding +// trap; MUST have a real (stub) definition or the /FORCE link AVs the call. +extern Logical BTResolveMessageBoard(Entity *tracked_mech, int *messageId, BitMap **nameBitmap); + +// Overflow lock only (like SectorDisplay/Sensor: the 2007 GraphicGauge base is not +// byte-identical to 1995, so exact-offset asserts would fail; every read here is via +// named members). Binary offsets: trackedMech@0x90 stripImage@0x94 color@0x98 +// previousNameId@0x9C previousMessageId@0xA0, sizeof 0xA4 == Make alloc. +struct MessageBoardLayoutCheck { + static_assert(sizeof(MessageBoard) <= 0xA4, "MessageBoard must fit its 0xA4 Make alloc"); +}; + +// 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. Alloc 0xa4 + construct; presence-check the strip bitmap +// (binary returns whether it exists). The `entity` param is ignored by the binary. +// +Logical + MessageBoard::Make( + int display_port_index, + Vector2DOf position, + Entity * /*entity -- ignored by FUN_004cb678*/, + GaugeRenderer *gauge_renderer + ) +{ + ParameterDescription *p = methodDescription.parameterList; + + MessageBoard *gauge = (MessageBoard *)operator new(0xa4); // FUN_00402298(0xa4) + if (gauge != NULL) + new (gauge) MessageBoard( + p[0].data.rate, p[1].data.modeMask, + (L4GaugeRenderer *)gauge_renderer, + display_port_index, // graphics_port_number + position.x, position.y, // offset=(113,607) -> SetOrigin + p[2].data.string, // strip image (btsmsgs.pcx) + p[3].data.color, // color (0) + "MessageBoard"); + + L4Warehouse *wh = (L4Warehouse *)gauge_renderer->warehousePointer; // renderer+0x4c + BitMap *strip = wh->bitMapBin.Get(p[2].data.string); // FUN_00442aec + if (strip == NULL) + return False; + wh->bitMapBin.Release(p[2].data.string); // FUN_00442c12 + return True; +} + +// +// @004cb704 -- ctor (vtable PTR_FUN_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_in, + int graphics_port_number, int x, int y, + const char *strip_image, int color_in, const char *identification_string) + : GraphicGauge(rate, mode_mask, renderer_in, 0, // FUN_00444818 (owner 0) + graphics_port_number, identification_string) +{ + stripImage = new char[strlen(strip_image) + 1]; // nameCopy (FUN_004700ac) + strcpy(stripImage, strip_image); + L4Warehouse *wh = (L4Warehouse *)renderer_in->warehousePointer; + wh->bitMapBin.Get(stripImage); // FUN_00442aec (hold for life) + + localView.SetOrigin(x, y); // this+0x48 vtbl+0x10 + color = color_in; // this[0x26] + trackedMech = NULL; // this[0x24] (:3864) + // previousNameId / previousMessageId set by BecameActive (byte-faithful). +} + +// +// @004cb788 -- dtor. Release + free the strip; base dtor runs implicitly. +// +MessageBoard::~MessageBoard() +{ + L4Warehouse *wh = (L4Warehouse *)renderer->warehousePointer; + wh->bitMapBin.Release(stripImage); // FUN_00442c12 + delete[] stripImage; stripImage = NULL; // FUN_004022e8 +} + +// @004cb7e4 -- TestInstance: out-of-line forward (so it isn't /FORCE-stubbed). +Logical MessageBoard::TestInstance() const { return GraphicGauge::TestInstance(); } + +// @004cb818 -- SetSource: bind the source mech. (No recovered caller -> deferred.) +void MessageBoard::SetSource(Entity *mech) { trackedMech = mech; } + +// @004cb7fc -- BecameActive: reset the change-sentinels. NON-inactivating. +void + MessageBoard::BecameActive() +{ + previousNameId = -1; // this[0x27] @0x9c (:3916) + previousMessageId = -2; // this[0x28] @0xa0 (:3917) +} + +// +// @004cb82c -- Execute. Draw the message strip cell + sender name when they change. +// FAITHFUL: the board only draws while a source mech is bound; the binder (SetSource) +// has no recovered caller AND the per-player status feed (StatusMessagePool) is a NULL +// stub, so trackedMech stays NULL and Execute is a safe no-op == the DEFERRED empty board. +// +void + MessageBoard::Execute() +{ + if (trackedMech == NULL) // binary gate :3949 + return; + + // Message record via the safe bridge (NOT raw *(mech+0x190)/+0x1dc offsets). + int messageId = -1; + BitMap *nameBitmap = NULL; + BTResolveMessageBoard(trackedMech, &messageId, &nameBitmap); + + // --- message strip cell (:3960-3981) --- + if (messageId != previousMessageId) + { + previousMessageId = messageId; + localView.MoveToAbsolute(0, 0); // vtbl+0x24 + if (messageId == -1) { + localView.SetColor(color); // vtbl+0x18 + localView.DrawFilledRectangleToRelative(0x80, 0x20); // vtbl+0x4c + } else { + int cellX = (messageId & 3) << 7; // :3969 + int cellY = (messageId >> 2) << 5; // :3970 + localView.SetColor(7); // FUN_004c2f88()==7 + L4Warehouse *wh = (L4Warehouse *)renderer->warehousePointer; + BitMap *strip = wh->bitMapBin.Get(stripImage); // FUN_00442aec + if (strip != NULL) + localView.DrawBitMapOpaque(color, 0, strip, // vtbl+0x58 + cellX, cellY, cellX + 0x7f, cellY + 0x1f); + wh->bitMapBin.Release(stripImage); // FUN_00442c12 + } + } + + // --- sender name cell (:3983-4009). STRUCTURAL SIMPLIFICATION (marked): the binary + // tracks the previous name ENTITY pointer at 0x9c and erases it via +0x1e0->App+0xc8; + // here previousNameId is a bool (0/1). Functionally identical while DEFERRED (name + // always NULL); RESTORE the entity-pointer tracking + erase branch when the feed goes live. + int nameState = (nameBitmap != NULL) ? 1 : 0; + if (nameState != previousNameId) + { + localView.MoveToAbsolute(0x80, 0); // vtbl+0x24 (name cell x=128) + if (nameBitmap == NULL) { + localView.SetColor(color); // vtbl+0x18 + localView.DrawFilledRectangleToRelative(0x80, 0x20); // vtbl+0x4c + } else { + localView.SetColor(7); + localView.DrawBitMapOpaque(color, 0, nameBitmap, 0, 0, // vtbl+0x58 + nameBitmap->Data.Size.x - 1, nameBitmap->Data.Size.y - 1); + } + previousNameId = nameState; // :4009 + } +} // === btl4grnd.cpp begins at @004cbea0 (BTL4GaugeRenderer ctor) -- not this TU. diff --git a/game/reconstructed/btl4gau3.hpp b/game/reconstructed/btl4gau3.hpp index c75758c..d3159d9 100644 --- a/game/reconstructed/btl4gau3.hpp +++ b/game/reconstructed/btl4gau3.hpp @@ -260,31 +260,40 @@ //####################################################################### - // MessageBoard -- the rank / message bitmap board. @004cb678 Make / - // @004cb704 ctor / @004cb788 dtor / @004cb7fc BecameActive / @004cb82c - // Execute. vtable PTR_FUN_0051bddc. Tracks the local player's mech - // (App+0x6c -> +0x190 controller +0xc/+0x10 message ids) and blits the - // message strip cell ((id&3)<<7, (id>>2)<<5) plus a player name bitmap. + // MessageBoard (config keyword "messageBoard") -- the secondary-MFD comm/status + // message ticker. @004cb678 Make / @004cb704 ctor / @004cb788 dtor / + // @004cb7fc BecameActive / @004cb82c Execute. vtable PTR_FUN_0051bddc. + // Blits one cell of the strip bitmap btsmsgs.pcx (message id -> cell (id&3)<<7, + // (id>>2)<<5) + the sender's name bitmap. Was PROSE-ONLY (no bodies/ + // methodDescription) -> "messageBoard" was parse-skipped. + // + // DEFERRED / EMPTY (authentic for bring-up): the source mech is never bound + // (SetSource @004cb818 has no caller) AND the per-player status queue + // (StatusMessagePool, btstubs.cpp:62) is a NULL stub, so there are no messages + // -> Execute early-returns on the NULL source (a safe no-op == the empty board). //####################################################################### class MessageBoard : public GraphicGauge { public: + static MethodDescription methodDescription; // "messageBoard" static Logical Make(int, Vector2DOf, Entity *, GaugeRenderer *); // @004cb678 MessageBoard( // @004cb704 - GaugeRate, ModeMask, L4GaugeRenderer *, int, + GaugeRate, ModeMask, L4GaugeRenderer *, int graphics_port_number, int x, int y, const char *strip_image, int color, const char *identification_string); ~MessageBoard(); // @004cb788 Logical TestInstance() const; void BecameActive(); // @004cb7fc + void SetSource(Entity *); // @004cb818 (binds trackedMech; no caller -> deferred) void Execute(); // @004cb82c protected: - int enabled; // @0x90 this[0x24] - char *stripImage; // @0x94 this[0x25] - int color; // @0x98 this[0x26] - int previousMessageId; // @0x9C this[0x27] - int previousNameId; // @0xA0 this[0x28] + Entity *trackedMech; // @0x90 this[0x24] source mech (SetSource; ctor=0) (FIX: was int enabled) + char *stripImage; // @0x94 this[0x25] interned btsmsgs.pcx (held ref) + int color; // @0x98 this[0x26] param (CFG 4th arg = 0) + int previousNameId; // @0x9C this[0x27] BecameActive=-1 (FIX: was previousMessageId) + int previousMessageId; // @0xA0 this[0x28] BecameActive=-2 (FIX: was previousNameId) + friend struct MessageBoardLayoutCheck; }; diff --git a/game/reconstructed/btl4grnd.cpp b/game/reconstructed/btl4grnd.cpp index 3dfb806..f80cc0e 100644 --- a/game/reconstructed/btl4grnd.cpp +++ b/game/reconstructed/btl4grnd.cpp @@ -149,6 +149,7 @@ MethodDescription &GeneratorCluster::methodDescription, // "GeneratorCluster" -- the 4 generator engineering panels (buttons 9-12) &SectorDisplay::methodDescription, // "sectorDisplay" -- radar SECTOR X/Z read-out (Secondary overlay) &PrepEngrScreen::methodDescription, // "prepEngr" -- per-Eng-screen static label overlay (12x) + &MessageBoard::methodDescription, // "messageBoard" -- sec-MFD comm/status ticker (DEFERRED source -> empty) &BTL4ChainToPrevious }; diff --git a/game/reconstructed/btplayer.cpp b/game/reconstructed/btplayer.cpp index 42ac51a..20adfcb 100644 --- a/game/reconstructed/btplayer.cpp +++ b/game/reconstructed/btplayer.cpp @@ -1118,6 +1118,21 @@ int BTPilotIsSelected(void *pilot, void *local_player) return ((void *)((BTPlayer *)pilot) == (void *)target->GetPlayerLink()) ? 1 : 0; } +// MessageBoard data bridge (consumed by btl4gau3.cpp MessageBoard::Execute). DEFERRED: +// the per-player status queue (StatusMessagePool, btstubs.cpp:62) is a NULL stub and +// BTPlayer+0x1dc is never populated, so there are no messages -> return False (empty +// board). Real impl (when StatusMessagePool is wired): owningPlayer (mech+0x190) -> +// status record (+0x1dc) -> {messageId@+0xc, nameEntity@+0x10 -> name bitmap} via the +// compiled BTPlayer/Player__StatusMessage members (decode AddStatusMessage @0042e580). +// A real (stub) definition is REQUIRED or the /FORCE link AVs MessageBoard::Execute's call. +class BitMap; +Logical BTResolveMessageBoard(Entity * /*tracked_mech*/, int *messageId, BitMap **nameBitmap) +{ + *messageId = -1; + *nameBitmap = 0; + return False; +} + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // gauge scoring wave: PRODUCERS -- posted by the combat path (mech4.cpp) to feed // the scoreboard. The inflictor is always the viewpoint mech in bring-up, so the diff --git a/scratchpad/msg_gauge.png b/scratchpad/msg_gauge.png new file mode 100644 index 0000000..f2ab031 Binary files /dev/null and b/scratchpad/msg_gauge.png differ