Files
TeslaRel410/restoration/source410/BT/MECHDMG.CPP
T
CydandClaude Fable 5 d4f0aef6e1 BT410 Phase 5.3.17: the destruction cascade -- zone deaths DESTROY things
Mech::DamageZone::TakeDamage is the full binary cascade (@0049c690): LOD
artifact router with same-attacker clustering (@0049c40c, hysteresis 0.33),
artifact parent level = mean of children, Energy-hit generator shorts
through the crit plug binding (novice-exempt), weighted CriticalHit
(@0049ccc4), zone-death allotment push (SendSubsystemDamage @0049c9a8) and
the segment-table destruction descent (RecurseSegmentTable @0049cad4 --
SIBS + DESCEND via the engine EntitySegment iterators).

Supporting bricks: Mech's OWN handler table with the TakeDamage override
(latches lastInflictingID @0x43c; gyro feed staged; cylinder table
deferred); friend class Mech__DamageZone (authentic); subsystem private
zones ARMED (structureReference + armorByFacing[5] normalized -- crits are
measurable); ApplyDamageAndMeasure; ForceCriticalFailure sets
DestroyedState so the weapon FSMs' GetSimulationState()==1 hard gate went
live; Generator::ForceShort + PoweredSubsystem::ForceShortRecovery.

Fight-verified (mutual BT_SPAWN_ENEMY + BT_FORCE_ZONE=8 concentrated fire):
arm zone to 1.0 -> sibling searchlight zone dies (Searchlight2/ThermalSight
crits destroyed, HUD degrading) -> DESCEND kills the gun zone -> PPC_2 +
ERMLaser_2 + Condenser6 DESTROYED and FALL SILENT (0 shots after death;
undamaged PPC_1 keeps firing); vital-zone hit = MECH KILL; PPC hit shorted
GeneratorD. This art has zero artifact zones (redirect=0 across all 20 --
router verified dormant-correct). Smoke + novice regressions clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 11:13:28 -05:00

776 lines
22 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
#if !defined(MECHSUB_HPP)
# include <mechsub.hpp>
#endif
#if !defined(POWERSUB_HPP)
# include <powersub.hpp>
#endif
#if !defined(SEGMENT_HPP)
# include <segment.hpp>
#endif
#if !defined(RANDOM_HPP)
# include <random.hpp>
#endif
const int
Mech__DamageZone::NullDamageZone = -1;
//
// Cascade constants (binary data words, BT411-verified addresses).
//
static const Scalar LODReuseWindowMin = 0.25f; // _DAT_0049c50c
static const Scalar LODReuseWindowMax = 10.0f; // _DAT_0049c510
static const Scalar LODReuseHysteresis = 0.33f; // _DAT_0049c514 (double 0.33)
static const Scalar StructureMin = 0.0f; // _DAT_0049c5f8
static const Scalar StructureMax = 1.0f; // _DAT_0049c5fc
static const Scalar LegHalfStructure = 0.5f; // _DAT_0049c9a0
static const Scalar CriticalDamageFraction = 0.5f; // _DAT_0049ce48
static const Scalar CriticalDamageMax = 1.0f; // _DAT_0049ce4c
//
//#############################################################################
// 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);
}
}
lastHitZone = (redirect_count == 0) ? 0 : RandomRedirect();
if (getenv("BT_SKEL_DUMP"))
{
DEBUG_STREAM << "[zone] " << damage_zone_index
<< " '" << damageZoneName << "'"
<< " pts=" << defaultArmorPoints
<< " seg=" << segmentIndex
<< " crit=" << criticalSubsystemCount
<< " redirect=" << redirect_count
<< (vitalDamageZone ? " VITAL" : "")
<< ((leftLeg || rightLeg) ? " LEG" : "")
<< (descendOnDestruction ? " DESCEND" : "")
<< (destroySiblingsOnDestruction ? " SIBS" : "")
<< endl << flush;
}
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);
}
}
}
//
//#############################################################################
// TakeDamage (binary @0049c690, vtable slot 6) -- the full zone cascade.
//
// ARTIFACT zones (non-empty redirect table = the low-LOD hull shell) never
// take damage themselves: the hit is routed to a real child zone. REAL
// zones apply the armor model, refresh their artifact parent's displayed
// level, roll the special-damage generator short, and drive the mech's
// graphic/failure state: a destroyed non-leg zone recurses the destruction
// down the segment table; a destroyed VITAL zone (or leg) is the mech kill.
//#############################################################################
//
void
Mech__DamageZone::TakeDamage(Damage &damage)
{
Check(this);
Mech *mech = (Mech *)GetOwningSimulation();
Check(mech);
{
DamageZoneIndexTableIterator redirects(redirectTable);
if (redirects.GetSize() != 0)
{
//
// Artifact shell: hand the hit to a real child (same-attacker
// clustering lives in the router).
//
Damage routed = damage;
LODDamageRouter(mech->lastInflictingID, routed);
return;
}
}
//
// Real zone: the engine armor model (amount x scale[type], clamp,
// Burning/Destroyed at 1.0).
//
Scalar preLevel = damageLevel;
DamageZone::TakeDamage(damage);
if (getenv("BT_MECH_LOG"))
{
DEBUG_STREAM << "[dmghit] zone=" << damageZoneIndex
<< " '" << damageZoneName << "'"
<< " type=" << (int)damage.damageType
<< " amt=" << damage.damageAmount
<< " lvl " << preLevel << "->" << damageLevel
<< (vitalDamageZone ? " VITAL" : "")
<< endl << flush;
}
Mech__DamageZone *parent = parentArtifactZone.GetCurrent();
if (parent != NULL)
{
parent->UpdateLODDamageLevel();
}
//
// Special damage: an Energy (type 4) hit on a zone with critical
// subsystems rolls ONE of them; a generator pick shorts it (the
// screen-flicker / weapons-dead moment; novice cockpits are exempt
// inside ForceShortRecovery).
//
if (damage.damageType == Damage::EnergyDamageType
&& criticalSubsystemCount > 0)
{
int pick = Random(criticalSubsystemCount);
Subsystem *victim = criticalSubsystems[pick]->subsystemPlug.GetCurrent();
if (victim != NULL
&& victim->IsDerivedFrom(Generator::ClassDerivations))
{
Generator *generator = (Generator *)victim;
if (!generator->NoviceLockout())
{
generator->ForceShort();
if (getenv("BT_MECH_LOG"))
{
DEBUG_STREAM << "[short] '" << generator->GetName()
<< "' SHORTED by Energy hit on zone "
<< damageZoneIndex << endl << flush;
}
}
}
}
//
// Graphic / failure state from the new structure level. The leg
// branches are gated in the binary by the LIVE MovementMode (3||4 =
// driving; IsDisabled = 2||9 wreck) -- our gait FSM is a later wave, so
// the STAGED gate is "mech not already destroyed" (equivalent while the
// only wreck path is the death level itself).
//
if (vitalDamageZone == 0 || damageLevel < StructureMax)
{
if (leftLeg == 0 && rightLeg == 0)
{
if (damageLevel >= StructureMax)
{
RecurseSegmentTable(mech);
}
}
else if (damageLevel >= StructureMax)
{
// leg destroyed -> fall / mech kill
mech->statusAlarm.SetLevel(9);
if (getenv("BT_MECH_LOG"))
{
DEBUG_STREAM << "[death] LEG zone " << damageZoneIndex
<< " destroyed -- mech down" << endl << flush;
}
}
else if (!mech->IsMechDestroyed())
{
// half-gone leg -> the gimp-gait graphic (right 4, left 3)
if (rightLeg != 0 && damageLevel >= LegHalfStructure)
{
mech->statusAlarm.SetLevel(4);
}
else if (leftLeg != 0 && damageLevel >= LegHalfStructure)
{
mech->statusAlarm.SetLevel(3);
}
}
}
else
{
//
// Vital zone destroyed -> mech death.
//
SetDamageZoneState(BurningState);
mech->statusAlarm.SetLevel(9);
if (getenv("BT_MECH_LOG"))
{
DEBUG_STREAM << "[death] VITAL zone " << damageZoneIndex
<< " '" << damageZoneName << "' destroyed -- MECH KILL"
<< endl << flush;
}
}
if (damageLevel < StructureMin)
{
damageLevel = StructureMin;
}
if (damageLevel > StructureMax)
{
damageLevel = StructureMax;
}
}
//
//#############################################################################
// LODDamageRouter (binary @0049c40c): pick which real child of this artifact
// takes the hit. The same attacker hitting again within [0.25s, 10s] tends
// to land on the SAME child (damage clustering, hysteresis 0.33); a new
// attacker or a stale window re-rolls.
//#############################################################################
//
void
Mech__DamageZone::LODDamageRouter(EntityID inflicting, Damage damage)
{
Check(this);
int zone;
if (!(inflicting == lastInflicting))
{
zone = RandomRedirect();
}
else
{
Scalar dt = (Scalar)Now() - lastDamageTime;
if (dt < LODReuseWindowMin)
{
zone = lastHitZone;
}
else if (dt > LODReuseWindowMax)
{
zone = RandomRedirect();
}
else if ((Scalar)Random <= LODReuseHysteresis)
{
zone = lastHitZone;
}
else
{
zone = RandomRedirect();
}
}
lastHitZone = zone;
lastDamageTime = (Scalar)Now();
lastInflicting = inflicting;
Mech *mech = (Mech *)GetOwningSimulation();
if (zone >= 0 && zone < mech->damageZoneCount)
{
((Mech__DamageZone *)mech->damageZones[zone])->TakeDamage(damage);
}
UpdateLODDamageLevel();
}
//
//#############################################################################
// RandomRedirect (binary @0049c600): uniform pick over the redirect table.
//#############################################################################
//
int
Mech__DamageZone::RandomRedirect()
{
Check(this);
DamageZoneIndexTableIterator iterator(redirectTable);
int count = iterator.GetSize();
if (count <= 0)
{
return NullDamageZone;
}
IntegerPlug *plug = iterator.GetNth(Random(count));
return (plug != NULL) ? (int)*plug : NullDamageZone;
}
//
//#############################################################################
// UpdateLODDamageLevel (binary @0049c51c): an artifact's displayed level is
// the mean of its real children's levels.
//#############################################################################
//
void
Mech__DamageZone::UpdateLODDamageLevel()
{
Check(this);
Mech *mech = (Mech *)GetOwningSimulation();
int count = 0;
Scalar sum = 0.0f;
DamageZoneIndexTableIterator iterator(redirectTable);
IntegerPlug *plug;
while ((plug = iterator.ReadAndNext()) != NULL)
{
int child = *plug;
if (child >= 0 && child < mech->damageZoneCount)
{
sum += ((Mech__DamageZone *)mech->damageZones[child])->damageLevel;
++count;
}
}
damageLevel = (count != 0) ? sum / (Scalar)count : StructureMin;
if (damageLevel < StructureMin)
{
damageLevel = StructureMin;
}
if (damageLevel > StructureMax)
{
damageLevel = StructureMax;
}
}
//
//#############################################################################
// CriticalHit (binary @0049ccc4): half the damage (capped 1.0) is carved off
// as the critical bite; the rest runs the normal armor model. The bite
// rolls ONE critical subsystem weighted by criticalWeight and applies the
// damage to it, charged against the entry's damagePercentage allotment.
// Returns the chosen subsystem (NULL if none).
//#############################################################################
//
Subsystem*
Mech__DamageZone::CriticalHit(Damage &damage_data)
{
Check(this);
Scalar bite = damage_data.damageAmount * CriticalDamageFraction;
if (bite > CriticalDamageMax)
{
bite = CriticalDamageMax;
}
damage_data.damageAmount -= bite;
TakeDamage(damage_data);
Scalar roll = (Scalar)Random;
if (criticalSubsystems != NULL && criticalWeightSum > 0.0f)
{
for (int ii = 0; ii < criticalSubsystemCount; ++ii)
{
Scalar cumulative = criticalSubsystems[ii]->criticalWeight;
for (int jj = 0; jj < ii; ++jj)
{
cumulative += criticalSubsystems[jj]->criticalWeight;
}
if (roll <= cumulative / criticalWeightSum)
{
Subsystem *victim =
criticalSubsystems[ii]->subsystemPlug.GetCurrent();
MechCriticalSubsystem *entry = criticalSubsystems[ii];
if (victim != NULL
&& entry->damagePercentageUsed < entry->damagePercentage)
{
entry->damagePercentageUsed +=
((MechSubsystem *)victim)->
ApplyDamageAndMeasure(damage_data);
if (getenv("BT_MECH_LOG"))
{
DEBUG_STREAM << "[crit] zone " << damageZoneIndex
<< " -> '" << victim->GetName()
<< "' used=" << entry->damagePercentageUsed
<< "/" << entry->damagePercentage
<< endl << flush;
}
}
return victim;
}
}
}
return NULL;
}
//
//#############################################################################
// SendSubsystemDamage (binary @0049c9a8): the zone is destroyed -- pin its
// structure, then push each critical entry's UNUSED damage allotment into
// the bound subsystem's own zone; a subsystem driven to 1.0 fails (and a
// VITAL one kills the mech).
//#############################################################################
//
void
Mech__DamageZone::SendSubsystemDamage()
{
Check(this);
SetDamageZoneState(BurningState);
damageLevel = StructureMax;
for (int ii = 0; ii < criticalSubsystemCount; ++ii)
{
MechCriticalSubsystem *entry = criticalSubsystems[ii];
MechSubsystem *victim =
(MechSubsystem *)entry->subsystemPlug.GetCurrent();
if (victim == NULL)
{
// Unbound plug = roster slot 0/1 residue (mapper installs
// post-ctor / voltage-bus stub) -- the 1995 binary derefs
// unguarded because every plug there is bound.
continue;
}
Scalar level = victim->GetSubsystemDamageLevel()
+ (entry->damagePercentage - entry->damagePercentageUsed);
if (level < StructureMin)
{
level = StructureMin;
}
if (level > StructureMax)
{
level = StructureMax;
}
victim->SetSubsystemDamageLevel(level);
if (level >= StructureMax)
{
if (victim->IsVitalSubsystem())
{
((Mech *)GetOwningSimulation())->statusAlarm.SetLevel(9);
}
victim->ForceCriticalFailure();
if (getenv("BT_MECH_LOG"))
{
DEBUG_STREAM << "[critfail] '" << victim->GetName()
<< "' DESTROYED (vital=" << victim->IsVitalSubsystem()
<< ") by zone " << damageZoneIndex << " death"
<< endl << flush;
}
}
else if (getenv("BT_MECH_LOG"))
{
DEBUG_STREAM << "[critfail] '" << victim->GetName()
<< "' damaged -> " << level << endl << flush;
}
}
}
//
//#############################################################################
// RecurseSegmentTable (binary @0049cad4): destruction descent. Push this
// zone's subsystem damage out, then -- per the streamed flags -- destroy the
// sibling zones on the same segment and/or descend into the child segments'
// zones, recursing on each.
//#############################################################################
//
void
Mech__DamageZone::RecurseSegmentTable(Mech *my_mech)
{
Check(this);
Check(my_mech);
SendSubsystemDamage();
SetGraphicState(DestroyedGraphicState);
EntitySegment *segment = my_mech->GetSegment(segmentIndex);
if (segment == NULL)
{
return;
}
if (destroySiblingsOnDestruction)
{
EntitySegment::IntegerTableIterator *siblings =
segment->MakeDamageZoneIndexTable();
EntitySegment::IntegerPlug *plug;
while ((plug = siblings->ReadAndNext()) != NULL)
{
int zi = *plug;
if (zi >= 0 && zi < my_mech->damageZoneCount)
{
Mech__DamageZone *sibling =
(Mech__DamageZone *)my_mech->damageZones[zi];
if (sibling != this
&& sibling->GetGraphicState() != DestroyedGraphicState)
{
sibling->RecurseSegmentTable(my_mech);
sibling->SetGraphicState(GoneGraphicState);
}
}
}
delete siblings;
}
if (descendOnDestruction)
{
EntitySegment::IntegerTableIterator *children =
segment->MakeChildIndexTable();
EntitySegment::IntegerPlug *plug;
while ((plug = children->ReadAndNext()) != NULL)
{
EntitySegment *child = my_mech->GetSegment(*plug);
if (child == NULL)
{
continue;
}
EntitySegment::IntegerTableIterator *child_zones =
child->MakeDamageZoneIndexTable();
EntitySegment::IntegerPlug *zone_plug;
while ((zone_plug = child_zones->ReadAndNext()) != NULL)
{
int zi = *zone_plug;
if (zi >= 0 && zi < my_mech->damageZoneCount)
{
Mech__DamageZone *child_zone =
(Mech__DamageZone *)my_mech->damageZones[zi];
// (defensive vs authored-cycle data; the 1995 binary
// trusted the tree and recursed unconditionally)
if (child_zone->GetGraphicState() != DestroyedGraphicState)
{
child_zone->RecurseSegmentTable(my_mech);
child_zone->SetGraphicState(GoneGraphicState);
}
}
}
delete child_zones;
}
delete children;
}
}
//
//#############################################################################
// 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);
}