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>
282 lines
8.9 KiB
C++
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);
|
|
}
|