BT410 5.3.115: the damage hub consolidates -- MESSMGR's three runtime bodies, a dead 1995 feature recorded, and two more donor drifts caught
SubsystemMessageManager reconstructed from the raw decomp with the wire format pinned by the engine's own parser (TakeDamageStreamMessageHandler: count, then type/amount/subsystemID per record -- the chain KEY carries the damage amount, which is why AddDamageMessage keys its plugs by it). AddDamageMessage @0049b6d8: first hit of the frame wins the common record. ConsolidateAndSendDamage @0049b784: one DynamicMessage(0x13, 0x34) at the hit entity, the firing weapon's explosion resource bundled per record (binary reads roster[id]+0x3e4 blind; [T1] guarded here), DeletePlugs teardown, commons reset. CreateWeaponExplosions @0049baa0 under its AUTHENTIC surviving-header signature (the donor's 'SendQueuedExplosions' rename was drift): per queued resource an Explosion::MakeMessage -- MakeMessageID 3, ExplosionClassID 0x31 (our VDATA enum ordinal matches the binary exactly), flags 0x100, identity-rotation Origin at the impact point -- posted to the Registry at CreationEventPriority, staggered 0.1s apart. The 1995 archaeology: the binary computes a terrain-hit flag from a Derivation at 0x4e6f54 (unresolved BSS class) and passes it to a parameter THE SHIPPED BODY NEVER READS; terrainHitExplosionID is resourced by the CSS and consumed nowhere. A declared, streamed, abandoned feature -- shipped anyway. We pass False [T3]; behavior is byte-identical. Ctor corrections vs our stub: weaponExplosions is UNIQUE-keyed (binary chain ctor flag 1), the Performance registers, resource fields copy from the stream. MechWeapon grows a public ExplosionResourceIDOf() accessor. The 0x160-byte engine helper at +0xE0 stays staged [T3]; the CSS stays in the authoring-family batch. No feeders call AddDamageMessage yet -- the consolidator idles empty per frame, soak-verified live (no faults, wheel turning). Stubs: 13 across 10 files. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -126,6 +126,15 @@
|
||||
Logical
|
||||
TestInstance() const;
|
||||
|
||||
//
|
||||
// The projectile/explosion GameModel this weapon spawns (binary
|
||||
// @0x3e4) -- read by SubsystemMessageManager's consolidator to
|
||||
// bundle the firing weapon's explosion with the damage it caused.
|
||||
//
|
||||
ResourceDescription::ResourceID
|
||||
ExplosionResourceIDOf()
|
||||
{ Check(this); return explosionResourceID; }
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Construction and Destruction
|
||||
//
|
||||
|
||||
@@ -1,101 +1,404 @@
|
||||
//===========================================================================//
|
||||
// File: messmgr.cpp //
|
||||
// Project: BattleTech Brick: Mech subsystems //
|
||||
// Contents: SubsystemMessageManager -- the per-mech damage/explosion hub //
|
||||
//---------------------------------------------------------------------------//
|
||||
// Copyright (C) 1995, Virtual World Entertainment, Inc. //
|
||||
// All Rights reserved worldwide //
|
||||
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL //
|
||||
//===========================================================================//
|
||||
|
||||
#include <bt.hpp>
|
||||
#pragma hdrstop
|
||||
|
||||
#if !defined(MESSMGR_HPP)
|
||||
# include <messmgr.hpp>
|
||||
#endif
|
||||
|
||||
#if !defined(MECH_HPP)
|
||||
# include <mech.hpp>
|
||||
#endif
|
||||
|
||||
Derivation
|
||||
SubsystemMessageManager::ClassDerivations(
|
||||
Subsystem::ClassDerivations,
|
||||
"SubsystemMessageManager"
|
||||
);
|
||||
|
||||
SubsystemMessageManager::SharedData
|
||||
SubsystemMessageManager::DefaultData(
|
||||
SubsystemMessageManager::ClassDerivations,
|
||||
Subsystem::MessageHandlers,
|
||||
Subsystem::AttributeIndex,
|
||||
Subsystem::StateCount
|
||||
);
|
||||
|
||||
SubsystemMessageManager::SubsystemMessageManager(
|
||||
Mech *owner,
|
||||
int subsystem_ID,
|
||||
SubsystemResource *sub_res,
|
||||
SharedData &shared_data
|
||||
):
|
||||
Subsystem(owner, subsystem_ID, sub_res, shared_data),
|
||||
weaponExplosions(NULL, False),
|
||||
damageInformation(NULL, False)
|
||||
{
|
||||
Check(owner);
|
||||
Check_Pointer(sub_res);
|
||||
|
||||
rendererCompensateTime = 0.0f;
|
||||
terrainHitExplosionID = ResourceDescription::NullResourceID;
|
||||
|
||||
Check_Fpu();
|
||||
}
|
||||
|
||||
SubsystemMessageManager::~SubsystemMessageManager()
|
||||
{
|
||||
}
|
||||
|
||||
Logical
|
||||
SubsystemMessageManager::TestInstance() const
|
||||
{
|
||||
return IsDerivedFrom(ClassDerivations);
|
||||
}
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
// The per-frame damage/explosion consolidation. Not yet reconstructed.
|
||||
//#############################################################################
|
||||
//
|
||||
void
|
||||
SubsystemMessageManager::ConsolidateAndSendDamage(Scalar)
|
||||
{
|
||||
Fail("SubsystemMessageManager::ConsolidateAndSendDamage -- messmgr.cpp not yet reconstructed");
|
||||
}
|
||||
|
||||
void
|
||||
SubsystemMessageManager::CreateWeaponExplosions(Logical, EntityID, Point3D)
|
||||
{
|
||||
Fail("SubsystemMessageManager::CreateWeaponExplosions -- messmgr.cpp not yet reconstructed");
|
||||
}
|
||||
|
||||
void
|
||||
SubsystemMessageManager::AddDamageMessage(Entity *, Entity__TakeDamageMessage *)
|
||||
{
|
||||
Fail("SubsystemMessageManager::AddDamageMessage -- messmgr.cpp not yet reconstructed");
|
||||
}
|
||||
|
||||
Logical
|
||||
SubsystemMessageManager::CreateStreamedSubsystem(
|
||||
ResourceFile *,
|
||||
NotationFile *,
|
||||
const char *,
|
||||
const char *,
|
||||
SubsystemResource *,
|
||||
NotationFile *,
|
||||
const ResourceDirectories *
|
||||
)
|
||||
{
|
||||
Fail("SubsystemMessageManager::CreateStreamedSubsystem -- messmgr.cpp not yet reconstructed");
|
||||
return False;
|
||||
}
|
||||
//===========================================================================//
|
||||
// File: messmgr.cpp //
|
||||
// Project: BattleTech Brick: Mech subsystems //
|
||||
// Contents: SubsystemMessageManager -- the per-mech damage/explosion hub //
|
||||
//---------------------------------------------------------------------------//
|
||||
// Copyright (C) 1995, Virtual World Entertainment, Inc. //
|
||||
// All Rights reserved worldwide //
|
||||
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL //
|
||||
//===========================================================================//
|
||||
|
||||
#include <bt.hpp>
|
||||
#pragma hdrstop
|
||||
|
||||
#if !defined(MESSMGR_HPP)
|
||||
# include <messmgr.hpp>
|
||||
#endif
|
||||
|
||||
#if !defined(MECH_HPP)
|
||||
# include <mech.hpp>
|
||||
#endif
|
||||
|
||||
#if !defined(MECHWEAP_HPP)
|
||||
# include <mechweap.hpp> // ExplosionResourceIDOf (weapon @0x3e4)
|
||||
#endif
|
||||
|
||||
#if !defined(EXPLODE_HPP)
|
||||
# include <explode.hpp> // Explosion::MakeMessage
|
||||
#endif
|
||||
|
||||
#if !defined(APP_HPP)
|
||||
# include <app.hpp> // application->Post / GetRegistry / priorities
|
||||
#endif
|
||||
|
||||
#if !defined(REGISTRY_HPP)
|
||||
# include <registry.hpp> // complete Registry type for the Post upcast
|
||||
#endif
|
||||
|
||||
Derivation
|
||||
SubsystemMessageManager::ClassDerivations(
|
||||
Subsystem::ClassDerivations,
|
||||
"SubsystemMessageManager"
|
||||
);
|
||||
|
||||
SubsystemMessageManager::SharedData
|
||||
SubsystemMessageManager::DefaultData(
|
||||
SubsystemMessageManager::ClassDerivations,
|
||||
Subsystem::MessageHandlers,
|
||||
Subsystem::AttributeIndex,
|
||||
Subsystem::StateCount
|
||||
);
|
||||
|
||||
SubsystemMessageManager::SubsystemMessageManager(
|
||||
Mech *owner,
|
||||
int subsystem_ID,
|
||||
SubsystemResource *sub_res,
|
||||
SharedData &shared_data
|
||||
):
|
||||
Subsystem(owner, subsystem_ID, sub_res, shared_data),
|
||||
weaponExplosions(NULL, True), // @0049bca4: chain ctor (0, 1) --
|
||||
// UNIQUE-keyed (one plug per explosion
|
||||
// resource; the consolidator also
|
||||
// Find()s before adding)
|
||||
damageInformation(NULL, False) // (0, 0) -- duplicate keys allowed
|
||||
{
|
||||
Check(owner);
|
||||
Check_Pointer(sub_res);
|
||||
|
||||
//
|
||||
// [T3] The binary ctor also allocates a 0x160-byte engine-internal
|
||||
// helper object at this+0xE0 (FUN_0041de1c(obj, this, 0); released in
|
||||
// the dtor by virtual op 3). It is not declared in the surviving
|
||||
// MESSMGR.HPP and nothing in the reconstructed damage path touches it;
|
||||
// STAGED until its class is identified.
|
||||
//
|
||||
|
||||
//
|
||||
// The per-frame Performance (binary @0049bca4 copies the member-pointer
|
||||
// triple @0050b6fc..0050b704 == &ConsolidateAndSendDamage).
|
||||
//
|
||||
SetPerformance(&SubsystemMessageManager::ConsolidateAndSendDamage);
|
||||
|
||||
//
|
||||
// The two streamed resource fields (res+0x30 / res+0x34).
|
||||
//
|
||||
terrainHitExplosionID = sub_res->terrainHitExplosionID;
|
||||
rendererCompensateTime = sub_res->rendererCompensateTime;
|
||||
|
||||
//
|
||||
// The common-damage record starts empty (first hit of a frame fills it).
|
||||
//
|
||||
commonDamageInformation.entityHit = NULL;
|
||||
commonDamageInformation.damageZoneIndex = -1;
|
||||
commonDamageInformation.impactPoint = Point3D::Identity;
|
||||
|
||||
Check_Fpu();
|
||||
}
|
||||
|
||||
SubsystemMessageManager::~SubsystemMessageManager()
|
||||
{
|
||||
}
|
||||
|
||||
Logical
|
||||
SubsystemMessageManager::TestInstance() const
|
||||
{
|
||||
return IsDerivedFrom(ClassDerivations);
|
||||
}
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
// AddDamageMessage -- binary @0049b6d8 (172 bytes), verified against the raw
|
||||
// decomp (part_012.c).
|
||||
//
|
||||
// Called by the damage paths while a frame runs: the FIRST hit of the frame
|
||||
// wins each field of the common record (entity, impact point, zone), and
|
||||
// every message queues a DamageInformation plug into the damage chain KEYED
|
||||
// BY ITS DAMAGE AMOUNT (the chain key is the Scalar the consolidator later
|
||||
// reads back with GetValue()).
|
||||
//#############################################################################
|
||||
//
|
||||
void
|
||||
SubsystemMessageManager::AddDamageMessage(
|
||||
Entity *damaged_entity,
|
||||
Entity__TakeDamageMessage *message
|
||||
)
|
||||
{
|
||||
Check(this);
|
||||
Check(damaged_entity);
|
||||
Check_Pointer(message);
|
||||
|
||||
if (commonDamageInformation.entityHit == NULL)
|
||||
{
|
||||
commonDamageInformation.entityHit = damaged_entity;
|
||||
}
|
||||
|
||||
//
|
||||
// The binary's origin test is an epsilon compare against the zero point
|
||||
// (FUN_004084fc(point, origin, 0.0001)).
|
||||
//
|
||||
if (Close_Enough(commonDamageInformation.impactPoint, Point3D::Identity, 0.0001f))
|
||||
{
|
||||
commonDamageInformation.impactPoint = message->damageData.impactPoint;
|
||||
}
|
||||
|
||||
if (commonDamageInformation.damageZoneIndex == -1)
|
||||
{
|
||||
commonDamageInformation.damageZoneIndex = message->damageZone;
|
||||
}
|
||||
|
||||
DamageInformation
|
||||
*info = new DamageInformation;
|
||||
Register_Object(info);
|
||||
info->damageType = message->damageData.damageType;
|
||||
info->subsystemID = message->inflictingSubsystemID;
|
||||
|
||||
damageInformation.AddValue(info, message->damageData.damageAmount);
|
||||
}
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
// ConsolidateAndSendDamage -- binary @0049b784 (796 bytes), the per-frame
|
||||
// Performance, verified against the raw decomp.
|
||||
//
|
||||
// If any damage queued this frame: build ONE TakeDamageStreamMessage for the
|
||||
// common hit entity (impact point, zone, shooter = the owning mech), append
|
||||
// {count, [damageType, damageAmount, subsystemID]...} through a dynamic
|
||||
// message -- the wire format is fixed by the ENGINE'S OWN PARSER
|
||||
// (Entity::TakeDamageStreamMessageHandler, entity.cpp: count first, then the
|
||||
// triple per record) -- bundle each firing weapon's explosion resource into
|
||||
// the explosion queue (dedup'd), Dispatch the stream at the hit entity, and
|
||||
// flush the queued explosions. Always: purge the damage chain and reset the
|
||||
// common record for the next frame.
|
||||
//#############################################################################
|
||||
//
|
||||
void
|
||||
SubsystemMessageManager::ConsolidateAndSendDamage(Scalar)
|
||||
{
|
||||
Check(this);
|
||||
|
||||
Mech
|
||||
*owner = GetEntity();
|
||||
Check(owner);
|
||||
|
||||
VChainIteratorOf<DamageInformation*, Scalar>
|
||||
damageWalk(&damageInformation);
|
||||
|
||||
damageWalk.First();
|
||||
if (damageWalk.GetCurrent() != NULL)
|
||||
{
|
||||
//
|
||||
// The engine parser reads the record count first, so count the
|
||||
// chain up front.
|
||||
//
|
||||
int
|
||||
entry_count = 0;
|
||||
for (damageWalk.First(); damageWalk.GetCurrent() != NULL; damageWalk.Next())
|
||||
{
|
||||
++entry_count;
|
||||
}
|
||||
|
||||
//
|
||||
// The consolidated stream (binary: Receiver__DynamicMessage ctor
|
||||
// FUN_00419e18(TakeDamageStreamMessageID == 0x13, sizeof == 0x34),
|
||||
// header fields written in place at +0x1c/+0x28/+0x2c).
|
||||
//
|
||||
Receiver::DynamicMessage
|
||||
stream_message(
|
||||
Entity::TakeDamageStreamMessageID,
|
||||
sizeof(Entity::TakeDamageStreamMessage)
|
||||
);
|
||||
{
|
||||
Entity::TakeDamageStreamMessage
|
||||
*header =
|
||||
(Entity::TakeDamageStreamMessage *)
|
||||
stream_message.GetMessagePointer();
|
||||
header->impactPoint = commonDamageInformation.impactPoint;
|
||||
header->damageZoneIndex = commonDamageInformation.damageZoneIndex;
|
||||
header->shootingEntity = owner->GetEntityID();
|
||||
}
|
||||
|
||||
stream_message << entry_count;
|
||||
|
||||
for (damageWalk.First(); damageWalk.GetCurrent() != NULL; damageWalk.Next())
|
||||
{
|
||||
DamageInformation
|
||||
*info = damageWalk.GetCurrent();
|
||||
|
||||
stream_message
|
||||
<< (int)info->damageType
|
||||
<< (Scalar)damageWalk.GetValue()
|
||||
<< (int)info->subsystemID;
|
||||
|
||||
//
|
||||
// Bundle the FIRING weapon's explosion (binary: blind read of
|
||||
// roster[subsystemID]+0x3e4). [T1] The binary neither range-
|
||||
// checks the id nor verifies the subsystem is a weapon; damage
|
||||
// with an unthreaded id (-1) or a non-weapon inflicter would
|
||||
// read garbage there. Guarded here -- a guarded miss queues
|
||||
// nothing, which is also what a Null resource does.
|
||||
//
|
||||
Subsystem
|
||||
*firing = NULL;
|
||||
if (
|
||||
info->subsystemID >= 0 &&
|
||||
info->subsystemID < owner->GetSubsystemCount()
|
||||
)
|
||||
{
|
||||
firing = owner->GetSubsystem(info->subsystemID);
|
||||
}
|
||||
if (
|
||||
firing != NULL &&
|
||||
firing->IsDerivedFrom(MechWeapon::ClassDerivations)
|
||||
)
|
||||
{
|
||||
ResourceDescription::ResourceID
|
||||
explosion_ID =
|
||||
((MechWeapon *)firing)->ExplosionResourceIDOf();
|
||||
if (
|
||||
explosion_ID != ResourceDescription::NullResourceID &&
|
||||
weaponExplosions.Find(explosion_ID) == NULL
|
||||
)
|
||||
{
|
||||
ResourceIDPlug
|
||||
*plug = new ResourceIDPlug(explosion_ID);
|
||||
Register_Object(plug);
|
||||
weaponExplosions.AddValue(plug, explosion_ID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// [T3] The binary computes a terrain-hit flag here:
|
||||
// entityHit->IsDerivedFrom(<Derivation @0x4e6f54>). The class at
|
||||
// that BSS address is UNRESOLVED (no static-ctor reference survives
|
||||
// in the decomp parts) -- but the flag is PROVABLY INERT:
|
||||
// CreateWeaponExplosions (@0049baa0) never reads its first
|
||||
// argument, and terrainHitExplosionID is never consumed anywhere in
|
||||
// the shipped cluster. A declared, resourced, and abandoned 1995
|
||||
// feature; False preserves shipped behavior exactly.
|
||||
//
|
||||
Logical
|
||||
terrain_hit = False;
|
||||
|
||||
if (commonDamageInformation.entityHit != NULL)
|
||||
{
|
||||
CreateWeaponExplosions(
|
||||
terrain_hit,
|
||||
commonDamageInformation.entityHit->GetEntityID(),
|
||||
commonDamageInformation.impactPoint
|
||||
);
|
||||
commonDamageInformation.entityHit->Dispatch(
|
||||
stream_message.GetMessagePointer());
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Teardown every frame (binary FUN_00417858(iter, 1)): unregister and
|
||||
// delete the consumed plugs, then reset the common record.
|
||||
//
|
||||
damageWalk.First();
|
||||
damageWalk.DeletePlugs();
|
||||
|
||||
commonDamageInformation.entityHit = NULL;
|
||||
commonDamageInformation.damageZoneIndex = -1;
|
||||
commonDamageInformation.impactPoint = Point3D::Identity;
|
||||
|
||||
Check_Fpu();
|
||||
}
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
// CreateWeaponExplosions -- binary @0049baa0 (516 bytes), verified against
|
||||
// the raw decomp. The authentic signature is the surviving MESSMGR.HPP's;
|
||||
// note terrain_hit is never read by the shipped body (see the [T3] note in
|
||||
// the consolidator).
|
||||
//
|
||||
// Flush the queued explosion resources: for each, post an
|
||||
// Explosion::MakeMessage at the Registry -- the byte-for-byte template from
|
||||
// the binary: MakeMessageID (3), sizeof 0x5c, ExplosionClassID (0x31 -- the
|
||||
// VDATA enum ordinal matches the binary exactly), no owner, the queued
|
||||
// resource, instance flags 0x100, an identity-rotation Origin at the impact
|
||||
// point, the hit entity, and the owning mech as creator. Deliveries stagger
|
||||
// 0.1s apart (the first at now+0.1) so several impacts in one frame read as
|
||||
// separate blasts. Then purge the queue.
|
||||
//#############################################################################
|
||||
//
|
||||
void
|
||||
SubsystemMessageManager::CreateWeaponExplosions(
|
||||
Logical /*terrain_hit*/,
|
||||
EntityID entity_hit,
|
||||
Point3D explode_position
|
||||
)
|
||||
{
|
||||
Check(this);
|
||||
|
||||
Mech
|
||||
*owner = GetEntity();
|
||||
Check(owner);
|
||||
|
||||
VChainIteratorOf<ResourceIDPlug*, ResourceDescription::ResourceID>
|
||||
explosionWalk(&weaponExplosions);
|
||||
|
||||
Origin
|
||||
explosion_origin(Origin::Identity);
|
||||
explosion_origin.linearPosition = explode_position;
|
||||
|
||||
Scalar
|
||||
time_step = 0.0f;
|
||||
|
||||
for (
|
||||
explosionWalk.First();
|
||||
explosionWalk.GetCurrent() != NULL;
|
||||
explosionWalk.Next()
|
||||
)
|
||||
{
|
||||
ResourceDescription::ResourceID
|
||||
explosion_ID = explosionWalk.GetValue();
|
||||
|
||||
time_step += 0.1f;
|
||||
Time
|
||||
when = Now();
|
||||
when += time_step;
|
||||
|
||||
Explosion::MakeMessage
|
||||
make_message(
|
||||
Entity::MakeMessageID,
|
||||
sizeof(Explosion::MakeMessage),
|
||||
RegisteredClass::ExplosionClassID,
|
||||
EntityID(),
|
||||
explosion_ID,
|
||||
0x100,
|
||||
explosion_origin,
|
||||
entity_hit,
|
||||
owner->GetEntityID()
|
||||
);
|
||||
|
||||
Check(application);
|
||||
application->Post(
|
||||
CreationEventPriority,
|
||||
application->GetRegistry(),
|
||||
&make_message,
|
||||
when
|
||||
);
|
||||
}
|
||||
|
||||
explosionWalk.First();
|
||||
explosionWalk.DeletePlugs();
|
||||
|
||||
Check_Fpu();
|
||||
}
|
||||
|
||||
Logical
|
||||
SubsystemMessageManager::CreateStreamedSubsystem(
|
||||
ResourceFile *,
|
||||
NotationFile *,
|
||||
const char *,
|
||||
const char *,
|
||||
SubsystemResource *,
|
||||
NotationFile *,
|
||||
const ResourceDirectories *
|
||||
)
|
||||
{
|
||||
Fail("SubsystemMessageManager::CreateStreamedSubsystem -- messmgr.cpp not yet reconstructed");
|
||||
return False;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
# MESSMGR.CPP — SubsystemMessageManager reconstruction notes
|
||||
|
||||
The per-mech damage/explosion hub: subsystems queue damage during a frame
|
||||
(`AddDamageMessage`), the Performance (`ConsolidateAndSendDamage`) folds the
|
||||
queue into ONE `TakeDamageStreamMessage` for the hit entity and flushes the
|
||||
bundled weapon explosions (`CreateWeaponExplosions`). The surviving
|
||||
MESSMGR.HPP (CODE/BT/BT) is authentic and untouched.
|
||||
|
||||
## Reconstructed 2026-08-04 (5.3.115), decomp-verified (part_012.c)
|
||||
|
||||
- **ctor @0049bca4**: `weaponExplosions(NULL, True)` — the binary constructs
|
||||
the explosion chain UNIQUE-keyed (0049bfde(.., 0, 1)); the damage chain
|
||||
allows duplicate keys. Installs `ConsolidateAndSendDamage` as the
|
||||
Performance (member-pointer triple @0050b6fc); copies the two streamed
|
||||
resource fields (res+0x30/+0x34); zeros the common record.
|
||||
[T3] The binary also allocates a 0x160-byte engine-internal helper at
|
||||
this+0xE0 (FUN_0041de1c) — not declared in the surviving header, untouched
|
||||
by the reconstructed paths, STAGED until identified.
|
||||
- **AddDamageMessage @0049b6d8**: first hit of the frame wins each common
|
||||
field (entity / impact point via epsilon-against-origin / zone); every
|
||||
message queues a `DamageInformation` plug KEYED BY DAMAGE AMOUNT (the
|
||||
chain key is read back with `GetValue()` at consolidation).
|
||||
- **ConsolidateAndSendDamage @0049b784**: wire format pinned by the ENGINE'S
|
||||
OWN PARSER (`Entity::TakeDamageStreamMessageHandler`, RP ENTITY.CPP:855):
|
||||
a `Receiver::DynamicMessage(TakeDamageStreamMessageID == 0x13, sizeof ==
|
||||
0x34)` with header {impactPoint@+0x1c, damageZoneIndex@+0x28,
|
||||
shootingEntity@+0x2c}, then `<< count`, then `<< type << amount <<
|
||||
subsystemID` per record. Explosion bundling reads the FIRING weapon's
|
||||
resource (binary: blind `roster[subsystemID]+0x3e4`); [T1] guarded here
|
||||
(range + IsDerivedFrom(MechWeapon)) — the binary is unguarded and relies
|
||||
on well-formed feeders. Dispatches the stream directly at the hit entity,
|
||||
flushes explosions, purges with `DeletePlugs()` (binary FUN_00417858(,1) —
|
||||
the explicit teardown), resets the commons.
|
||||
- **CreateWeaponExplosions @0049baa0**: the authentic HEADER signature
|
||||
(terrain_hit, entity_hit, explode_position) — the BT411 donor's
|
||||
"SendQueuedExplosions" rename was port drift. Per queued resource:
|
||||
`Explosion::MakeMessage(MakeMessageID(3), sizeof(0x5c),
|
||||
RegisteredClass::ExplosionClassID(0x31 — our VDATA enum ordinal matches
|
||||
the binary byte-for-byte), no owner, resourceID, instanceFlags 0x100,
|
||||
Origin{impact position, identity rotation}, entity_hit, owner mech)`,
|
||||
posted at `CreationEventPriority(3)` to `application->GetRegistry()` with
|
||||
delivery `Now() + 0.1s × n` (impacts in one frame stagger). Then purge.
|
||||
|
||||
## The inert terrain-hit flag (recorded, not tuned)
|
||||
|
||||
The binary computes `terrain_hit = entityHit->IsDerivedFrom(<Derivation @
|
||||
0x4e6f54>)` before calling CreateWeaponExplosions. The class at 0x4e6f54 is
|
||||
UNRESOLVED (BSS object; no static-ctor reference survives in the decomp
|
||||
parts). It does not matter: @0049baa0 NEVER READS its first argument, and
|
||||
`terrainHitExplosionID` is never consumed anywhere in the shipped cluster —
|
||||
a declared, resourced, abandoned 1995 feature. We pass `False` [T3];
|
||||
shipped behavior is preserved exactly. If the constant is ever resolved
|
||||
(candidates: a terrain/cultural class), only the dead flag changes.
|
||||
|
||||
## Status / staging
|
||||
|
||||
- `CreateStreamedSubsystem` @0049be10 remains a Fail stub — the authoring-
|
||||
side CSS family (8 fns) is batched separately; missions load prebuilt
|
||||
streams from BTL4.RES and never call it.
|
||||
- **No feeders yet**: nothing in source410 calls `AddDamageMessage` (the
|
||||
weapon damage paths still dispatch TakeDamage directly), so the
|
||||
consolidator runs empty every frame — verified live (5-period soak,
|
||||
wheel turning, no faults). When the weapon-fire damage path is rebased
|
||||
onto the manager, this TU is ready for it.
|
||||
Reference in New Issue
Block a user