Files
TeslaRel410/restoration/source410/BT/MECHSUB.CPP
T
CydandClaude Fable 5 4168f1ad4d BT410 5.3.47: the whole authored watcher set is published -- cockpit renders in the pod
Ladder rungs, each run-verified on the pod rig: ReportLeak (HeatSink),
GeneratorOn (Generator -- member existed, publication missing),
ConfigureActivePress (MechSubsystem -- likewise), AmmoState +
FireCountdownStarted + the rest of the AmmoBin table.

THE PINNED RANGE IS NOW THE AUTHENTIC SHAPE.  Publishing ReportLeak on
HeatSink and ConfigureActivePress on MechSubsystem shifts the chain down two,
and MechWeapon's bridge to its binary-pinned PercentDone (0x12) collapses
from THREE guessed pads to ONE -- which is exactly the arithmetic the shipped
string pool predicts (MechSubsystem 1, HeatableSubsystem 3, HeatSink 6,
PoweredSubsystem 5).  Three pads of guesswork replaced by a real attribute
and a real base-class row.  HeatableSubsystem and Torso rebase onto
MechSubsystem::AttributeIndex; MechControlsMapper does not (it derives from
Subsystem directly).

Types were chosen deliberately this time, per the AudioWatcher families the
engine instantiates (Motion / Hinge / Scalar / StateIndicator): every *State
name resolves to a StateIndicator, ReportLeak is a Scalar.

RESULT: the pod run now gets past every authored watcher and DRAWS THE FULL
COCKPIT -- sensor cluster, myomers, cooling, the weapon panels, kills/deaths
-- with the board booted and audio running.  It then exits without flushing
its redirected stdout, so the exit reason is not yet known; next step is a
run with the redirect removed so the console is readable.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-28 08:09:56 -05:00

352 lines
11 KiB
C++

//===========================================================================//
// File: mechsub.cpp //
// Project: BattleTech Brick: Mech subsystems //
// Contents: Implementation details for the MechSubsystem base //
//---------------------------------------------------------------------------//
// Copyright (C) 1995, Virtual World Entertainment, Inc. //
// All Rights reserved worldwide //
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL //
//===========================================================================//
#include <bt.hpp>
#pragma hdrstop
#if !defined(MECHSUB_HPP)
# include <mechsub.hpp>
#endif
#if !defined(MECH_HPP)
# include <mech.hpp>
#endif
#if !defined(DAMAGE_HPP)
# include <damage.hpp>
#endif
#if !defined(BTPLAYER_HPP)
# include <btplayer.hpp>
#endif
#if !defined(BTMSSN_HPP)
# include <btmssn.hpp>
#endif
//
//#############################################################################
// Shared data support -- MechSubsystem adds no message handlers or attributes
// that are boot-critical, so it reuses the base Subsystem sets (which are the
// inherited Simulation sets).
//#############################################################################
//
Derivation
MechSubsystem::ClassDerivations(
Subsystem::ClassDerivations,
"MechSubsystem"
);
const MechSubsystem::IndexEntry
MechSubsystem::AttributePointers[]=
{
ATTRIBUTE_ENTRY(MechSubsystem, ConfigureActivePress, configureActivePress)
};
MechSubsystem::AttributeIndexSet
MechSubsystem::AttributeIndex(
ELEMENTS(MechSubsystem::AttributePointers),
MechSubsystem::AttributePointers,
Subsystem::AttributeIndex
);
MechSubsystem::SharedData
MechSubsystem::DefaultData(
MechSubsystem::ClassDerivations,
Subsystem::MessageHandlers,
MechSubsystem::AttributeIndex,
Subsystem::StateCount
);
//
//#############################################################################
// Light constructor (name + classID). For a subsystem created without a
// streamed resource; builds a trivial (armour-free) damage zone named by the
// base.
//#############################################################################
//
MechSubsystem::MechSubsystem(
Mech *owner,
int subsystem_ID,
const char *subsystem_name,
RegisteredClass::ClassID class_id,
SharedData &shared_data
):
Subsystem(owner, subsystem_ID, subsystem_name, class_id, shared_data)
{
this->owner = owner;
vitalSubsystem = False;
alarmModel = ResourceDescription::NullResourceID;
criticalReference = 0.0f;
collisionCriticalHitWeight = 0.0f;
printSimulationState = False;
configureActivePress = -1;
resource = NULL;
statusAlarm.SetLevel(0);
//
// The base Subsystem ctor leaves damageZone NULL; give this subsystem a
// trivial (armour-free) zone.
//
damageZone = new DamageZone(owner, 0);
Register_Object(damageZone);
}
//
//#############################################################################
// Resource constructor. Chains the base Subsystem resource ctor and seeds the
// mech-specific fields from the subsystem resource.
//
// The 4.10 damage model is per-damage-TYPE: the damage zone owns
// defaultArmorPoints + damageScale[5] and streams them itself (a
// Mech__DamageZone built from the model's damage-zone stream). Wiring that
// stream through from the resource is deferred (see MECHSUB.NOTES.md); until
// then the subsystem gets a trivial armour-free zone so it constructs and takes
// damage with default (unscaled) behaviour.
//#############################################################################
//
MechSubsystem::MechSubsystem(
Mech *owner,
int subsystem_ID,
SubsystemResource *subsystem_resource,
SharedData &shared_data
):
Subsystem(owner, subsystem_ID, subsystem_resource, shared_data)
{
Check_Pointer(subsystem_resource);
this->owner = owner;
resource = subsystem_resource;
vitalSubsystem = (subsystem_resource->vitalSubsystemIndex == 1);
alarmModel = subsystem_resource->alarmModel;
printSimulationState = subsystem_resource->printSimulationState;
criticalReference = subsystem_resource->criticalReference;
collisionCriticalHitWeight = subsystem_resource->collisionCriticalHitWeight;
configureActivePress = -1;
statusAlarm.SetLevel(0);
//
// The subsystem's PRIVATE damage zone (binary @4abf28: `new
// DamageZone(this, 0)` -- owned by the SUBSYSTEM, index 0, NOT an entry
// in the entity's hull-zone array). The resource authors its armor the
// same way the hull stream does -- structureReference points +
// armorByFacing[5] per-type values -- normalized with the same rule as
// Mech::DamageZone (scale = 1/(raw x points); already-normalized cells
// pass through). This is what makes critical hits MEASURABLE
// (ApplyDamageAndMeasure) and the crit-allotment push meaningful.
//
damageZone = new DamageZone(this, 0);
Register_Object(damageZone);
damageZone->damageZoneName = GetName();
if (subsystem_resource->structureReference > 0.0f)
{
damageZone->defaultArmorPoints = subsystem_resource->structureReference;
for (int tt = 0; tt < Damage::DamageTypeCount; ++tt)
{
Scalar raw = subsystem_resource->armorByFacing[tt];
Scalar even = 1.0f / subsystem_resource->structureReference;
Scalar diff = raw - even;
if (diff < 0.0f)
{
diff = -diff;
}
if (diff > 1.0e-4f && raw > 0.0f)
{
damageZone->damageScale[tt] =
1.0f / (raw * subsystem_resource->structureReference);
}
else
{
damageZone->damageScale[tt] = raw;
}
}
}
}
//
//#############################################################################
//#############################################################################
//
MechSubsystem::~MechSubsystem()
{
if (damageZone)
{
Unregister_Object(damageZone);
delete damageZone;
damageZone = NULL;
}
}
//
//#############################################################################
//#############################################################################
//
Logical
MechSubsystem::TestClass(Mech &)
{
return True;
}
Logical
MechSubsystem::TestInstance() const
{
return IsDerivedFrom(ClassDerivations);
}
//
//#############################################################################
// Damage support -- the 4.10 damage zone tracks damageLevel in [0,1]
// (0 = intact, 1 = destroyed).
//#############################################################################
//
Scalar
MechSubsystem::GetSubsystemDamageLevel() const
{
Check(this);
return damageZone ? damageZone->damageLevel : 0.0f;
}
void
MechSubsystem::SetSubsystemDamageLevel(Scalar level)
{
Check(this);
if (damageZone)
{
damageZone->damageLevel = level;
}
}
void
MechSubsystem::ForceCriticalFailure()
{
Check(this);
//
// Binary @4ac8xx family: the status alarm's DESTROYED display level is 1
// (the TechStatusType enum's Destroyed=0 is the display CATEGORY, not
// the alarm level -- BT411 disasm: "+0x2C: 1 = Destroyed").
//
statusAlarm.SetLevel(1);
//
// DestroyedState is the hard-failure gate the weapon simulations poll
// (`GetSimulationState() == 1`): a destroyed weapon drops its charge /
// pins the unavailable alarm from the next frame on.
//
SetSimulationState(DestroyedState);
if (damageZone)
{
damageZone->SetDamageZoneState(DamageZone::BurningState);
}
}
//
//#############################################################################
// DeathReset -- the respawn restore (the engine base virtual pair's reset
// half; Mech::Reset @0049fb74 loops every subsystem through it). The base:
// heal the private zone, clear the Destroyed display level and -- the
// load-bearing part -- clear DestroyedState so the weapon hard gates
// (GetSimulationState() == 1) release.
//#############################################################################
//
void
MechSubsystem::DeathReset(Logical /*full_reset*/)
{
Check(this);
if (damageZone != NULL)
{
damageZone->damageLevel = 0.0f;
damageZone->SetDamageZoneState(0);
}
statusAlarm.SetLevel(0);
SetSimulationState(Simulation::DefaultState);
}
//
//#############################################################################
// ApplyDamageAndMeasure (binary @4ac07c): apply through the virtual
// TakeDamage and report the zone-level delta -- the critical accountant
// (Mech::DamageZone::CriticalHit) charges the delta against the entry's
// damagePercentage allotment.
//#############################################################################
//
Scalar
MechSubsystem::ApplyDamageAndMeasure(Damage &damage)
{
Check(this);
Scalar before = (damageZone != NULL) ? damageZone->damageLevel : 0.0f;
TakeDamage(damage);
Scalar after = (damageZone != NULL) ? damageZone->damageLevel : 0.0f;
return after - before;
}
//
//#############################################################################
// TakeDamage Forward to the damage zone (per-type scaling happens there).
//#############################################################################
//
void
MechSubsystem::TakeDamage(Damage &damage)
{
Check(this);
Check_Pointer(&damage);
if (damageZone)
{
damageZone->TakeDamage(damage);
}
}
//
//#############################################################################
// CreateStreamedSubsystem Model-load-time construction of the subsystem
// resource + its damage-zone stream. Not yet reconstructed (see
// MECHSUB.NOTES.md -- the 4.10 per-type damage-zone stream format).
//#############################################################################
//
int
MechSubsystem::CreateStreamedSubsystem(
NotationFile *,
const char *,
const char *,
SubsystemResource *,
NotationFile *,
const ResourceDirectories *,
int
)
{
Fail("MechSubsystem::CreateStreamedSubsystem -- mechsub.cpp not yet reconstructed");
return 0;
}
//
//#############################################################################
// NoviceLockout -- the novice-experience lockout predicate (binary @4ac9c8):
// owner mech -> Entity::playerLink -> the BTPlayer's experience level == 0.
// Locks the advanced cockpit controls (condenser valves, coolant flush,
// cooling toggles). An unlinked mech reads UNLOCKED (dev-permissive).
//#############################################################################
//
Logical
MechSubsystem::NoviceLockout()
{
Check(this);
if (owner == NULL)
{
return False;
}
BTPlayer *player = Cast_Object(BTPlayer *, owner->GetPlayerLink());
if (player == NULL)
{
return False;
}
return (player->GetExperienceLevel() == BTMission::NoviceMode)
? True : False;
}