Combat: critical-subsystem plugs BOUND -- zone destruction damages carried subsystems (task #2)
The binding was in the zone ctor all along: Ghidra dropped the two arg pushes @0049d0e1 (Slot::AddImplementation(subsystemArray[streamedIndex])), making it read as a bare Resolve(). DZSlot stand-in -> engine SlotOf<T>; SendSubsystemDamage rewritten to the recovered @0049c9a8 body (allotment into the subsystem's OWN private zone; vital -> graphicAlarm 9); CriticalHit -> real ApplyDamageAndMeasure; parentArtifactZone.Add revived (LOD damage averaging); videoObjectFlag renamed vitalSubsystem (+0xE4). BT_CRIT_PROBE diag added. Verified: 66 plugs bound/mech; probe-destroyed zone -> crits damaged/DESTROYED, statusAlarm + destroyed-skin chain fire; MP kill + solo un-regressed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
a652ddcdbc
commit
87c25b9206
@@ -179,13 +179,24 @@ authentic path scoped.
|
||||
(the authentic death-row removal — needs the mech render tree unhooked from the renderer first;
|
||||
the inert wreck is the safe stand-in until then).
|
||||
Do NOT issue DestroyEntityMessage on death (before the render-tree teardown lands).
|
||||
- **Critical-subsystem plugs UNBOUND (43 skips/mech) [T3].** `MechCriticalSubsystem::subsystemPlug`
|
||||
never gets WIRED to its live subsystem (the ctor's Resolve() at mechdmg.cpp:276 only READS; the
|
||||
binding write is elsewhere/unreconstructed), so `SendSubsystemDamage` skips every entry via the
|
||||
unbound-plug guard (mechdmg.cpp, added 2026-07-08 after a live NULL-deref AV in
|
||||
`SendSubsystemDamage+0x93` — the 1995 binary derefs unchecked because there every plug is bound).
|
||||
Consequence: zone destruction does NOT yet propagate damage into carried subsystems (generator/
|
||||
gyro failures etc.). Also latent there: `SubProxy2` raw-offset reads (databinding trap) once s≠0.
|
||||
- ✅ **Critical-subsystem plugs BOUND — damage propagates (task #2, 2026-07-11) [T1/T2].**
|
||||
The "binding write is elsewhere" reading was WRONG: the binding is IN the zone ctor loop
|
||||
(@0049d0e1-0049d10d) — **Ghidra dropped the two argument pushes** ("type propagation not
|
||||
settling"), making `Slot::AddImplementation(roster[streamedIndex])` read as a bare no-arg
|
||||
call (mislabeled Resolve). The stream's third per-entry dword IS the roster index into
|
||||
`subsystemArray` (mech+0x128). Port: the `DZSlot` stand-in (always-null Resolve) replaced
|
||||
with the engine `SlotOf<T>` (binary vtables 0050bb84/0050bb7c ARE its instantiations); bind
|
||||
gated MasterInstance+DynamicFlag (`simulationFlags@+0x28`); replicant plugs stay unbound
|
||||
(master-authoritative). `SendSubsystemDamage` rewritten to the recovered @0049c9a8 logic:
|
||||
the unused crit allotment lands in the subsystem's OWN private 0x160 DamageZone (the old
|
||||
`SubProxy2` cast to a mech zone was wrong), ≥1.0 → statusAlarm Destroyed + gated PrintState
|
||||
+ zone valve, vital → `graphicAlarm 9` (mech kill); `CriticalHit` now calls the real
|
||||
`ApplyDamageAndMeasure` (@4ac07c); the artifact `parentArtifactZone.Add` back-pointer bind
|
||||
also revived (LOD damage averaging). `videoObjectFlag` was `vitalSubsystem` (+0xE4,
|
||||
res+0x48 "VitalSubsystem"). VERIFIED live (`BT_CRIT_PROBE=<zone>` diag): 66 plugs bound at
|
||||
ctor per mech, zone destroyed → crits damaged/DESTROYED with authentic accumulation.
|
||||
Residue: roster idx 0/1 (mapper installed post-ctor / voltage-bus stub) stay unbound with a
|
||||
logged skip — none of the 8 mechs' streams crit-list them in practice.
|
||||
- **Two dead Wword comparison branches (multiplayer)**: `mech.cpp:1511` + `:1613` (replicant
|
||||
leg-state/stability sync in ReadUpdateRecord) — always-false BTVal comparisons ([[reconstruction-gotchas]] §2);
|
||||
needs slots 0xf/0x10 promoted to named members when P6 replication work resumes.
|
||||
|
||||
@@ -2750,6 +2750,43 @@ void
|
||||
<< " zones=" << m->damageZoneCount << "\n" << std::flush;
|
||||
}
|
||||
|
||||
// CRIT-PROPAGATION PROBE (BT_CRIT_PROBE=<zone>, task #2): every 4s
|
||||
// hammer ONE named zone of the LOCAL mech with a heavy hit until the
|
||||
// zone is destroyed -- drives Zone::TakeDamage -> RecurseSegmentTable
|
||||
// -> SendSubsystemDamage over the now-BOUND critical plugs, without
|
||||
// waiting for combat to randomly destroy a non-vital zone. Diag
|
||||
// only, off by default.
|
||||
{
|
||||
static int s_critZone = -2;
|
||||
static float s_critT = 0.0f;
|
||||
if (s_critZone == -2)
|
||||
{
|
||||
const char *cz = getenv("BT_CRIT_PROBE");
|
||||
s_critZone = cz ? atoi(cz) : -1;
|
||||
}
|
||||
if (s_critZone >= 0 && s_critZone < damageZoneCount
|
||||
&& isPlayerMech && !IsMechDestroyed())
|
||||
{
|
||||
s_critT += dt;
|
||||
if (s_critT >= 4.0f)
|
||||
{
|
||||
s_critT = 0.0f;
|
||||
Mech__DamageZone *z = Zone(s_critZone);
|
||||
if (z != 0)
|
||||
{
|
||||
Damage dmg;
|
||||
dmg.damageType = Damage::ExplosiveDamageType;
|
||||
dmg.damageAmount = 40.0f;
|
||||
dmg.burstCount = 1;
|
||||
dmg.impactPoint = localOrigin.linearPosition;
|
||||
DEBUG_STREAM << "[crit-probe] hitting zone "
|
||||
<< s_critZone << "\n" << std::flush;
|
||||
z->TakeDamage(dmg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CROSS-POD DAMAGE TEST HOOK (BT_MP_FORCE_DMG, task #47): once a second,
|
||||
// dispatch an unaimed TakeDamage at the first live REPLICANT through the
|
||||
// SAME virtual Entity::Dispatch path a beam hit uses -- Entity::Dispatch
|
||||
|
||||
@@ -40,7 +40,8 @@
|
||||
// FUN_0042484f DamageZoneIndexTableIterator::~TableIteratorOf
|
||||
// FUN_00424716 IntegerPlug::PlugOf(&int)
|
||||
// FUN_00424811 ...IteratorOf(&list) (segment child iterators)
|
||||
// FUN_00417ab4 SlotOf<>::Resolve() / SharedData::Resolve()
|
||||
// FUN_00417ab4 Slot::GetCurrentPlug (READ; was mislabeled Resolve)
|
||||
// FUN_00417a80 Slot::AddImplementation (BIND -- Ghidra dropped its args)
|
||||
// FUN_00417858 SlotOf<>::Release
|
||||
// FUN_0041a1a4 IsDerivedFrom(classDerivations)
|
||||
// FUN_0041b9ec AlarmIndicator(levels) FUN_0041bbd8 AlarmIndicator::SetLevel(n)
|
||||
@@ -199,7 +200,8 @@ Mech__DamageZone::Mech__DamageZone(
|
||||
int damage_zone_index,
|
||||
MemoryStream *stream
|
||||
)
|
||||
: DamageZone(mech, damage_zone_index, stream) // FUN_0041df5c
|
||||
: DamageZone(mech, damage_zone_index, stream), // FUN_0041df5c
|
||||
parentArtifactZone(0) // FUN_0049ddc9(,0) -- Socket(NULL) legal
|
||||
{
|
||||
// vtable @0050bb90
|
||||
redirectTable.Construct(0, 1); // Wword(0x58) = FUN_00424767(,0,1)
|
||||
@@ -207,7 +209,6 @@ Mech__DamageZone::Mech__DamageZone(
|
||||
// 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)
|
||||
|
||||
//
|
||||
@@ -268,12 +269,38 @@ Mech__DamageZone::Mech__DamageZone(
|
||||
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
|
||||
stream->ReadBytes(&subsystemIndex, 4); // roster index (mech+0x128 order)
|
||||
|
||||
// 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
|
||||
// THE BINDING (task #2, disasm @0049d0e1-0049d10d -- Ghidra DROPPED
|
||||
// the two arg pushes, which is why this read as a bare Resolve():
|
||||
// the real call is Slot::AddImplementation(roster[index])). Gate =
|
||||
// MasterInstance + DynamicFlag on the flags LWord @+0x28
|
||||
// (simulationFlags). Replicant plugs stay UNBOUND on purpose:
|
||||
// subsystem criticals are master-authoritative. [T1]
|
||||
if ((mech->simulationFlags & Entity::InstanceMask) == Entity::MasterInstance
|
||||
&& (mech->simulationFlags & Entity::DynamicFlag) != 0)
|
||||
{
|
||||
Subsystem *target =
|
||||
(subsystemIndex >= 0 && subsystemIndex < mech->GetSubsystemCount())
|
||||
? mech->GetSubsystem(subsystemIndex) : 0;
|
||||
if (target != 0)
|
||||
{
|
||||
criticalSubsystems[i]->subsystemPlug.Add(target); // FUN_00417a80
|
||||
if (getenv("BT_DEATH_LOG"))
|
||||
DEBUG_STREAM << "[crit] zone " << damage_zone_index
|
||||
<< " entry " << i << " -> roster[" << subsystemIndex
|
||||
<< "] = " << (void *)target << "\n" << std::flush;
|
||||
}
|
||||
else if (getenv("BT_DEATH_LOG"))
|
||||
{
|
||||
// roster slot NULL: idx 0 (mapper, installed post-ctor) or
|
||||
// idx 1 (voltage bus, ReconSlot stub). The engine
|
||||
// Link::AddToPlug derefs the plug, so a null bind crashes.
|
||||
DEBUG_STREAM << "[crit] zone " << damage_zone_index
|
||||
<< " entry " << i << " roster[" << subsystemIndex
|
||||
<< "] is NULL -- left unbound\n" << std::flush;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -307,7 +334,7 @@ void
|
||||
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
|
||||
child->parentArtifactZone.Add(this); // slot @0x188: FUN_00417a80 (NOT flag-gated)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -380,7 +407,7 @@ void
|
||||
// ---- real zone: apply to armour/structure ----
|
||||
DamageZone::TakeDamage(damage); // FUN_0041e4e0
|
||||
|
||||
Mech__DamageZone *parent = parentArtifactZone.Resolve(); // FUN_00417ab4(this+0x62)
|
||||
Mech__DamageZone *parent = parentArtifactZone.GetCurrent(); // FUN_00417ab4(this+0x62)
|
||||
if (parent != 0)
|
||||
parent->UpdateLODDamageLevel(); // FUN_0049c51c
|
||||
|
||||
@@ -388,8 +415,9 @@ void
|
||||
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)
|
||||
Subsystem *s = criticalSubsystems[n]->subsystemPlug.GetCurrent();
|
||||
if (s != 0 // unbound-plug guard (idx-0/1 roster residue)
|
||||
&& s->IsDerivedFrom(*Generator::GetClassDerivations())) // FUN_0041a1a4(,0x50f4bc)
|
||||
((Generator *)s)->ForceShortRecovery(); // FUN_004b11bc
|
||||
}
|
||||
|
||||
@@ -546,11 +574,13 @@ Subsystem*
|
||||
|
||||
if (roll <= cum / criticalWeightSum)
|
||||
{
|
||||
Subsystem *s = criticalSubsystems[i]->subsystemPlug.Resolve();
|
||||
Subsystem *s = criticalSubsystems[i]->subsystemPlug.GetCurrent();
|
||||
MechCriticalSubsystem *cs = criticalSubsystems[i];
|
||||
if (cs->damagePercentageUsed < cs->damagePercentage)
|
||||
cs->damagePercentageUsed += ((SubProxy2 *)s)->ApplyDamage(damage_data); // FUN_004ac07c
|
||||
return criticalSubsystems[i]->subsystemPlug.Resolve();
|
||||
if (s != 0 // unbound-plug guard
|
||||
&& cs->damagePercentageUsed < cs->damagePercentage)
|
||||
cs->damagePercentageUsed +=
|
||||
((MechSubsystem *)s)->ApplyDamageAndMeasure(damage_data); // FUN_004ac07c
|
||||
return s;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -573,39 +603,43 @@ void
|
||||
|
||||
for (int i = 0; i < criticalSubsystemCount; ++i)
|
||||
{
|
||||
Subsystem *s = criticalSubsystems[i]->subsystemPlug.Resolve(); // +4
|
||||
MechSubsystem *s =
|
||||
(MechSubsystem *)criticalSubsystems[i]->subsystemPlug.GetCurrent(); // +4
|
||||
MechCriticalSubsystem *cs = criticalSubsystems[i];
|
||||
|
||||
// UNBOUND-PLUG GUARD. Resolve() (FUN_00417ab4) returns the subsystem bound
|
||||
// to the DZSlot, or 0 if plug+8 was never wired. In the 1995 binary every
|
||||
// critical subsystem exists and is bound, so it dereferences s with no check.
|
||||
// In this reconstruction a critical subsystem can be UNBOUND (its type is not
|
||||
// yet built, or its slot never connected), leaving s == 0 -> the original
|
||||
// code AV'd here (mov [edx+0xc], edx=0) as soon as a zone with such a plug was
|
||||
// destroyed. Skip the unbound entry rather than crash; log it so the missing
|
||||
// binding can be tracked. NOT a behavioural stand-in: a bound plug takes the
|
||||
// authentic path below unchanged. [T3 -- see open-questions: crit-subsys binding]
|
||||
// Unbound-plug guard: with the authentic binding live (the ctor loop)
|
||||
// this is only the idx-0/1 roster residue (mapper installed post-ctor
|
||||
// / voltage-bus stub), not the norm. The 1995 binary derefs with no
|
||||
// check because every plug there is bound.
|
||||
if (s == 0)
|
||||
{
|
||||
if (getenv("BT_DEATH_LOG"))
|
||||
DEBUG_STREAM << "[deathfx] crit-subsys " << i << "/" << criticalSubsystemCount
|
||||
<< " UNBOUND (plug not wired) -- skipped\n" << std::flush;
|
||||
<< " UNBOUND (roster slot residue) -- skipped\n" << std::flush;
|
||||
continue;
|
||||
}
|
||||
|
||||
Mech__DamageZone *zone = (Mech__DamageZone *)((SubProxy2 *)s)->damageZone; // subsystem[0x38] @0xE0
|
||||
// Push the UNUSED crit allotment into the subsystem's OWN private
|
||||
// DamageZone (the 0x160-byte zone its ctor built at +0xE0) -- NOT a
|
||||
// mech dz_* zone (the old SubProxy2 cast misread this). [T1 @0049c9a8]
|
||||
Scalar level = s->GetSubsystemDamageLevel()
|
||||
+ (cs->damagePercentage - cs->damagePercentageUsed);
|
||||
level = Clamp(level, StructureMin, StructureMax);
|
||||
s->SetSubsystemDamageLevel(level);
|
||||
|
||||
zone->damageLevel += (cs->damagePercentage - cs->damagePercentageUsed);
|
||||
zone->damageLevel = Clamp(zone->damageLevel, StructureMin, StructureMax);
|
||||
|
||||
if (zone->damageLevel >= StructureMax) // destroyed
|
||||
if (level >= StructureMax) // subsystem 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
|
||||
if (s->IsVitalSubsystem()) // +0xE4
|
||||
((Mech *)GetOwningSimulation())->graphicAlarm.SetLevel(9); // mech kill
|
||||
s->ForceCriticalFailure(); // status 1 + print + zone valve
|
||||
if (getenv("BT_DEATH_LOG"))
|
||||
DEBUG_STREAM << "[deathfx] crit-subsys " << i
|
||||
<< " DESTROYED (vital=" << s->IsVitalSubsystem() << ")\n" << std::flush;
|
||||
}
|
||||
else if (getenv("BT_DEATH_LOG"))
|
||||
{
|
||||
DEBUG_STREAM << "[deathfx] crit-subsys " << i << " damaged -> "
|
||||
<< level << "\n" << std::flush;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,15 +80,12 @@
|
||||
DZIntegerPlug *operator[](int) { return 0; }
|
||||
void DeleteContents() {}
|
||||
};
|
||||
template<class T> struct DZSlot // SlotOf<T> stand-in
|
||||
{
|
||||
DZSlot() {}
|
||||
DZSlot(const void *) {} // ctor(owner)
|
||||
void Construct(int) {}
|
||||
T Resolve() { return T(); }
|
||||
DZSlot &operator=(const DZSlot &) { return *this; }
|
||||
template<class A> DZSlot &operator=(const A &) { return *this; }
|
||||
};
|
||||
// (task #2: the DZSlot<T> stand-in is RETIRED -- the engine SlotOf<T>
|
||||
// (engine/MUNGA/SLOT.h) is the real type; the binary vtables 0050bb84 /
|
||||
// 0050bb7c ARE its <Subsystem*> / <Mech__DamageZone*> instantiations:
|
||||
// FUN_004179d4 = Slot::Slot(Node*), FUN_00417a80 = AddImplementation
|
||||
// (BIND), FUN_00417ab4 = GetCurrentPlug (READ). The stand-in's
|
||||
// always-null Resolve() was why every critical plug read as unbound.)
|
||||
|
||||
//##########################################################################
|
||||
//###################### MechCriticalSubsystem #################
|
||||
@@ -108,7 +105,7 @@
|
||||
|
||||
virtual ~MechCriticalSubsystem(); // @0049dd44 (vtable slot 0)
|
||||
|
||||
DZSlot<Subsystem*>
|
||||
SlotOf<Subsystem*>
|
||||
subsystemPlug; // @0x04 ctor @0049dd7e / dtor @0049dd9d (vtable @0050bb84)
|
||||
|
||||
Scalar
|
||||
@@ -175,7 +172,7 @@
|
||||
// With the current art (1/30/96) no zone has both a parent and children,
|
||||
// and many zones have neither.
|
||||
|
||||
DZSlot<Mech__DamageZone*> parentArtifactZone; // @0x188 ctor @0049ddc9 (vtable @0050bb7c)
|
||||
SlotOf<Mech__DamageZone*> parentArtifactZone; // @0x188 ctor @0049ddc9 (vtable @0050bb7c)
|
||||
|
||||
// --- reconstruction-named state (decomp spellings; real DamageZone
|
||||
// base uses damageLevel/damageZoneState/etc.) ------------------
|
||||
|
||||
@@ -126,7 +126,7 @@ MechSubsystem::MechSubsystem(
|
||||
this->owner = owner; // this[0x34] -- the owning Mech (canonical)
|
||||
hostEntity = owner; // this[0x3a] = param_7
|
||||
subsystemId2 = subsystem_ID; // this[0x3b] = param_8
|
||||
videoObjectFlag = 0; // this[0x39]
|
||||
vitalSubsystem = 0; // this[0x39]
|
||||
BindName(refCount, "None"); // FUN_00402a98(.,this+0x3d,"None") @0x50df7a
|
||||
alarmModel = 0; // identity ResourceID // FUN_00408440(this+0x3e, identity)
|
||||
criticalReference = 0; // this[0x42]
|
||||
@@ -163,7 +163,7 @@ MechSubsystem::MechSubsystem(
|
||||
subsystemId2 = subsystem_ID; // this[0x3b]=param_7
|
||||
resource = subsystem_resource; // this[0x3c]
|
||||
vitalSubsystemIndex = -1; // this[0x44]
|
||||
videoObjectFlag = (subsystem_resource->vitalSubsystemIndex == 1); // res+0x48==1
|
||||
vitalSubsystem = (subsystem_resource->vitalSubsystemIndex == 1); // res+0x48==1
|
||||
|
||||
BindName(refCount, subsystem_resource->videoObjectName); // res+0x4c
|
||||
alarmModel = subsystem_resource->alarmModel; // res+0xcc -> this[0x3e]
|
||||
@@ -218,6 +218,24 @@ Scalar MechSubsystem::GetSubsystemDamageLevel() const
|
||||
return damageZone ? ((DamageZone *)damageZone)->damageLevel : 0.0f;
|
||||
}
|
||||
|
||||
//
|
||||
// @0049c9a8 (zone-side inline) -- the destroyed-side effects applied when a
|
||||
// crit drives this subsystem's own zone to 1.0: status Destroyed, the gated
|
||||
// debug print, and the zone's state valve. (task #2)
|
||||
//
|
||||
void MechSubsystem::ForceCriticalFailure()
|
||||
{
|
||||
statusAlarm.SetLevel(1); // +0x2C: 1 = Destroyed
|
||||
if (printSimulationState) // +0x104 gate
|
||||
{
|
||||
PrintState(); // vtable slot 0x34 @4ac8c0
|
||||
}
|
||||
if (damageZone != 0)
|
||||
{
|
||||
((DamageZone *)damageZone)->SetDamageZoneState(1); // zone+0x10
|
||||
}
|
||||
}
|
||||
|
||||
void MechSubsystem::SetSubsystemDamageLevel(Scalar level)
|
||||
{
|
||||
if (damageZone)
|
||||
@@ -263,7 +281,7 @@ Logical
|
||||
OnAlarmChanged(); // (*this.vtable+0x34)(this)
|
||||
}
|
||||
damageZone->structureLevel = 1.0f; // dz+0x158 = 1.0
|
||||
if (videoObjectFlag != 0) // this[0x39]
|
||||
if (vitalSubsystem != 0) // this[0x39]
|
||||
{
|
||||
((Mech *)owner)->RaiseStatusAlarm(9); // FUN_0041bbd8(owner+0x2c, 9)
|
||||
}
|
||||
|
||||
@@ -248,13 +248,22 @@ class Damage;
|
||||
Scalar GetSubsystemDamageLevel() const;
|
||||
void SetSubsystemDamageLevel(Scalar level);
|
||||
|
||||
// task #2 (crit propagation): the zone-side crit paths
|
||||
// (Mech__DamageZone::CriticalHit @0049ccc4 / SendSubsystemDamage
|
||||
// @0049c9a8) reach these subsystem facets.
|
||||
int IsVitalSubsystem() const { return vitalSubsystem; }
|
||||
// @0x4ac07c -- run the virtual TakeDamage and return the resulting
|
||||
// rise in the subsystem's own damageLevel (the crit allotment used).
|
||||
Scalar ApplyDamageAndMeasure(Damage &damage);
|
||||
// The destroyed-side effects SendSubsystemDamage applies when the
|
||||
// subsystem's own zone hits 1.0: statusAlarm Destroyed(1) + the
|
||||
// PrintState debug gate + the zone's state valve. [T1 @0049c9a8]
|
||||
void ForceCriticalFailure();
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Internal helpers
|
||||
//
|
||||
protected:
|
||||
// @0x4ac07c -- run TakeDamage and return the resulting drop in
|
||||
// structureLevel (DamageZone+0x158).
|
||||
Scalar ApplyDamageAndMeasure(Damage &damage);
|
||||
|
||||
// @0x4ac22c -- clear structure & both alarms to the inactive level.
|
||||
void ClearStatus();
|
||||
@@ -276,7 +285,10 @@ class Damage;
|
||||
AlarmIndicator statusAlarm; // @0x2C this+0xb (condition/status alarm)
|
||||
int simulationState; // @0x40 DefaultState/Destroyed/Exploding
|
||||
ReconDamageZone *damageZone; // @0xE0 this[0x38] (0x160-byte zone)
|
||||
int videoObjectFlag; // @0xE4 this[0x39]
|
||||
int vitalSubsystem; // @0xE4 this[0x39] -- destroying this
|
||||
// subsystem KILLS the mech (ctor: res+0x48
|
||||
// "VitalSubsystem"==1; was misnamed
|
||||
// videoObjectFlag) [T1 part_012.c:15776]
|
||||
Mech *hostEntity; // @0xE8 this[0x3a]
|
||||
int subsystemId2; // @0xEC this[0x3b]
|
||||
SubsystemResource *resource; // @0xF0 this[0x3c]
|
||||
|
||||
Reference in New Issue
Block a user