Files
BT411/game/reconstructed/mechtech.cpp
T
arcattackandClaude Fable 5 6f5a264835 Gitea #47 COMPLETE: the ENG-button attention FLASH is live (jam / bay fire) -- MechTech status scan + BTL4GaugeAlarmManager + lamp chain, all from the binary
The pod behaviour Cyd described -- "on a jam or bay fire the display eng button
for the system flashes" -- is authored data + a five-stage chain, now running:

  MechTech::TechnicalAssistance (@004ad33c, per frame)
    edge-scans every monitored subsystem's GetStatusFlags() 7-bit condition
    mask (TechStatusType: Destroyed 0, Damaged 1, CoolantLeaking 2,
    Overheating 3, AmmoBurning 4, Jammed 5, BadPower 6)
  -> Start/StopEntityAlarmMessage (@00436688 id 7 size 0x20 / @004366b8 id 8
     size 0x1C; broadcast @004364e4; port shape: direct
     Start/StopEntityAlarmImplementation calls on the gauge renderer)
  -> GaugeAlarmManager::Activate(alarmModel) -- alarmModel = MechTech+0x100 =
     the 'mechalrm' ModelList (id 83) -> SearchList(83, type 31) -> the baked
     GaugeAlarmStream (id 331, 11 items {condition, lampCode}), decoded:
         Destroyed      -> gotoEngineering + engCooling + engBusMode
         CoolantLeaking -> gotoEngineering + engCooling
         AmmoBurning    -> gotoEngineering + engEject
         Jammed         -> gotoEngineering + engEject
         BadPower       -> gotoEngineering + engBusMode
  -> BTL4GaugeAlarmManager::ReadGaugeAlarmStreamItem -- THE REAL BODY, from
     @004cc2fc + helpers @004cc108/148/1a0/264/27c + tables @0051cf1c..0x51d084
     (gotoEngineering 0x80 = the subsystem's QUAD-SELECT bezel button via
     lamp[aux]/mode[aux] tables; 0x81+ descend the eng-page bank; Condenser /
     Generator specials on CoolantLeaking; <0x80 = the heat-bank fixed map).
     btl4galm.cpp's old bodies were admitted fabrications and its provenance
     note ("no override body exists in the image") was wrong -- corrected.
  -> LampManager::FindLamp (@00444c80) -> Lamp::SetAlertState (@00444e64, a
     COUNTER so stacked alarms hold the flash) -> L4Lamp::NotifyOfStateChange
     emits RIO flashFast states 0x37/0x13 (== T0 L4LAMP.cpp:234-239) -> the
     pod's physical lamps AND the glass panels (PadRIO IS rioPointer there).

FOUR load-bearing defects found and fixed en route -- each independently fatal
to the feature:

  1. HeatableSubsystem's Derivation chained Subsystem directly, SKIPPING
     MechSubsystem (written before the WAVE-1 re-basing) -- so EVERY subsystem
     on both family branches failed IsDerivedFrom(MechSubsystem), which is
     exactly MechTech's monitor filter: the status scan watched NOTHING.
  2. MechSubsystem::GetStatusFlags was non-virtual (binary: vtable slot 12) --
     the scan's MechSubsystem* call bound statically to the base tier and the
     weapon bits could never surface.
  3. ProjectileWeapon::GetStatusFlags sat in a Ghidra export gap -- raw-disasm
     @004bbf88: base | 0x20 (weaponAlarm==5 Jammed) | 0x10 (linked bin
     cookOffArmed@0x18C AmmoBurning).  The port had "defer to base".
  4. Mech::Reset's respawn sweep blanket-cast every roster entry to
     MechSubsystem and called RespawnRepair -- wrong for the Subsystem-level
     entries (MechTech 0xBDC, SubsystemMessageManager 0xBD3).  On MechTech the
     recon damageZone slot lands on its subsystemMonitors chain head: NULL
     while the scan was broken (fix #1's bug MASKED this one), but the moment
     the monitors populated, RespawnRepair virtual-called through a
     SubsystemMonitor as if it were a DamageZone -> load-time crash in
     StateIndicator::SetState (caught with cdb; call [edx+14h] on code bytes).
     The sweep now filters IsDerivedFrom(MechSubsystem) before the cast.

Also: GUID identities pinned -- 0x50f4bc = PoweredSubsystem::ClassDerivations
(via @004b1208 = its TestInstance; btl4gau2's two "Generator" comments were
wrong, swept), 0x50fb60 = Generator's.  A shadow-field instance documented for
the deferred de-shadow: MechSubsystem::damageZone re-declares the PUBLIC engine
Subsystem::damageZone (SUBSYSTM.h:159) -- mechtech's naive `sub->damageZone`
read the never-written engine member (0 monitors again); now reads the recon
member via GetDamageZoneProxy().  Logged in open-questions.

Wiring: BTL4GaugeRenderer now constructs + assigns the BTL4GaugeAlarmManager
(the base ctor NULLs it and Activate Check()s it); MechTech's Report*/
StatusMessageSink stubs are real; alarmModel confirmed baked (= 83) at runtime.

VERIFIED LIVE (scratchpad/lampflash.py, BT_LAMP_LOG chain trace):
    [techstat] MechTech id 32 monitors 29 subsystems, alarmModel 83
    [techstat] LRM15_1 condition 4 SET (alarmModel 83)      <- bay fire armed
    [galarm] condition 4 code 0x80 -> lamp 0xd mode 0x1 FLASH
    [lamp] 0xd <- 0x37  (FLASHING)                          <- the select button
    [galarm] condition 4 code 0x85 -> lamp 0xb mode 0x4 FLASH   <- engEject
    [techstat] LRM15_1 condition 4 CLEARED                  <- detonation clears
    [techstat] AmmoBinLRM15_1 condition 0 SET               <- bin Destroyed
Regressions: baytest PASS (bay fire kills), baypurge PASS (purge extinguishes),
sim3 3-pod brawl PASS with ZERO crashes (6 kills, organic heat-route bay fires,
respawns clean through the new sweep filter).

Env: BT_LAMP_LOG ([techstat]/[galarm]/[lamp]).  KB: gauges-hud (the flash
section), decomp-reference (the GaugeAlarm closure + tables + GUIDs),
open-questions (built-note + the de-shadow deferred item), btl4gau2 comment
sweep.  checkctx CLEAN.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 16:04:34 -05:00

569 lines
20 KiB
C++

//===========================================================================//
// File: mechtech.cpp //
// Project: BattleTech Brick: Entity Manager //
// Contents: MechTech -- the mech's subsystem-monitor / diagnostics subsystem //
//---------------------------------------------------------------------------//
// Copyright (C) 1995, Virtual World Entertainment, Inc. All Rights reserved //
// PROPRIETARY AND CONFIDENTIAL //
//===========================================================================//
//
// RECONSTRUCTED from the shipped binary. Behaviour follows the Ghidra
// pseudo-C for the @004ad124-@004ad6c9 cluster; the public interface and the
// member / type names are GROUND TRUTH from the surviving MECHTECH.HPP.
// Each non-trivial method cites its originating @ADDR.
//
// What MechTech does:
// At construction it walks the owning Mech's segment roster and, for every
// damageable MechSubsystem (those flagged with segment-bit 0x8), allocates a
// SubsystemMonitor and threads it onto the `subsystemMonitors` chain. Each
// frame the registered Performance (TechnicalAssistance @004ad33c) iterates
// that chain, samples each watched subsystem's status flags, and -- whenever
// a status bit changes -- posts an "alert" / "clear" message into the
// cockpit status / MFD message sink. Set alerts carry the `alarmModel`
// resource; the per-cell wooHoo timer latches an alert for a duration so the
// readout doesn't flicker.
//
// Binary identity (mech.cpp's switch mislabelled these -- corrected here):
// MechTech == ClassID 0xBDC, ctor @004ad228, size 0x104,
// vtable @0050e3a0.
// (mech.cpp's "MechTech" @004b7f94 / 0xBD6 is actually the HUD
// torso-horizon gimbal display and is NOT this class.)
//
// Helper-function name mapping (engine internals referenced by the decomp):
// FUN_0041c52c Subsystem base constructor
// FUN_0041c684 Subsystem::CreateStreamedSubsystem
// FUN_0041c648 Subsystem destructor
// FUN_0041a1a4 IsDerivedFrom(classDerivations)
// FUN_004022d0 operator delete (global heap)
// FUN_00415e90 Plug constructor FUN_00415ed8 Plug destructor
// FUN_004de998 array-construct N elements (count, stride, ..., elem-init)
// FUN_004ad619 trivial element-init thunk (returns its arg)
// FUN_00402f74 MemoryBlock::New(&block) FUN_00402f98 MemoryBlock::Delete(&block,p)
// FUN_00417be0 / 00417c0c / 00417d00 / 00417d28 / 00417d54
// ChainOf<> / chain-iterator internals (ctor/dtor/iter)
// FUN_004ad621 ChainOf<SubsystemMonitor*> ctor (vtable @0050e398)
// FUN_004ad640 ChainOf<> dtor
// FUN_004ad68b chain iterator ctor (vtable @0050e348)
// FUN_004ad6c9 chain iterator dtor
// FUN_00402f74(0x50e248) ... SubsystemMonitor::operator new
// FUN_00404088 NotationFile::Read(section,key,&string)
// FUN_00406ff8 ResourceFile::ResolveModel(name,...) -> ResourceID*
// FUN_004dbb24 DebugStream << (error reporting)
// FUN_004366b8 build "status cleared" cockpit message
// FUN_00436688 build "status set" cockpit message (carries alarmModel)
// FUN_004364e4 dispatch cockpit/MFD message to the status sink
// DAT_004efc94 the running Application/World; (+0x38) == status sink
//
#include <bt.hpp>
#pragma hdrstop
#if !defined(MECHTECH_HPP)
# include <mechtech.hpp>
#endif
#if !defined(APP_HPP)
# include <app.hpp>
#endif
#if !defined(TESTBT_HPP)
# include <testbt.hpp>
#endif
//###########################################################################
//###########################################################################
// MechTech::SubsystemMonitor
//###########################################################################
//###########################################################################
//#############################################################################
// Memory Allocation Support
//
// Static MemoryBlock @0050e248 (zero-filled in .data, primed at runtime).
//
MemoryBlock
MechTech::SubsystemMonitor::AllocatedMemory(
sizeof(MechTech__SubsystemMonitor), // record size (0x80)
8, // initial record count
8, // growth delta
"MechTech::SubsystemMonitor"
);
//#############################################################################
// Construction / Destruction
//
// @004ad124 -- construct a monitor bound to one watched MechSubsystem.
//
// FUN_00415e90(this,1) -> Plug base ctor
// *this = &PTR_FUN_0050e3d0 -> SubsystemMonitor vtable
// FUN_004de998(this+0xC, 0x10, 7, 1, ...) -> placement-build statusArray[7]
// this[0x7C] = subsystem -> monitoredSubsystem
// loop x7: zero {currentStatus,wooHooed,startTime,wooHooEnd}
//
MechTech__SubsystemMonitor::MechTech__SubsystemMonitor(MechSubsystem *subsystem)
:
Plug()
{
monitoredSubsystem = subsystem;
for (int i = 0; i < MechSubsystem::TechStatusTypeCount; ++i) // 7
{
statusArray[i].currentStatus = 0;
statusArray[i].wooHooed = 0;
statusArray[i].startTime = 0L; // Time::operator=(long)
statusArray[i].wooHooEnd = 0L; // Time::operator=(long)
}
}
//
// @004ad180 -- destructor + deleting-destructor thunk. Re-seats the vtable,
// runs the Plug base teardown (FUN_00415ed8) and, when invoked as the
// deleting destructor (flag bit 0), returns the storage to the MemoryBlock.
//
MechTech__SubsystemMonitor::~MechTech__SubsystemMonitor()
{
// Plug base + AllocatedMemory.Delete(this) handled by the generated
// deleting-destructor thunk (FUN_00402f98(&AllocatedMemory, this)).
}
//###########################################################################
//###########################################################################
// MechTech
//###########################################################################
//###########################################################################
//#############################################################################
// Shared Data Support
//
// DefaultData @0050e270 ClassDerivations @0050e280
//
Derivation
MechTech::ClassDerivations(
Subsystem::GetClassDerivations(), // MUNGA Subsystem exposes a Derivation* getter
"MechTech"
);
Receiver::MessageHandlerSet
MechTech::MessageHandlers;
MechTech::AttributeIndexSet
MechTech::AttributeIndex;
MechTech::SharedData
MechTech::DefaultData(
&MechTech::ClassDerivations,
MechTech::MessageHandlers,
MechTech::AttributeIndex,
MechTech::StateCount
);
//#############################################################################
// Construction / Destruction
//
// @004ad228 (mech.cpp allocates 0x104 bytes and calls this for ClassID 0xBDC).
//
// FUN_0041c52c(...) -> Subsystem base ctor
// *this = &PTR_FUN_0050e3a0 -> MechTech vtable
// FUN_004ad621(this+0xE4, 0) -> subsystemMonitors chain ctor
//
// A live (non-copy / non-destroyed) instance -- (entity flags & 0xC) == 0 --
// installs the per-frame Performance and builds its monitor roster; a copy
// just flags itself dormant (this[10] |= 2).
//
MechTech::MechTech(
Mech *entity,
int subsystem_id,
SubsystemResource *model,
SharedData &shared_data
):
Subsystem(entity, subsystem_id, model, shared_data),
subsystemMonitors(0) // FUN_004ad621(this+0xE4, 0) -- Chain(Node* == 0)
{
Check(entity);
Check_Pointer(model);
segmentFlags = 0;
// (entity+0x28 & 0xC) == 0 -- owner is a live primary, not a copy/template.
// CROSS-FAMILY: the original reads the owning Mech's per-segment flags
// (Mech::GetSegmentFlags); mapped here to the entity's simulationFlags.
if ((entity->simulationFlags & SegmentCopyMask) == 0)
{
SetPerformance(&MechTech::TechnicalAssistance); // this[7..9] = {&TechnicalAssistance,0,0} @0050e2b0
alarmModel = model->alarmModel; // res +0x30 -> this+0x100
wooHooMinimumDuration = model->wooHooMinimumDuration; // res +0x34 -> this+0xF4
wooHooDurationRange = model->wooHooDurationRange; // res +0x38 -> this+0xF8
wooHooChance = model->wooHooChance; // res +0x3C -> this+0xFC
//
// Walk the owner's subsystem roster and attach a monitor to every
// damageable MechSubsystem. (Binary walked a Mech-specific segment
// table at owner+0x124/+0x128 whose entries are these subsystem
// objects; the engine exposes the same roster via Entity::GetSubsystem.)
//
for (int seg = entity->GetSubsystemCount() - 1; seg >= 0; --seg)
{
Subsystem *sub = entity->GetSubsystem(seg);
// #47 SHADOW-FIELD TRAP (gotcha #1): the naive `sub->damageZone`
// resolves to the PUBLIC engine Subsystem::damageZone
// (SUBSYSTM.h:159) -- which the reconstruction NEVER WRITES; the
// recon zone lives on the re-declared MechSubsystem member @0xE0.
// Testing the engine one rejected every subsystem (0 monitors).
// Read the recon member through its accessor. (The re-declaration
// itself is logged in open-questions as a deferred de-shadow.)
if (
sub != 0
&& sub->IsDerivedFrom(MechSubsystem::ClassDerivations) // FUN_0041a1a4(**sub[3], 0x50de2c)
&& ((MechSubsystem *)sub)->GetDamageZoneProxy() != 0 // damageable segment (segment bit 0x8)
)
{
SubsystemMonitor *monitor =
new SubsystemMonitor((MechSubsystem *)sub); // MemoryBlock @0050e248
subsystemMonitors.Add(monitor); // (*this[0x39].vt[1])(...)
}
}
// #47 diag: how many subsystems the status scan actually watches, and
// which alarm table it will Activate with. A monitor count of ~2 here
// betrays the roster-order hazard (the ctor walk sees only the slots
// built BEFORE MechTech in the stream).
if (getenv("BT_LAMP_LOG"))
{
int n = 0;
ChainIteratorOf<SubsystemMonitor*> it(&subsystemMonitors);
for (it.First(); it.GetCurrent() != 0; it.Next()) ++n;
DEBUG_STREAM << "[techstat] MechTech id " << subsystem_id
<< " monitors " << n << " subsystems (roster count "
<< entity->GetSubsystemCount() << "), alarmModel "
<< alarmModel << "\n" << std::flush;
}
}
else
{
segmentFlags |= DormantInstanceFlag; // this[10] |= 2 -- dormant (copy / template instance)
}
Check_Fpu();
}
//
// @004ad1b8 -- destructor + deleting-destructor thunk.
//
// *this = &PTR_FUN_0050e3a0
// FUN_004ad68b/FUN_004ad6c9 ... walk + tear down the monitor chain,
// FUN_004ad640(this+0xE4, 2) -> subsystemMonitors dtor,
// FUN_0041c648(this, 0) -> ~Subsystem.
//
MechTech::~MechTech()
{
Check(this);
//
// Release every SubsystemMonitor still on the chain, then the chain
// itself; ~Subsystem finishes the base teardown.
//
ChainIteratorOf<SubsystemMonitor*> iterator(&subsystemMonitors);
for (
iterator.First();
SubsystemMonitor *monitor = iterator.GetCurrent();
/* Remove() re-seats currentLink */
)
{
iterator.Remove();
delete monitor;
}
Check_Fpu();
}
//###########################################################################
// TestInstance -- MechTech
//
// @004ad470 -> FUN_0041a1a4(**this[3], 0x50e280)
//
Logical
MechTech::TestInstance() const
{
return IsDerivedFrom(ClassDerivations);
}
//#############################################################################
// Per-frame Performance
//
//
// @004ad33c -- TechnicalAssistance: poll every monitored subsystem's status
// flags and emit cockpit/MFD status messages on change.
//
// Decomp shape:
// sink = *(DAT_004efc94 + 0x38); // the status / MFD message sink
// for (monitor in subsystemMonitors) // iterator FUN_004ad68b
// {
// sub = monitor->monitoredSubsystem; // monitor[0x7C]
// flags = sub->GetStatusFlags(); // vtable slot @+0x30
// for (type = 0; type < 7; ++type) // TechStatusTypeCount
// {
// bit = flags & 1;
// cell = &monitor->statusArray[type];
// if (cell->wooHooed) // an alert is latched
// {
// if (cell->wooHooEnd < GetTime()) cell->wooHooed = 0; // expired
// else bit = cell->currentStatus; // hold
// }
// if (bit != cell->currentStatus) // status changed -> report
// {
// Mech *owner = GetEntity();
// if (bit == 0) // cleared
// FUN_004364e4(sink, owner, BuildClear(owner, sub, type));
// else // set (carries alarmModel)
// FUN_004364e4(sink, owner, BuildSet(owner, sub, type, alarmModel));
// }
// cell->currentStatus = bit;
// flags >>= 1;
// }
// }
//
// NOTE (best-effort): the actual latch -- setting cell->wooHooed /
// cell->wooHooEnd from wooHooChance / wooHooMinimumDuration /
// wooHooDurationRange -- lives inside the message-builder helpers
// (FUN_00436688 et al.), which are part of the HUD/message brick and were
// not captured in this window. TechnicalAssistance only *clears* expired
// latches and detects edges.
//
void
MechTech::TechnicalAssistance(Scalar /*time_slice*/)
{
Check(this);
Mech *owner = GetEntity();
void *sink = StatusMessageSink(); // *(DAT_004efc94 + 0x38)
ChainIteratorOf<SubsystemMonitor*> it(&subsystemMonitors); // FUN_004ad68b
SubsystemMonitor *monitor;
for (
it.First();
(monitor = it.GetCurrent()) != 0;
it.Next()
)
{
MechSubsystem *sub = monitor->monitoredSubsystem;
LWord flags = sub->GetStatusFlags(); // vt[+0x30]
for (int type = 0; type < MechSubsystem::TechStatusTypeCount; ++type)
{
int bit = (int)(flags & 1);
StatusInfo &cell = monitor->statusArray[type];
if (cell.wooHooed != 0)
{
if (cell.wooHooEnd.ticks < GetTime().ticks) // this+0x10 == current time (integer tick compare)
{
cell.wooHooed = 0; // latch expired
}
else
{
bit = cell.currentStatus; // hold the latched state
}
}
if (bit != cell.currentStatus)
{
if (bit == 0)
{
ReportStatusCleared(sink, owner, sub, type); // FUN_004366b8 + FUN_004364e4
}
else
{
ReportStatusSet(sink, owner, sub, type, alarmModel);// FUN_00436688 + FUN_004364e4
}
}
cell.currentStatus = bit;
flags >>= 1;
}
}
Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// CreateStreamedSubsystem -- MechTech
//
// @004ad48c
//
// FUN_0041c684(...) -> Subsystem::CreateStreamedSubsystem
// resource->subsystemModelSize = 0x40 (res[0x24] = 0x40)
// resource->classID = 0xBDC (res[0x20] = 0x0BDC)
// resource->alarmModel = -1 (res+0x30)
// resource->wooHoo{Min,Range,Chance} = 0 (res+0x34,+0x38,+0x3C)
//
// "AlarmModel" is optional: if absent (and alarmModel still -1) the parse
// succeeds with no alarm. If present, the named model is resolved against
// the resource file; failure reports "<name> couldn't locate <name>.mod".
//
Logical
MechTech::CreateStreamedSubsystem(
NotationFile *model_file,
const char *model_name,
const char *subsystem_name,
SubsystemResource *subsystem_resource,
NotationFile *subsystem_file,
const ResourceDirectories *directories,
ResourceFile *resource_file
)
{
if (
!Subsystem::CreateStreamedSubsystem( // FUN_0041c684
model_file, model_name, subsystem_name,
subsystem_resource, subsystem_file, directories
)
)
{
return False;
}
subsystem_resource->subsystemModelSize = sizeof(*subsystem_resource); // 0x40
subsystem_resource->classID = RegisteredClass::MechTechClassID; // 0xBDC
subsystem_resource->alarmModel = -1;
subsystem_resource->wooHooMinimumDuration = 0.0f;
subsystem_resource->wooHooDurationRange = 0.0f;
subsystem_resource->wooHooChance = 0.0f;
//
// "AlarmModel" -- optional model name resolved to a resource id.
//
const char *alarmModelName = 0;
if (
subsystem_resource->alarmModel == -1
&& !subsystem_file->GetEntry(subsystem_name, "AlarmModel", &alarmModelName) // FUN_00404088(param_5,param_3,"AlarmModel",&local_8)
)
{
return True; // no alarm model specified -- fine
}
//
// Resolve the named model to a resource id. The binary calls
// FUN_00406ff8(resource_file, name, 1, -1) ("ResolveModel"); the engine
// ResourceFile exposes FindResourceDescription instead.
// TODO(verify): confirm the resource type used for the alarm .mod.
//
ResourceDescription *description =
resource_file->FindResourceDescription(
alarmModelName,
ResourceDescription::GameModelResourceType
);
if (description == 0)
{
// @004ad48c: "<subsystem_name> couldn't locate <name>.mod"
// (original logged via DebugStream / FUN_004dbb24 -- HUD/diag brick).
return False;
}
subsystem_resource->alarmModel = description->resourceID;
Check_Fpu();
return True;
}
//#############################################################################
// Status reporting support
//
// CROSS-FAMILY stubs. The shipped code dispatches cockpit/MFD status messages
// through the running Application/World status sink (DAT_004efc94 + 0x38) using
// the HUD message builders FUN_004366b8 ("status cleared") / FUN_00436688
// ("status set", carries alarmModel) and dispatcher FUN_004364e4. Those live
// in the cockpit-message brick (btl4gaug / app), not reconstructed here, so the
// bodies are stubs that satisfy the call sites in TechnicalAssistance.
//
//
// @0050e248-era global: the status / MFD message sink == *(DAT_004efc94 + 0x38)
// = the RENDERER MANAGER whose renderer chain (+0x10) FUN_004364e4 broadcasts
// Start/StopEntityAlarm messages to (ids 7/8, sizes 0x20/0x1C).
//
// PORT SHAPE (Gitea #47): the port has no renderer-manager broadcast -- the only
// alarm consumer is the gauge renderer (Renderer::Start/StopEntityAlarmMessage-
// Handler just forwards to Start/StopEntityAlarmImplementation, RENDERER.cpp:574/
// 601, both T0) -- so the sink is the gauge renderer and Report* below call the
// implementations directly. Faithful semantics, direct delivery (the same shape
// the port uses for StartEntityEffect).
//
void*
MechTech::StatusMessageSink()
{
return (application != 0) ? (void *)application->GetGaugeRenderer() : 0;
}
//
// Engine current time (binary read this+0x10; the WinTesla clock is Now()).
//
Time
MechTech::GetTime() const
{
return Now();
}
//
// FUN_004366b8 = Renderer__StopEntityAlarmMessage ctor (id 8, size 0x1C) +
// FUN_004364e4 = the renderer broadcast. Port shape: direct implementation
// call on the gauge renderer (see StatusMessageSink). [Gitea #47]
//
void
MechTech::ReportStatusCleared(
void *sink,
Mech *owner,
MechSubsystem *sub,
int type
)
{
if (getenv("BT_LAMP_LOG"))
DEBUG_STREAM << "[techstat] " << (sub ? sub->GetName() : "?")
<< " condition " << type << " CLEARED\n" << std::flush;
if (sink != 0)
((GaugeRenderer *)sink)->StopEntityAlarmImplementation(
owner, sub, (Enumeration)type);
}
//
// FUN_00436688 = Renderer__StartEntityAlarmMessage ctor (id 7, size 0x20:
// {msg 0xC | EntityID 8 | subsystem | condition | resourceID}) + FUN_004364e4.
// resourceID = MechTech's alarmModel (this+0x100) -- the 'mechalrm' ModelList
// (id 83) whose list resolves to the type-31 GaugeAlarmStream (id 331) inside
// GaugeAlarmManager::Activate's SearchList. [Gitea #47]
//
void
MechTech::ReportStatusSet(
void *sink,
Mech *owner,
MechSubsystem *sub,
int type,
ResourceDescription::ResourceID alarm_model
)
{
if (getenv("BT_LAMP_LOG"))
DEBUG_STREAM << "[techstat] " << (sub ? sub->GetName() : "?")
<< " condition " << type << " SET (alarmModel " << alarm_model
<< ")\n" << std::flush;
if (sink != 0)
((GaugeRenderer *)sink)->StartEntityAlarmImplementation(
owner, sub, (Enumeration)type, alarm_model);
}
//===========================================================================//
// WAVE 2 factory bridge -- MechTech (factory case 0xBDC, "HeatableSubsystem"
// label; the case currently builds the heat base + discards the pointer).
//===========================================================================//
Subsystem *CreateMechTechSubsystem(Mech *owner, int id, void *seg)
{
Check(sizeof(MechTech) <= 0x140);
return (Subsystem *) new (Memory::Allocate(0x140))
MechTech(owner, id, (MechTech::SubsystemResource *)seg, MechTech::DefaultData);
}