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>
405 lines
12 KiB
C++
405 lines
12 KiB
C++
//===========================================================================//
|
|
// 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;
|
|
}
|