Files
TeslaRel410/restoration/source410/BT/MECHDMG.CPP
T
CydandClaude Fable 5 76cfc64353 BT410 Phase 5.3.16: targeting + damage -- two-mech fights are LIVE
The simulation is now a FIGHT. Mech target slot (+0x388), authentic
Loaded->Firing gate (viewFireEnable && HasActiveTarget), UpdateTargeting
range refresh, and SendDamage via the engine TakeDamageMessage.

MECHDMG.CPP is BORN against the AUTHENTIC surviving CODE/BT/BT/MECHDMG.HPP:
the Mech ctor's Pass-3 hull zone fill constructs Mech__DamageZone per zone
(the Entity base only allocates the raw pointer array -- unfilled slots were
crash #1; a base-class fill parses each record short and skews the stream --
crash #2). Streamed ctor parses the full BT tail (flags / Scalar-weighted
criticals with the Master+Dynamic plug gate / LOD redirect table) and
normalizes the armor economy: scale[type]=1/(raw x armorPoints), legs
halved -- BT411-verified @0049ce50.

Live two-mech verification (BT_SPAWN_ENEMY harness in DropZoneReply):
20 named hull zones stream on both mechs; PPC lands 11.77/hit (12 x
chargeRatio^2, type 4), ER-M laser 3.43 (type 3), SRM6 salvos 5.83 x 6
rounds on independent zones (type 2, one message per missile -- burstCount
is NOT in the zone formula); repeat hits climb linearly, leg zones absorb
at half scale, zones reach 1.0 Burning/Destroyed. No-target and novice
regressions hold (weapons load but never fire without a target).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 10:21:43 -05:00

282 lines
8.9 KiB
C++

//===========================================================================//
// File: mechdmg.cpp //
// Project: BattleTech Brick: Mech damage //
// Contents: Mech::DamageZone -- the hull zone armor economy //
//---------------------------------------------------------------------------//
// Copyright (C) 1995, Virtual World Entertainment, Inc. //
// All Rights reserved worldwide //
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL //
//===========================================================================//
//
// The class interface is the AUTHENTIC surviving CODE/BT/BT/MECHDMG.HPP
// (06/05/95 JM). The bodies here are reconstructions: the streamed ctor /
// SetLODParentPointers / dtor follow the BT411-verified binary parse
// (@0049ce50 / @0049d1d0), while TakeDamage and SetGraphicState are STAGED
// pass-throughs to the engine base -- the LOD artifact router (@0049c690),
// the critical destruction cascade and the death-handler graphics are the
// mechdmg cascade wave (see MECHDMG.NOTES.md).
//
#include <bt.hpp>
#pragma hdrstop
#if !defined(MECHDMG_HPP)
# include <mechdmg.hpp>
#endif
#if !defined(MECH_HPP)
# include <mech.hpp>
#endif
const int
Mech__DamageZone::NullDamageZone = -1;
//
//#############################################################################
// MechCriticalSubsystem (binary ctor @0049dd18): one critical-subsystem
// entry of a hull zone -- the plug binds the roster subsystem this zone
// destroys when its structure runs out; weight / percentage stream in the
// zone ctor below.
//#############################################################################
//
MechCriticalSubsystem::MechCriticalSubsystem(DamageZone *owner):
subsystemPlug(owner)
{
Check(owner);
criticalWeight = 0.0f;
damagePercentage = 0.0f;
damagePercentageUsed = 0.0f;
}
MechCriticalSubsystem::~MechCriticalSubsystem()
{
}
Logical
MechCriticalSubsystem::TestInstance() const
{
return True;
}
//
//#############################################################################
// Mech::DamageZone construction (binary @0049ce50).
//
// Chains to the engine DamageZone streamed ctor (name / effect sites /
// defaultArmorPoints / damageScale[5] / material lists -- DAMAGE.CPP:234),
// then consumes the BT mech-specific TAIL of the same per-zone record:
//
// Logical descendOnDestruction, destroySiblingsOnDestruction;
// int segmentIndex;
// Logical leftLeg, rightLeg, vitalDamageZone;
// int criticalSubsystemCount;
// critCount x { Scalar criticalWeight; Scalar damagePercentage;
// int subsystemIndex -> subsystemPlug }
// int redirectCount; redirectCount x int childZoneIndex
//
// Parsing the FULL record is LOAD-BEARING: the Mech ctor streams all hull
// zones sequentially from ONE stream, so a short read here skews every
// following zone (its effect-site indices then read garbage and
// GetSegment() returns NULL -- the 5.3.16 bring-up crash).
//
// THE ARMOR ECONOMY (BT411 combat-damage, verified vs the binary): the
// stream authors armor POINTS; each damageScale cell is normalized to a
// damage-per-point fraction scale[type] = 1/(raw[type] * armorPoints)
// (cells already equal to 1/armorPoints pass through), then LEG zones halve
// every type's scale. The engine base TakeDamage applies
// damageLevel += amount * damageScale[type] (1.0 = destroyed).
//#############################################################################
//
Mech__DamageZone::Mech__DamageZone(
Mech *mech,
int damage_zone_index,
MemoryStream *stream
):
DamageZone(mech, damage_zone_index, stream),
redirectTable(NULL, False),
parentArtifactZone(NULL)
{
Check(mech);
Check(stream);
lastHitZone = 0;
lastDamageTime = 0.0f;
lastInflicting = mech->GetEntityID();
criticalWeightSum = 0.0f;
criticalSubsystems = NULL;
//
// The streamed scalar/flag block (binary order @0049cf00-0049cf5c).
//
*stream >> descendOnDestruction;
*stream >> destroySiblingsOnDestruction;
*stream >> segmentIndex;
*stream >> leftLeg;
*stream >> rightLeg;
*stream >> vitalDamageZone;
*stream >> criticalSubsystemCount;
//
// Normalize the per-type armor scales in place (binary @0049cf60,
// constants 1e-4 / 0.5).
//
{
for (int type = 0; type < Damage::DamageTypeCount; ++type)
{
Scalar even = 1.0f / defaultArmorPoints;
Scalar diff = damageScale[type] - even;
if (diff < 0.0f)
{
diff = -diff;
}
if (diff > 1.0e-4f)
{
damageScale[type] =
1.0f / (damageScale[type] * defaultArmorPoints);
}
if (leftLeg || rightLeg)
{
damageScale[type] *= 0.5f;
}
}
}
//
// The critical-subsystem table. The streamed roster index binds the
// subsystem plug on the MASTER mech only (binary gate @0049d0e1:
// MasterInstance + DynamicFlag -- replicant plugs stay unbound, subsystem
// criticals are master-authoritative). A NULL roster slot is left
// unbound: slot 0 (the control mapper) installs post-ctor and
// Link::AddToPlug derefs the plug.
//
if (criticalSubsystemCount > 0)
{
criticalSubsystems =
new MechCriticalSubsystem *[criticalSubsystemCount];
Register_Pointer(criticalSubsystems);
for (int cc = 0; cc < criticalSubsystemCount; ++cc)
{
criticalSubsystems[cc] = new MechCriticalSubsystem(this);
Register_Object(criticalSubsystems[cc]);
int subsystem_index = -1;
*stream >> criticalSubsystems[cc]->criticalWeight;
*stream >> criticalSubsystems[cc]->damagePercentage;
*stream >> subsystem_index;
criticalWeightSum += criticalSubsystems[cc]->criticalWeight;
if (
mech->GetInstance() == Entity::MasterInstance
&& mech->IsDynamic()
&& subsystem_index >= 0
&& subsystem_index < mech->GetSubsystemCount()
)
{
Subsystem *target = mech->GetSubsystem(subsystem_index);
if (target != NULL)
{
criticalSubsystems[cc]->subsystemPlug.Add(target);
}
}
}
}
//
// The LOD redirect table: an ARTIFACT zone (low-LOD hull shell) lists
// the real child zones a hit on it lands on.
//
int redirect_count = 0;
*stream >> redirect_count;
{
for (int rr = 0; rr < redirect_count; ++rr)
{
int child_zone = 0;
*stream >> child_zone;
IntegerPlug *plug = new IntegerPlug(child_zone);
Register_Object(plug);
redirectTable.AddValue(plug, child_zone);
}
}
Check_Fpu();
}
Mech__DamageZone::~Mech__DamageZone()
{
if (criticalSubsystems != NULL)
{
for (int cc = 0; cc < criticalSubsystemCount; ++cc)
{
Unregister_Object(criticalSubsystems[cc]);
delete criticalSubsystems[cc];
}
Unregister_Pointer(criticalSubsystems);
delete [] criticalSubsystems;
}
DamageZoneIndexTableIterator iterator(redirectTable);
iterator.DeletePlugs();
}
//
//#############################################################################
// SetLODParentPointers (binary @0049d1d0). Run by the Mech ctor after EVERY
// zone of the mech exists: each redirect child's parentArtifactZone slot
// points back at this artifact zone (the parent-level damage recompute walks
// it -- cascade wave).
//#############################################################################
//
void
Mech__DamageZone::SetLODParentPointers()
{
Check(this);
Mech *mech = (Mech *)GetOwningSimulation();
Check(mech);
DamageZoneIndexTableIterator iterator(redirectTable);
IntegerPlug *plug;
while ((plug = iterator.ReadAndNext()) != NULL)
{
int child = *plug;
if (child >= 0 && child < mech->damageZoneCount)
{
Mech__DamageZone *child_zone =
(Mech__DamageZone *)mech->damageZones[child];
child_zone->parentArtifactZone.Add(this);
}
}
}
//
//#############################################################################
// Damage entry. STAGED: the engine base armor model applies directly --
// the authentic override (@0049c690) first routes ARTIFACT hits through the
// LOD redirect table to a real child zone, recomputes the artifact parent's
// displayed level, rolls critical-subsystem hits and updates the leg-failure
// state. All of that is the mechdmg cascade wave; the armor economy
// (amount x normalized scale, clamp, Burning/Destroyed at 1.0) is live NOW.
//#############################################################################
//
void
Mech__DamageZone::TakeDamage(Damage &damage)
{
Check(this);
DamageZone::TakeDamage(damage);
}
//
//#############################################################################
// Graphic state. STAGED pass-through: the authentic override also flips the
// zone's skeleton-segment video objects to the destroyed skins (cascade
// wave); the base keeps the state machine + changed flags correct for the
// network update records.
//#############################################################################
//
void
Mech__DamageZone::SetGraphicState(Enumeration new_state)
{
Check(this);
DamageZone::SetGraphicState(new_state);
}