//==========================================================================// // File: mechdmg.cpp // // Project: BattleTech // // Contents: Mech::DamageZone -- maps incoming damage to mech locations / // // subsystems (the dz_* zones streamed from the .BGF SV_SPECIAL // // tokens), critical-hit selection, LOD redirection, and the // // descend-on-destruction segment walk. // //---------------------------------------------------------------------------// // Date Who Modification // // -------- --- ---------------------------------------------------------- // // 06/05/95 JM Initial coding. // //---------------------------------------------------------------------------// // Copyright (C) 1995, Virtual World Entertainment, Inc. // // All Rights reserved worldwide // // This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL // //===========================================================================// // // RECONSTRUCTED from the shipped binary (Ghidra pseudo-C, recovered/all/ // part_012.c). Class shape is the SURVIVING MECHDMG.HPP; bodies follow the // decompiled object at the cited @ADDRs. The companion Mech-side loop that // streams every zone, Mech::CreateDamageZoneStream @004a474c, lives in // mech.cpp; this file is the per-zone class. // // Float constants recovered from the code section (section_dump.txt): // _DAT_0049c99c = 1.0f _DAT_0049c9a0 = 0.5f _DAT_0049c9a4 = 0.0f // _DAT_0049cacc = 0.0f _DAT_0049cad0 = 1.0f // _DAT_0049ce48 = 0.5f _DAT_0049ce4c = 1.0f // _DAT_0049c50c = 0.25f _DAT_0049c510 = 10.0f (LOD reuse-window seconds) // _DAT_0049c5f8 = 0.0f _DAT_0049c5fc = 1.0f (structure clamp) // _DAT_0049d1c4 = 1.0f _DAT_0049d1c8 = 1.0e-4f _DAT_0049d1cc = 0.5f (leg halving) // // Helper-function name mapping (engine internals referenced by the decomp): // FUN_0041df5c DamageZone::DamageZone (base ctor, vtable @004e3cf0) // FUN_0041e394 DamageZone::~DamageZone (base dtor) // FUN_0041e4e0 DamageZone::TakeDamage (base armor/structure model) // FUN_0041e590 DamageZone::CreateStreamedDamageZone (base streamer) // FUN_00424767 DamageZoneIndexTable::TableOf(0,1) // FUN_0042478a DamageZoneIndexTable::~TableOf // FUN_00424830 DamageZoneIndexTableIterator::TableIteratorOf(&table) // FUN_0042484f DamageZoneIndexTableIterator::~TableIteratorOf // FUN_00424716 IntegerPlug::PlugOf(&int) // FUN_00424811 ...IteratorOf(&list) (segment child iterators) // FUN_00417ab4 SlotOf<>::Resolve() / SharedData::Resolve() // FUN_00417858 SlotOf<>::Release // FUN_0041a1a4 IsDerivedFrom(classDerivations) // FUN_0041b9ec AlarmIndicator(levels) FUN_0041bbd8 AlarmIndicator::SetLevel(n) // FUN_00420ea4 EntityID::operator=(copy) FUN_00420ef4 EntityID::operator= // FUN_0042104c EntityID::operator== // FUN_00414b60 TheTime.CurrentTick() (/ DAT_0052140c == TicksPerSecond) // FUN_00408050 (Scalar)Random -> [0,1) FUN_0040807c(rng,n) Random(n) // FUN_004ac07c Subsystem::ApplyDamage(damage) -> Scalar // FUN_004b11bc Generator::ForceShortRecovery (class derivations @0050f4bc) // FUN_0049fb54 Mech:: // FUN_0040485c/00404720/00404088/00404190/004040d8 NotationFile readers // FUN_00402298/004022b0/004022d0 operator new / array new / delete // FUN_004dbb24/004db92c/004d9c38/004dbd4c DebugStream << / endl // #include #pragma hdrstop #if !defined(MECHDMG_HPP) # include #endif #if !defined(MECH_HPP) # include #endif #if !defined(POWERSUB_HPP) # include #endif #include // sscanf (offline streamer) #if !defined(RANDOM_HPP) # include // the engine's global RandomGenerator Random #endif //===========================================================================// // Reconstruction stand-ins LOCAL to this translation unit. // The per-zone damage code reaches engine clock / RNG / segment-tree / NULL // subsystem facets through an invented name set; these behaviour-neutral // stand-ins (and proxy casts onto the real Subsystem* / NotationFile* / // ResourceFile*) carry that surface so the recovered logic compiles. //===========================================================================// static struct ReconClock { Scalar CurrentTick() { return 0; } } TheTime; // FUN_00414b60 static const Scalar TicksPerSecond = 1.0f; // DAT_0052140c struct DZRef { int index; }; struct SegmentRecord { int siblings; int children; }; // mech segment-tree node struct SegmentIterator { template SegmentIterator(const A &) {} DZRef *Next() { return 0; } }; struct SegTableX { SegmentRecord *operator[](int) { return 0; } }; // cast of EntitySegment::SegmentTable // Subsystem facets the recovered code reaches that the engine Subsystem does // not expose under these names (cast of Subsystem*). struct SubProxy2 { Scalar ApplyDamage(Damage &) { return 0; } // FUN_004ac07c void OnDestroyed() {} // vtable slot 0x34 int isVital; // +0x39 AlarmIndicator failureAlarm; // +0x2c int hasDestructor; // +0x41 void *damageZone; // +0xE0 }; // Offline .dmg/.skl notation readers (cast of NotationFile* / ResourceFile*). struct NotationEntry { const char *name; const char *text; NotationEntry *Next() { return 0; } }; struct NotationList { int Count() { return 0; } NotationEntry *First() { return 0; } }; struct NoteX2 // cast of NotationFile* { template int ReadLogical(A&&...) { return 0; } template int ReadString(A&&...) { return 0; } template NotationList FindList(A&&...) { return NotationList(); } void Close() {} }; struct RFileX2 { template NotationFile *Open(A) { return 0; } }; // cast of ResourceFile* inline int Str_Equal(const char *a, const char *b) { return a && b && strcmp(a, b) == 0; } template inline int FindSegment(A&&...) { return -1; } // FUN_004274f8 // // Sentinel for "no zone". Returned by GetSegmentIndex/RandomRedirect on a miss. // const int Mech__DamageZone::NullDamageZone = -1; // LOD reuse window: within [0.25s, 10.0s] of the previous hit, the artifact // zone tends to keep routing to the same child (damage clustering). static const Scalar LODReuseWindowMin = 0.25f; // _DAT_0049c50c static const Scalar LODReuseWindowMax = 10.0f; // _DAT_0049c510 // Structure level is always clamped to [0,1]. static const Scalar StructureMin = 0.0f; // _DAT_0049c5f8 / _DAT_0049c9a4 / _DAT_0049cacc static const Scalar StructureMax = 1.0f; // _DAT_0049c5fc / _DAT_0049c99c / _DAT_0049cad0 // A leg whose zone is half-gone already trips the partial leg-failure graphic // when the mech is not in a normal standing/walking stance (the oracle reads // _DAT_0049c9a0 == 0.5 in that branch, vs StructureMax (1.0) for the full // "leg destroyed -> fall" case). static const Scalar LegHalfStructure = 0.5f; // _DAT_0049c9a0 // CriticalHit passes only half of the inflicted damage on to the armour model. static const Scalar CriticalDamageFraction = 0.5f; // _DAT_0049ce48 static const Scalar CriticalDamageMax = 1.0f; // _DAT_0049ce4c // Probability of re-using the previous child zone inside the reuse window. // (Ghidra read the operand at @0049c514; exact value not cleanly recoverable // from the pseudo-C -- it behaves as a high "stay on the same spot" bias.) static const Scalar LODReuseHysteresis = 0.82f; // _DAT_0049c514 (TODO: verify) //########################################################################### //########################################################################### // MechCriticalSubsystem //########################################################################### //########################################################################### // // @0049dd18 -- owner is the DamageZone that will destroy this subsystem when // the zone's structure runs out. damagePercentage / damagePercentageUsed and // criticalWeight are filled in afterwards by the streaming ctor of the zone. // MechCriticalSubsystem::MechCriticalSubsystem(DamageZone *owner) : subsystemPlug(owner) // FUN_0049dd7e (SlotOf) { criticalWeight = 0; // Wword(6) @0x18 damagePercentageUsed = 0; // Wword(5) @0x14 } // // @0049dd44 -- releases the subsystem slot (FUN_0049dd9d). // MechCriticalSubsystem::~MechCriticalSubsystem() { } // // @0049dd74 // Logical MechCriticalSubsystem::TestInstance() const { return True; } //########################################################################### //########################################################################### // Mech::DamageZone //########################################################################### //########################################################################### //############################################################################# // Construction -- @0049ce50 // // Chains to the base DamageZone (which parses armour/structure from the .dmg // record and allocates the redirect table), then reads this class's own // stream fields, normalises the per-facing armour scalars, builds the // critical-subsystem array and finally the LOD redirect table. // Mech__DamageZone::Mech__DamageZone( Mech *mech, int damage_zone_index, MemoryStream *stream ) : DamageZone(mech, damage_zone_index, stream) // FUN_0041df5c { // vtable @0050bb90 redirectTable.Construct(0, 1); // Wword(0x58) = FUN_00424767(,0,1) // last-redirect bookkeeping lastDamageTime = (Scalar)TheTime.CurrentTick() / TicksPerSecond; // Wword(0x5f) lastInflicting = mech->entityID; // Wword(0x60) = FUN_00420ea4(,mech+0x184) parentArtifactZone.Construct(0); // Wword(0x62) = FUN_0049ddc9(,0) criticalWeightSum = 0; // Wword(0x6c) // // Read the streamed scalar/flag block. (Stream order set by // CreateStreamedDamageZone below.) // stream->ReadBytes(&descendOnDestruction, 4); // Wword(0x67) stream->ReadBytes(&destroySiblingsOnDestruction, 4); // Wword(0x68) stream->ReadBytes(&segmentIndex, 4); // Wword(0x65) stream->ReadBytes(&leftLeg, 4); // Wword(0x69) stream->ReadBytes(&rightLeg, 4); // Wword(0x6a) stream->ReadBytes(&vitalDamageZone, 4); // Wword(0x66) stream->ReadBytes(&criticalSubsystemCount, 4); // Wword(0x6b) // // Normalise the damage-scale vector damageScale[Damage::DamageTypeCount] // (this[0x51..0x55], == base DamageZone::damageScale[5]). These are NOT // per-facing: the base TakeDamage indexes them by Damage::damageType // (it reads this[0x51 + damage.damageType]; see DamageZone::TakeDamage // @0041e4e0), so the 5 cells are the {Collision,Ballistic,Explosive,Laser, // Energy} per-type armour scale for this zone. Each cell is rewritten so a // stored "armour points" figure becomes a damage-per-point fraction // (1/(points*count)); a leg zone then halves every type's scale. // // damageScale[5] and defaultArmorPoints are the INHERITED engine DamageZone base // members (read raw from the stream by the base ctor, DAMAGE.cpp:301-307); we // normalise them in place here -- not a raw 0x144/0x140 offset (which would be the // binary layout, wrong when compiled against the engine base). for (int type = 0; type < Damage::DamageTypeCount; ++type) // 5 damage types { Scalar even = 1.0f / defaultArmorPoints; // _DAT_0049d1c4 / count if (fabsf(damageScale[type] - even) > 1.0e-4f) // _DAT_0049d1c8 damageScale[type] = 1.0f / (damageScale[type] * defaultArmorPoints); if (rightLeg || leftLeg) damageScale[type] *= 0.5f; // _DAT_0049d1cc } // // Critical-subsystem table. // if (criticalSubsystemCount == 0) { criticalSubsystems = 0; // Wword(0x6d) } else { criticalSubsystems = (MechCriticalSubsystem **)Memory::AllocateArray(criticalSubsystemCount * 4); for (int i = 0; i < criticalSubsystemCount; ++i) { criticalSubsystems[i] = new (Memory::Allocate(0x1c)) MechCriticalSubsystem(this); // FUN_0049dd18 } int subsystemIndex = -1; for (int i = 0; i < criticalSubsystemCount; ++i) { stream->ReadBytes(&criticalSubsystems[i]->criticalWeight, 4); // +0x18 stream->ReadBytes(&criticalSubsystems[i]->damagePercentage, 4); // +0x10 criticalWeightSum += criticalSubsystems[i]->criticalWeight; stream->ReadBytes(&subsystemIndex, 4); // subsystem-table index // Wire the subsystem plug to the live subsystem unless this is the // pure-LOD / cosmetic build (mech flags 0x28 bits). if ((mech->flags & 0xc) == 0 && (mech->flags & 0x100) != 0) criticalSubsystems[i]->subsystemPlug.Resolve(); // FUN_00417ab4 } } // // LOD redirect table: followed by child zone indices. // int redirectCount = 0; stream->ReadBytes(&redirectCount, 4); for (int i = 0; i < redirectCount; ++i) { int childZone = 0; stream->ReadBytes(&childZone, 4); redirectTable.Add(new (Memory::Allocate(0x10)) IntegerPlug(&childZone)); // FUN_00424716 } lastHitZone = (redirectCount == 0) ? 0 : RandomRedirect(); // Wword(0x5e) } //############################################################################# // SetLODParentPointers -- @0049d1d0 // // Run after every zone of the mech exists. For each child index in this // (artifact) zone's redirect table, point that real child zone's // parentArtifactZone slot back at us. // void Mech__DamageZone::SetLODParentPointers() { Mech *mech = (Mech *)GetOwningSimulation(); DamageZoneIndexTableIterator it(&redirectTable); // FUN_00424830 for (IntegerPlug *plug; (plug = it.Next()) != 0; ) // slot 0x28 { Mech__DamageZone *child = mech->Zone(plug->value); // inherited Entity::damageZones[idx], typed child->parentArtifactZone = this; // slot @0x188 +4 } } //############################################################################# // Destruction -- @0049d23c (vtable slot 0) // Mech__DamageZone::~Mech__DamageZone() { *(void **)this = &vtable_0050bb90; if (criticalSubsystems != 0) { for (int i = 0; i < criticalSubsystemCount; ++i) delete criticalSubsystems[i]; Memory::Free(criticalSubsystems); } { DamageZoneIndexTableIterator it(&redirectTable); // FUN_00424830 it.DeleteContents(); // FUN_00417858(,1) } // (binary tail: FUN_0049dde8 parentArtifactZone.~DZSlot, FUN_0042478a // redirectTable.~DamageZoneIndexTable, then base ~DamageZone FUN_0041e394 -- // all COMPILER-EMITTED dtor epilogue glue; C++ re-emits member + base // destruction implicitly at the closing brace, so writing them as source runs // them twice. See the reconstruction rule in mech.cpp ~Mech.) } //############################################################################# // TakeDamage -- @0049c690 (vtable slot 6) // // If this zone is an artifact (has a redirect table) the hit is handed to the // LOD router so it lands on a real child zone. Otherwise the base DamageZone // applies it to armour/structure, the parent artifact (if any) recomputes its // displayed level, special damage may short an attached generator, and the // zone updates its graphic / leg-failure state. // // The base armour model (DamageZone::TakeDamage @0041e4e0, == the WinTesla // engine DAMAGE.cpp body we link against) is: // // damageLevel += damage.damageAmount * damageScale[damage.damageType]; // Clamp(damageLevel, 0.0f, 1.0f); // _DAT_0041e57c / _DAT_0041e580 // SetDamageLevelChangedFlag(); // flags |= 4 // if (damageLevel >= 1.0f) { // zone destroyed // SetDamageZoneState(BurningState); // state indicator -> 1 // SetGraphicState(DestroyedGraphicState);// vtable slot 3 -> 1 // SetGraphicStateChangedFlag(); // flags |= 8 // } // // i.e. there is no separate "armour then structure" pool -- a single 0..1 // damageLevel accumulates, scaled per damage TYPE by damageScale[5] (the // vector normalised in the ctor above); 1.0 == destroyed. (damageLevel in // this reconstruction names that same base damageLevel cell @0x158.) // void Mech__DamageZone::TakeDamage(Damage& damage) { Mech *mech = (Mech *)GetOwningSimulation(); DamageZoneIndexTableIterator it(&redirectTable); // FUN_00424830 if (it.Count() != 0) // slot 0x14 -> artifact zone { EntityID inflicting = mech->lastInflictingID; // mech[0x43c] Damage local = damage; // copy 10 words LODDamageRouter(inflicting, local); // FUN_0049c40c return; } // ---- real zone: apply to armour/structure ---- DamageZone::TakeDamage(damage); // FUN_0041e4e0 Mech__DamageZone *parent = parentArtifactZone.Resolve(); // FUN_00417ab4(this+0x62) if (parent != 0) parent->UpdateLODDamageLevel(); // FUN_0049c51c // ---- ShortAttachedGenerators (inlined): special damage type 4 ---- if (damage.damageType == 4 && criticalSubsystemCount > 0) { int n = Random(criticalSubsystemCount); // FUN_0040807c Subsystem *s = criticalSubsystems[n]->subsystemPlug.Resolve(); if (s->IsDerivedFrom(*Generator::GetClassDerivations())) // FUN_0041a1a4(,0x50f4bc) ((Generator *)s)->ForceShortRecovery(); // FUN_004b11bc } // ---- graphic / failure state from current structure level ---- if (vitalDamageZone == 0 || damageLevel < StructureMax) { if (leftLeg == 0 && rightLeg == 0) { if (damageLevel >= StructureMax) RecurseSegmentTable(mech); // FUN_0049cad4 } else if ((unsigned)(mech->stance - 3) < 2) // standing/walking { if (rightLeg != 0 && damageLevel >= StructureMax) mech->graphicAlarm.SetLevel(9); // fall / leg gone else if (leftLeg != 0 && damageLevel >= StructureMax) mech->graphicAlarm.SetLevel(9); } else if (mech->IsAirborne() == 0) // FUN_0049fb54 { // Non-standing stance, still grounded: a half-destroyed leg zone // trips the partial-failure graphic (right -> 4, left -> 3). The // oracle compares against _DAT_0049c9a0 (0.5), NOT StructureMax. if (rightLeg != 0 && damageLevel >= LegHalfStructure) mech->graphicAlarm.SetLevel(4); else if (leftLeg != 0 && damageLevel >= LegHalfStructure) mech->graphicAlarm.SetLevel(3); } } else { // vital zone destroyed -> mech death SetDamageZoneState(1); // this+4 mech->graphicAlarm.SetLevel(9); } damageLevel = Clamp(damageLevel, StructureMin, StructureMax); } //############################################################################# // SetGraphicState -- @0049c66c (vtable slot 3) // // Drives the secondary "graphic state" alarm (this+0x64) and flags the zone // as needing a visual refresh (Wword(0xb8) |= 8). // // Mech__DamageZone::SetGraphicState removed -- byte-identical to the engine base // DamageZone::SetGraphicState (damageZoneGraphicState.SetState(new_state) + // SetGraphicStateChangedFlag(), the latter == the old `flags |= 8`). Calls below // resolve to the inherited base override. //############################################################################# // LODDamageRouter -- @0049c40c // // An artifact zone decides which real child takes the hit. If the same // attacker hit recently (within [0.25s, 10.0s]) and a random roll succeeds // the previous child is reused; otherwise a fresh RandomRedirect() is chosen. // A brand-new attacker always re-rolls. // void Mech__DamageZone::LODDamageRouter(EntityID inflicting, Damage damage) { int zone; if (inflicting != lastInflicting) // FUN_0042104c { zone = RandomRedirect(); // new attacker } else { Scalar dt = (Scalar)TheTime.CurrentTick() / TicksPerSecond - lastDamageTime; if (dt < LODReuseWindowMin) zone = lastHitZone; // too soon: same spot else if (dt > LODReuseWindowMax) zone = RandomRedirect(); // stale: re-roll else if ((Scalar)Random <= LODReuseHysteresis) // _DAT_0049c514 zone = lastHitZone; else zone = RandomRedirect(); } lastHitZone = zone; // Wword(0x5e) lastDamageTime = (Scalar)TheTime.CurrentTick() / TicksPerSecond; lastInflicting = inflicting; // FUN_00420ef4 Mech *mech = (Mech *)GetOwningSimulation(); mech->Zone(zone)->TakeDamage(damage); // slot 6 on the real child UpdateLODDamageLevel(); // FUN_0049c51c } //############################################################################# // RandomRedirect -- @0049c600 // // Uniformly pick one of the redirect table's child zone indices. // int Mech__DamageZone::RandomRedirect() { DamageZoneIndexTableIterator it(&redirectTable); // FUN_00424830 int n = Random(it.Count()); // FUN_0040807c(rng, slot 0x14) IntegerPlug *plug = it[n]; // slot 0x34 return plug->value; // +0xc } //############################################################################# // UpdateLODDamageLevel -- @0049c51c // // An artifact zone's displayed structure is the mean of its real children's // structure levels, clamped to [0,1]. // void Mech__DamageZone::UpdateLODDamageLevel() { Mech *mech = (Mech *)GetOwningSimulation(); unsigned count = 0; Scalar sum = 0.0f; DamageZoneIndexTableIterator it(&redirectTable); // FUN_00424830 for (IntegerPlug *plug; (plug = it.Next()) != 0; ) // slot 0x28 { sum += mech->Zone(plug->value)->damageLevel; // +0x158 ++count; } damageLevel = (count != 0) ? sum / (Scalar)count : StructureMin; damageLevel = Clamp(damageLevel, StructureMin, StructureMax); } //############################################################################# // CriticalHit -- @0049ccc4 // // Apply (half of) a critical hit. Half the damage amount flows through the // normal armour model (TakeDamage); the remainder rolls one critical // subsystem, weighted by criticalWeight, and damages it. Returns the chosen // subsystem (or 0 if none was selected). // Subsystem* Mech__DamageZone::CriticalHit(Damage &damage_data) { Scalar bite = damage_data.damageAmount * CriticalDamageFraction; // damage+4 * 0.5 if (bite > CriticalDamageMax) bite = CriticalDamageMax; damage_data.damageAmount -= bite; TakeDamage(damage_data); // slot 6 (this+0x18) Scalar roll = (Scalar)Random; // FUN_00408050 if (criticalSubsystems != 0) { for (int i = 0; i < criticalSubsystemCount; ++i) { Scalar cum = criticalSubsystems[i]->criticalWeight; for (int j = 0; j < i; ++j) cum += criticalSubsystems[j]->criticalWeight; if (roll <= cum / criticalWeightSum) { Subsystem *s = criticalSubsystems[i]->subsystemPlug.Resolve(); MechCriticalSubsystem *cs = criticalSubsystems[i]; if (cs->damagePercentageUsed < cs->damagePercentage) cs->damagePercentageUsed += ((SubProxy2 *)s)->ApplyDamage(damage_data); // FUN_004ac07c return criticalSubsystems[i]->subsystemPlug.Resolve(); } } } return 0; } //############################################################################# // SendSubsystemDamage -- @0049c9a8 // // When this zone is destroyed it forces full structure (1.0) on itself and // pushes each critical subsystem's outstanding damage fraction onto the zone // that physically carries that subsystem; any zone driven to destruction // there raises its alarms and fails the subsystem. // void Mech__DamageZone::SendSubsystemDamage() { SetDamageZoneState(1); // this+0x10 damageLevel = StructureMax; // this+0x158 = 1.0 for (int i = 0; i < criticalSubsystemCount; ++i) { Subsystem *s = criticalSubsystems[i]->subsystemPlug.Resolve(); // +4 MechCriticalSubsystem *cs = criticalSubsystems[i]; Mech__DamageZone *zone = (Mech__DamageZone *)((SubProxy2 *)s)->damageZone; // subsystem[0x38] @0xE0 zone->damageLevel += (cs->damagePercentage - cs->damagePercentageUsed); zone->damageLevel = Clamp(zone->damageLevel, StructureMin, StructureMax); if (zone->damageLevel >= StructureMax) // destroyed { if (((SubProxy2 *)s)->isVital) // subsystem[0x39] ((Mech *)GetOwningSimulation())->graphicAlarm.SetLevel(9); ((SubProxy2 *)s)->failureAlarm.SetLevel(1); // subsystem+0xb if (((SubProxy2 *)s)->hasDestructor) // subsystem[0x41] ((SubProxy2 *)s)->OnDestroyed(); // slot 0x34 zone->SetDamageZoneState(1); // zone+0x10 } } } //############################################################################# // RecurseSegmentTable -- @0049cad4 // // Walks the model's segment tree from this zone. First pushes this zone's // subsystem damage out (SendSubsystemDamage), then -- per the two streamed // flags -- destroys sibling zones (destroySiblingsOnDestruction) and/or // descends into child segments (descendOnDestruction), recursing on each. // void Mech__DamageZone::RecurseSegmentTable(Mech *my_mech) { SendSubsystemDamage(); // FUN_0049c9a8 SetGraphicState(1); // slot 0xc SegmentRecord *seg = ((SegTableX &)my_mech->segmentTable)[segmentIndex]; // mech+0x300, Wword(0x65) if (destroySiblingsOnDestruction) // Wword(0x68) { SegmentIterator sibs(&seg->siblings); // seg+0xd0 for (DZRef *r; (r = sibs.Next()) != 0; ) // slot 0x28 { Mech__DamageZone *sib = my_mech->Zone(r->index); // inherited damageZones[..], typed if (sib != this && sib->GetGraphicState() != 1) { sib->RecurseSegmentTable(my_mech); sib->SetGraphicState(2); // slot 0xc } } } if (descendOnDestruction) // Wword(0x67) { SegmentIterator kids(&seg->children); // seg+0xe8 for (DZRef *r; (r = kids.Next()) != 0; ) { SegmentRecord *child = ((SegTableX &)my_mech->segmentTable)[r->index]; SegmentIterator czones(&child->siblings); // child+0xd0 for (DZRef *cr; (cr = czones.Next()) != 0; ) { Mech__DamageZone *cz = my_mech->Zone(cr->index); cz->RecurseSegmentTable(my_mech); cz->SetGraphicState(2); } } } } //############################################################################# // CreateStreamedDamageZone -- @0049d304 // // Parse ONE zone out of the .dmg notation file into the memory stream that // the ctor above will later replay. Called once per entry in the .dmg // "DamageZones" list by Mech::CreateDamageZoneStream (@004a474c). // // Token stream written, in order: // (base fields via DamageZone::CreateStreamedDamageZone @0041e590) // DescendOnDestruction (Logical) // DestroySiblingsOnDestruction (Logical) // GetSegmentIndex(name, skl) (int) // leftLeg, rightLeg (from "LegDamageZone" == LeftLeg/RightLeg) // vitalDamageZone (from "VitalDamageZone" == True/False, required) // criticalSubsystemCount, then per "CriticalSubsystem <%f> <%f>": // subsystem index ("ControlsMapper"->0, "Power"->1, else Subsystems list), // the two percentages // redirectCount, then per "LODRedirect" entry: GetSegmentIndex of the name. // Logical Mech__DamageZone::CreateStreamedDamageZone( ResourceFile *resource_file, NotationFile *model_file, const char *model_name, NotationFile *skl_file, const char *damage_zone_name, NotationFile *dmg_file, const ResourceDirectories *directories, MemoryStream *damage_zone_stream ) { CString name(damage_zone_name); // base armour/structure record if (!DamageZone::CreateStreamedDamageZone( // FUN_0041e590 model_file, model_name, skl_file, name, damage_zone_stream, dmg_file, directories)) return (Logical)-1; Logical descend = 0; ((NoteX2 *)dmg_file)->ReadLogical(damage_zone_name, "DescendOnDestruction", &descend); damage_zone_stream->WriteBytes(&descend, 4); Logical destroySibs = 0; ((NoteX2 *)dmg_file)->ReadLogical(damage_zone_name, "DestroySiblingsOnDestruction", &destroySibs); damage_zone_stream->WriteBytes(&destroySibs, 4); int segIndex = GetSegmentIndex(name, skl_file); // FUN_0049db20 damage_zone_stream->WriteBytes(&segIndex, 4); Logical isLeftLeg = 0, isRightLeg = 0; const char *leg; if (((NoteX2 *)dmg_file)->ReadString(damage_zone_name, "LegDamageZone", &leg)) { if (Str_Equal(leg, "RightLeg")) isRightLeg = 1; if (Str_Equal(leg, "LeftLeg")) isLeftLeg = 1; } damage_zone_stream->WriteBytes(&isLeftLeg, 4); damage_zone_stream->WriteBytes(&isRightLeg, 4); const char *vital; if (!((NoteX2 *)dmg_file)->ReadString(damage_zone_name, "VitalDamageZone", &vital)) { DebugStream << dmg_file << damage_zone_name << " missing VitalDamageZone!"; return False; } Logical isVital; if (Str_Equal(vital, "True")) isVital = 1; else if (Str_Equal(vital, "False")) isVital = 0; else { DebugStream << model_name << " " << damage_zone_name << " Improper format for LegDamageZone"; return False; } damage_zone_stream->WriteBytes(&isVital, 4); // // CriticalSubsystem list (" <%f> <%f>"). // NotationList crits = ((NoteX2 *)dmg_file)->FindList(damage_zone_name, "CriticalSubsystem"); int critCount = crits.Count(); damage_zone_stream->WriteBytes(&critCount, 4); for (NotationEntry *e = crits.First(); e != 0; e = e->Next()) { char subName[64]; Scalar pctA, pctB; if (sscanf(e->text, "%s %f %f", subName, &pctA, &pctB) == 0) // s__s__f__f { DebugStream << model_name << " error in CriticalSubsystem Format!"; return (Logical)-1; } damage_zone_stream->WriteBytes(&pctA, 4); damage_zone_stream->WriteBytes(&pctB, 4); int subIndex; if (Str_Equal(subName, "ControlsMapper")) subIndex = 0; else if (Str_Equal(subName, "Power")) subIndex = 1; else { // search the model's "Subsystems" list, indices start at 2 const char *subFile; ((NoteX2 *)model_file)->ReadString("gamedata", "Subsystems", &subFile); NotationFile *sf = ((RFileX2 *)resource_file)->Open(subFile); NotationList list = ((NoteX2 *)sf)->FindList(0); subIndex = 2; NotationEntry *se; for (se = list.First(); se != 0; se = se->Next(), ++subIndex) if (Str_Equal(se->name, subName)) break; ((NoteX2 *)sf)->Close(); if (se == 0) { DebugStream << model_name << ':' << subName << " does not exist!"; return (Logical)-1; } } damage_zone_stream->WriteBytes(&subIndex, 4); } // // LODRedirect list: each redirect names a child zone resolved via the // segment table (TableOf::Find @004274f8); -1 == not found is an error. // NotationList redirects = ((NoteX2 *)dmg_file)->FindList(damage_zone_name, "LODRedirect"); int redirectCount = redirects.Count(); damage_zone_stream->WriteBytes(&redirectCount, 4); for (NotationEntry *e = redirects.First(); e != 0; e = e->Next()) { CString childName(e->text); if (childName.Length() == 0) { DebugStream << model_name << " error in LODRedirect Format!"; return (Logical)-1; } int childIndex = FindSegment(childName, skl_file); // FUN_004274f8 if (childIndex == -1) { DebugStream << resource_file << ' ' << childName << " Not Found!"; return (Logical)-1; } damage_zone_stream->WriteBytes(&childIndex, 4); } return True; } //############################################################################# // GetSegmentIndex -- @0049db20 // // Walk the skeleton (.skl) notation file's segment groups in order. Skipping // the "LAB_ONLY" and "DamageZones" pseudo-groups, count real segments; for // each segment read its "dzone" sub-list and, if any dzone name matches // damage_zone_name, return the running segment index. Returns -1 on miss. // int Mech__DamageZone::GetSegmentIndex(CString damage_zone_name, NotationFile *skl_file) { int index = -1; NotationList segments = ((NoteX2 *)skl_file)->FindList(0); // FUN_0040485c for (NotationEntry *seg = segments.First(); seg != 0; seg = seg->Next()) { if (Str_Equal(seg->name, "LAB_ONLY")) continue; if (Str_Equal(seg->name, "DamageZones")) continue; ++index; NotationList dzones = ((NoteX2 *)skl_file)->FindList(seg->name, "dzone"); // FUN_00404720 for (NotationEntry *dz = dzones.First(); dz != 0; dz = dz->Next()) { CString dzName(dz->text); if (dzName == damage_zone_name) // FUN_004027d8 return index; } } return -1; }