The interactive 2-node playtest wave -- every fix decomp-grounded and live-verified: COLLISION ECONOMY (the ram one-shot): StaticBounce mutates worldLinearVelocity per contact and ProcessCollisionList walks EVERY touched solid per frame; with 2007 terrain-as-solids the reflections compounded x4-x40 within one frame and a walking bump one-shot a pristine mech for 112,375 pts (62-pt authentic economy). Fix: frameEntryWorldVelocity restore per contact (damage always priced at the real approach speed -- all the binary's physics ever saw); Mech::Reset zeroes the mover motion (respawn = teleport); [collide-tx]/[mp-hdlr] telemetry. Gotcha #16 (engine-facility drift class). MISSILES: peer-visible salvos (the launcher record extension carries a salvo counter + aim point; ForceUpdate actually enqueues it -- the dirty flag alone never serialized), the authentic arc (authored MuzzleVelocity vector + the Seeker's 200m/0.1/300 loft + gain-4 steering, decoded from @004beae4/@004bef78), world-impact bursts (rounds detonate on cave geometry instead of phasing through), contact-only damage (flight-cap expiry = fizzle, no more teleport damage), live re-lead, and ballistic (unguided) shells for autocannons. projweap's stale BTPushProjectile extern (the /FORCE signature trap, gotcha #6 corollary) crashed the Avatar's first AFC100 shot -- fixed + sweep rule recorded. RADAR: two transcription bugs made the scope permanently empty -- FUN_0040b244 is the affine INVERSE (not a copy) and FUN_0040adec writes ONLY the 3x3 rotation (never the translation); worldToView now Invert(view) built rotation-first. CulturalIcons sorted out of the moving grid (the phantom red pips were map props), visible-radius culls on all three draw passes, live pip verified at |delta| x ppm px. Gotcha #17 (verify the FUN_ body, not its call shape). WEAPON PANELS (the frozen-dial hunt): the binary's *(subsystem+0x40) means FAILED -- the recon's 'operating' name was backwards, inverting the destroyed-X lamps, the panel look, the children enable and the ready-lamp gate (which had NEVER executed). Polarity chain corrected end-to-end (failedState, fed by real damage saturation). Root cause of the freezes: MFD page-mode gating -- the dev composite shows ALL pages at once, so off-page dials legitimately stopped; under BT_DEV_GAUGES the 15 page-plane bits stay active (the exclusive secondary trio untouched). The SEH gauge guard now names its kills; repaint-heal resets the incremental arc after panel repaints; [panel]/[arc] probes added. COMM/SCORE: MessageBoard LIVE (the engine already shipped the whole Player__StatusMessage queue; wired the binary's one producer -- the kill branch, victim's name, 6s -- plus the consumer bridge and a lazy source bind); MP DEATHS counted via the observed-death tally (each node scores every pilot from locally observed events, the same model as the KILLS credit) and the -2/-1 engine seed clamped for display. DEV UX: node-tagged window titles (-net port), gauge panel reworked (1320x480, true 4:3 MFD cells, the portrait secondary UNROTATED upright, linear filtering, BT_GAUGE_SCALE), fixed close spawns via BT_SPAWN_XZ, Boreas flies an Avatar (first second-chassis live outing). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
594 lines
23 KiB
C++
594 lines
23 KiB
C++
//===========================================================================//
|
|
// File: messmgr.cpp //
|
|
// Project: BattleTech //
|
|
// Contents: Consolidates Messages from Subsystems and Graphics Generation //
|
|
//---------------------------------------------------------------------------//
|
|
// Date Who Modification //
|
|
// -------- --- ---------------------------------------------------------- //
|
|
// 09/30/95 JM Initial coding. //
|
|
//---------------------------------------------------------------------------//
|
|
// Copyright (C) 1995, Virtual World Entertainment, Inc. All Rights reserved //
|
|
// PROPRIETARY AND CONFIDENTIAL //
|
|
//===========================================================================//
|
|
//
|
|
// RECONSTRUCTED from the shipped binary. Behaviour follows the Ghidra
|
|
// pseudo-C cluster @0049b6d8..@0049c3e3 (recovered/all/part_012.c) and the
|
|
// surviving MESSMGR.HPP. Each non-trivial method cites its @ADDR.
|
|
//
|
|
// The SubsystemMessageManager is the per-mech message hub: it accumulates
|
|
// damage / weapon-explosion notifications from the other subsystems during a
|
|
// frame (AddDamageMessage), then, when its Performance fires
|
|
// (ConsolidateAndSendDamage), it folds them into the common-damage record,
|
|
// re-broadcasts TakeDamage messages to the affected entities, and emits the
|
|
// queued explosion graphics to the renderer.
|
|
//
|
|
// Object layout (this+offset), recovered from the ctor/dtor:
|
|
// this[0x07] @0x1C activePerformance (Simulation base; set to
|
|
// ConsolidateAndSendDamage)
|
|
// this[0x34] @0xD0 owner Mech* (Subsystem base)
|
|
// this[0x38] @0xE0 message/graphics helper object (0x160 bytes, created
|
|
// in ctor; engine-internal,
|
|
// NOT declared in MESSMGR.HPP)
|
|
// this[0x39] @0xE4 commonDamageInformation.entityHit
|
|
// this[0x3A] @0xE8 commonDamageInformation.damageZoneIndex
|
|
// this[0x3B] @0xEC commonDamageInformation.impactPoint (Point3D)
|
|
// this[0x3E] @0xF8 rendererCompensateTime
|
|
// this[0x3F] @0xFC weaponExplosions (VChainOf, link vtable @0050b938)
|
|
// this[0x45] @0x114 damageInformation (VChainOf, link vtable @0050b91c)
|
|
// this[0x4B] @0x12C terrainHitExplosionID
|
|
//
|
|
// Helper-function name mapping (engine internals referenced by the decomp):
|
|
// FUN_0041c52c Subsystem base constructor
|
|
// FUN_0041c648 Subsystem destructor
|
|
// FUN_0041de1c message-helper-object constructor (0x160-byte object @0xE0)
|
|
// FUN_00402298 operator new(size) FUN_004022d0 operator delete
|
|
// FUN_00415e90 Plug constructor FUN_00415ed8 Plug destructor
|
|
// FUN_00408440 Point3D::operator= / copy FUN_004084fc Point3D::AlmostEqual
|
|
// FUN_00419e18 <message>::ctor(classID,size)
|
|
// FUN_0041a1a4 IsDerivedFrom(classDerivations) / name compare
|
|
// FUN_00420ea4 Point3D copy into message field
|
|
// FUN_004336f0 build renderer command record
|
|
// FUN_0041acbc GraphicsPipe::Submit(...)
|
|
// FUN_00424716 PlugOf<ResourceID>::ctor
|
|
// VChain helpers: 0049bfde/0049c123 (chain ctors), 0049c001/0049c146
|
|
// (chain dtors), 0049c02d/0049c05c/0049c172/0049c1a3
|
|
// (key comparators), 0049c37e/0049c3ae (link allocators).
|
|
//
|
|
// Constants:
|
|
// message ClassID 0x13 (damage/notify message, vtable @0050b984, size 0x34)
|
|
// explosion ClassID 0x5C, sub-type 3 (queued weapon explosion record)
|
|
// 0.1f (DAT @0049b784 frame) = per-explosion render time step
|
|
//
|
|
#include <bt.hpp>
|
|
#include <mechweap.hpp> // MechWeapon::GetExplosionResourceID (task #7 explosion bundling)
|
|
#include <EXPLODE.hpp> // Explosion::Make (the established explosion port)
|
|
#pragma hdrstop
|
|
|
|
#if !defined(MESSMGR_HPP)
|
|
# include <messmgr.hpp>
|
|
#endif
|
|
#if !defined(MECH_HPP)
|
|
# include <mech.hpp>
|
|
#endif
|
|
#if !defined(APP_HPP)
|
|
# include <app.hpp>
|
|
#endif
|
|
|
|
//
|
|
// `Origin3D` (DAT_004e0f80) is the zero point. The engine point is
|
|
// `Point3D::Identity` (== 0,0,0); Point3D has no `AlmostEqual`, so compare the
|
|
// components directly. (Used to detect "no impact point recorded yet".)
|
|
//
|
|
static Logical
|
|
PointIsOrigin(const Point3D &p)
|
|
{
|
|
return p.x == 0.0f && p.y == 0.0f && p.z == 0.0f;
|
|
}
|
|
|
|
|
|
//#############################################################################
|
|
// Shared Data Support
|
|
//
|
|
// SubsystemMessageManager derives DIRECTLY from Subsystem, which exposes the
|
|
// parent derivation via the function `Subsystem::GetClassDerivations()`
|
|
// (returns a Derivation*), not a `ClassDerivations` object -- so no '&' here.
|
|
Derivation
|
|
SubsystemMessageManager::ClassDerivations( // static @0050b6cc
|
|
Subsystem::GetClassDerivations(),
|
|
"SubsystemMessageManager"
|
|
);
|
|
|
|
Receiver::MessageHandlerSet
|
|
SubsystemMessageManager::MessageHandlers;
|
|
|
|
SubsystemMessageManager::AttributeIndexSet
|
|
SubsystemMessageManager::AttributeIndex;
|
|
|
|
// Real Simulation__SharedData ctor: (Derivation*, MessageHandlerSet&,
|
|
// AttributeIndexSet&, int stateCount). StateCount resolves to
|
|
// Simulation::StateCount. The default activePerformance (@0050b6fc ->
|
|
// 0049b784 ConsolidateAndSendDamage) is installed in the ctor body instead.
|
|
SubsystemMessageManager::SharedData
|
|
SubsystemMessageManager::DefaultData(
|
|
&SubsystemMessageManager::ClassDerivations,
|
|
SubsystemMessageManager::MessageHandlers,
|
|
SubsystemMessageManager::AttributeIndex,
|
|
SubsystemMessageManager::StateCount
|
|
);
|
|
|
|
|
|
//#############################################################################
|
|
// Construction / Destruction
|
|
//#############################################################################
|
|
//
|
|
// @0049bca4 -- chains to the Subsystem base ctor, installs the vtable
|
|
// (@0050b954), constructs both message chains in place, allocates the
|
|
// message-helper object, installs the default Performance, and copies the two
|
|
// streamed resource fields.
|
|
//
|
|
SubsystemMessageManager::SubsystemMessageManager(
|
|
Mech *owner,
|
|
int subsystem_ID,
|
|
SubsystemResource *sub_res,
|
|
SharedData &shared_data
|
|
):
|
|
Subsystem(owner, subsystem_ID, sub_res, shared_data), // FUN_0041c52c
|
|
weaponExplosions(NULL, True), // @0xFC unique-keyed by explosion id
|
|
damageInformation(NULL, False) // @0x114 keyed by damage amount
|
|
{
|
|
Check(owner);
|
|
Check_Pointer(sub_res);
|
|
|
|
//
|
|
// In-place construction of the two VChains (the ctor calls the chain
|
|
// header constructors directly: weaponExplosions @0049bfde, with one
|
|
// pre-allocated link; damageInformation @0049c123).
|
|
//
|
|
// weaponExplosions : VChainOf<ResourceIDPlug*, ResourceID> @0xFC
|
|
// damageInformation : VChainOf<DamageInformation*, Scalar> @0x114
|
|
//
|
|
|
|
// Engine-internal message/graphics helper (0x160-byte object) parked at
|
|
// +0xE0. Created via FUN_0041de1c(obj, this, 0); released in the dtor by
|
|
// virtual call (op==3). Not a MESSMGR.HPP member -- best-effort.
|
|
// this[0x38] = new <messageHelper>(this);
|
|
|
|
// Default Performance := ConsolidateAndSendDamage (this[0x07..0x09] copied
|
|
// from the constants @0050b6fc/0050b700/0050b704).
|
|
SetPerformance(&SubsystemMessageManager::ConsolidateAndSendDamage);
|
|
|
|
terrainHitExplosionID = sub_res->terrainHitExplosionID; // this[0x4B] = sub_res+0x30
|
|
rendererCompensateTime = sub_res->rendererCompensateTime; // this[0x3E] = sub_res+0x34
|
|
|
|
commonDamageInformation.entityHit = 0; // this[0x39]
|
|
commonDamageInformation.damageZoneIndex = -1; // this[0x3A]
|
|
commonDamageInformation.impactPoint = Point3D::Identity; // this[0x3B] = DAT_004e0f80
|
|
|
|
Check_Fpu();
|
|
}
|
|
|
|
//
|
|
// @0049bd7c (vtable slot 0) -- if the message-helper object exists, release it
|
|
// (virtual op 3); destroy both message chains; chain to ~Subsystem.
|
|
//
|
|
SubsystemMessageManager::~SubsystemMessageManager()
|
|
{
|
|
Check(this);
|
|
|
|
// release this[0x38] message-helper object (virtual destroy, op==3)
|
|
// damageInformation.~VChainOf(); // @0049c146
|
|
// weaponExplosions.~VChainOf(); // @0049c001
|
|
|
|
Check_Fpu();
|
|
}
|
|
|
|
//
|
|
// @0049be10 -- SubsystemMessageManager::CreateStreamedSubsystem.
|
|
// Reached from the mech3.cpp CreateSubsystemStream factory when the component
|
|
// type string == "SubsystemMessageManager". Stamps ClassID 0xBD3 / model
|
|
// record size 0x38, then reads the two manager-specific resource fields.
|
|
//
|
|
Logical
|
|
SubsystemMessageManager::CreateStreamedSubsystem(
|
|
ResourceFile *resource_file,
|
|
NotationFile *model_file,
|
|
const char *model_name,
|
|
const char *subsystem_name,
|
|
SubsystemResource *subsystem_resource,
|
|
NotationFile *subsystem_file,
|
|
const ResourceDirectories *directories
|
|
)
|
|
{
|
|
if (
|
|
!Subsystem::CreateStreamedSubsystem(
|
|
model_file, model_name, subsystem_name,
|
|
subsystem_resource, subsystem_file, directories
|
|
)
|
|
)
|
|
{
|
|
return False;
|
|
}
|
|
|
|
subsystem_resource->classID = (RegisteredClass::ClassID)0xBD3; // resource+0x20
|
|
subsystem_resource->subsystemModelSize = 0x38; // resource+0x24
|
|
|
|
//
|
|
// "TerrainHitExplosion" -- a resource name that must resolve to a model in
|
|
// resource_file; the resolved ResourceID is stored at resource+0x30.
|
|
//
|
|
const char *terrainHitName;
|
|
if (!subsystem_file->GetEntry(subsystem_name, "TerrainHitExplosion", &terrainHitName))
|
|
{
|
|
std::cerr << subsystem_name << " missing TerrainHitExplosion!\n";
|
|
return False;
|
|
}
|
|
ResourceDescription *rd = // FUN_00406ff8
|
|
resource_file->FindResourceDescription(
|
|
terrainHitName,
|
|
ResourceDescription::GameModelResourceType // recovered type literal 1
|
|
);
|
|
if (rd == 0)
|
|
{
|
|
std::cerr << subsystem_name << " cannot find " << terrainHitName
|
|
<< " in resource file!\n";
|
|
return False;
|
|
}
|
|
subsystem_resource->terrainHitExplosionID = rd->resourceID; // resource+0x30
|
|
|
|
//
|
|
// "RendererCompensateTime" -- a Scalar stored at resource+0x34.
|
|
//
|
|
if (!subsystem_file->GetEntry(subsystem_name, "RendererCompensateTime",
|
|
&subsystem_resource->rendererCompensateTime)) // resource+0x34
|
|
{
|
|
std::cerr << subsystem_name << " missing RendererCompensateTime\n";
|
|
return False;
|
|
}
|
|
|
|
return True;
|
|
}
|
|
|
|
//
|
|
// TestInstance -- emitted inline by the shipped build (no standalone body in
|
|
// the cluster). Standard subsystem form.
|
|
//
|
|
Logical
|
|
SubsystemMessageManager::TestInstance() const
|
|
{
|
|
Check(this);
|
|
return IsDerivedFrom(ClassDerivations);
|
|
}
|
|
|
|
|
|
//#############################################################################
|
|
// AddDamageMessage @0049b6d8
|
|
//#############################################################################
|
|
//
|
|
// Called by other subsystems / the damage path when an entity takes damage.
|
|
// Records the first hit's common information (entity, zone, impact point) and
|
|
// queues a per-message DamageInformation Plug into the damageInformation
|
|
// chain, keyed by the message's timestamp (Scalar at message+0x30).
|
|
//
|
|
void
|
|
SubsystemMessageManager::AddDamageMessage(
|
|
Entity *damaged_entity,
|
|
Entity__TakeDamageMessage *message
|
|
)
|
|
{
|
|
// First damaged entity of the frame wins the common record.
|
|
if (commonDamageInformation.entityHit == 0)
|
|
{
|
|
commonDamageInformation.entityHit = damaged_entity;
|
|
}
|
|
|
|
// First non-origin impact point of the frame wins. The recovered struct
|
|
// fields live inside the embedded Damage record (Entity__TakeDamageMessage::
|
|
// damageData): impactPoint @message+0x4C, damageType @+0x2C, amount @+0x30.
|
|
if (PointIsOrigin(commonDamageInformation.impactPoint)) // FUN_004084fc
|
|
{
|
|
commonDamageInformation.impactPoint = message->damageData.impactPoint; // message+0x4C
|
|
}
|
|
|
|
// First valid damage-zone index of the frame wins.
|
|
if (commonDamageInformation.damageZoneIndex == -1)
|
|
{
|
|
commonDamageInformation.damageZoneIndex = message->damageZone; // message+0x24
|
|
}
|
|
|
|
//
|
|
// Queue a DamageInformation plug (size 0x14: Plug + damageType + subsystemID),
|
|
// inserted into the damageInformation chain keyed by message+0x30 (the damage
|
|
// amount -- the Scalar immediately after damageType inside the Damage record).
|
|
//
|
|
DamageInformation *info = new DamageInformation; // FUN_00402298(0x14)
|
|
if (info != 0)
|
|
{
|
|
// Plug base init (FUN_00415e90 / vtable @0050b9a8)
|
|
info->damageType = message->damageData.damageType; // puVar2[3] = message+0x2C
|
|
info->subsystemID = message->inflictingSubsystemID; // puVar2[4] = message+0x5C
|
|
}
|
|
|
|
damageInformation.AddValue(info, message->damageData.damageAmount); // chain+0x114, key = message+0x30
|
|
}
|
|
|
|
|
|
//#############################################################################
|
|
// ConsolidateAndSendDamage @0049b784 (Performance)
|
|
//#############################################################################
|
|
//
|
|
// The per-frame Performance (installed as activePerformance by the ctor).
|
|
// Walks the queued DamageInformation chain, re-broadcasting a consolidated
|
|
// damage/notify message (ClassID 0x13, vtable @0050b984) for the common
|
|
// entity, queues a terrain-hit explosion resource into weaponExplosions when
|
|
// the hit zone has no explosion yet, flushes the explosion graphics to the
|
|
// renderer (SendQueuedExplosions @0049baa0), then clears the common record and
|
|
// empties the damage chain.
|
|
//
|
|
void
|
|
SubsystemMessageManager::ConsolidateAndSendDamage(Scalar /*time_slice*/)
|
|
{
|
|
Mech *owner = GetEntity(); // this[0x34] @0xD0
|
|
|
|
VChainIteratorOf<DamageInformation*, Scalar>
|
|
damageWalk(&damageInformation); // FUN_0049c28b over this+0x114
|
|
|
|
// VChainIteratorOf has no IsDone(); GetCurrent() returns NULL past the end.
|
|
damageWalk.First();
|
|
if (damageWalk.GetCurrent() != 0)
|
|
{
|
|
//
|
|
// THE CONSOLIDATED SEND (task #7 tail; this was a "recovered note"
|
|
// stub): build the Entity::TakeDamageStreamMessage (id 0x13, base
|
|
// 0x34 -- ENTITY3.h:189, byte-for-byte the binary's layout), append
|
|
// {count, [damageType, damageAmount, subsystemID]...} through the
|
|
// dynamic message (the binary's msg-vtable slot-8 append,
|
|
// FUN_00419e60), and Dispatch it at the hit entity -- whose T0
|
|
// handler (ENTITY.cpp:817) re-splits it into per-record
|
|
// TakeDamageMessages. A REPLICANT victim's Dispatch reroutes the
|
|
// stream cross-pod exactly as the old direct path did (task #47).
|
|
//
|
|
int entryCount = 0;
|
|
for (damageWalk.First(); damageWalk.GetCurrent() != 0; damageWalk.Next())
|
|
{
|
|
++entryCount;
|
|
}
|
|
|
|
Entity::DynamicMessage streamMessage(
|
|
Entity::TakeDamageStreamMessageID,
|
|
sizeof(Entity::TakeDamageStreamMessage));
|
|
{
|
|
Entity::TakeDamageStreamMessage *header =
|
|
(Entity::TakeDamageStreamMessage *)streamMessage.GetMessagePointer();
|
|
header->impactPoint = commonDamageInformation.impactPoint;
|
|
header->damageZoneIndex = commonDamageInformation.damageZoneIndex;
|
|
header->shootingEntity = owner->GetEntityID();
|
|
}
|
|
streamMessage << entryCount;
|
|
|
|
for (damageWalk.First(); damageWalk.GetCurrent() != 0; damageWalk.Next())
|
|
{
|
|
DamageInformation *info = damageWalk.GetCurrent();
|
|
|
|
streamMessage << (int)info->damageType
|
|
<< (Scalar)damageWalk.GetValue() // the chain KEY = damageAmount
|
|
<< (int)info->subsystemID;
|
|
|
|
//
|
|
// Bundle the FIRING weapon's explosion: the record's subsystemID
|
|
// is the inflicting weapon on the OWNER's roster (ENTITY3.h:143's
|
|
// documented purpose; binary reads roster[id]+0x3E4). Non-weapon
|
|
// / unthreaded (-1) ids resolve Null and queue nothing.
|
|
//
|
|
Subsystem *firingWeapon = (info->subsystemID >= 0
|
|
&& info->subsystemID < owner->GetSubsystemCount())
|
|
? owner->GetSubsystem(info->subsystemID) : 0;
|
|
ResourceDescription::ResourceID explosionID =
|
|
ResolveExplosionID(firingWeapon); // weapon+0x3E4
|
|
if (getenv("BT_FIRE_LOG"))
|
|
DEBUG_STREAM << "[boom-q] subsysID=" << info->subsystemID
|
|
<< " weapon=" << (firingWeapon ? firingWeapon->GetName() : "<none>")
|
|
<< " explosionID=" << explosionID << std::endl;
|
|
if (explosionID != ResourceDescription::NullResourceID
|
|
&& !weaponExplosions.Find(explosionID)) // chain+0xFC, slot 0xC
|
|
{
|
|
ResourceIDPlug *plug = new ResourceIDPlug(explosionID);
|
|
weaponExplosions.AddValue(plug, explosionID); // chain+0xFC, slot 8
|
|
}
|
|
}
|
|
|
|
//
|
|
// Deliver the consolidated stream to the hit entity.
|
|
//
|
|
if (commonDamageInformation.entityHit != 0)
|
|
{
|
|
Entity::TakeDamageStreamMessage *finalMessage =
|
|
(Entity::TakeDamageStreamMessage *)streamMessage.GetMessagePointer();
|
|
if (getenv("BT_MSGMGR_LOG"))
|
|
DEBUG_STREAM << "[msgmgr] consolidated " << entryCount
|
|
<< " records -> " << (void *)commonDamageInformation.entityHit
|
|
<< " len=" << (int)finalMessage->messageLength
|
|
<< " zone=" << commonDamageInformation.damageZoneIndex
|
|
<< std::endl;
|
|
commonDamageInformation.entityHit->Dispatch(finalMessage);
|
|
}
|
|
|
|
// Render queued weapon explosions and tear down the message.
|
|
SendQueuedExplosions(); // @0049baa0
|
|
}
|
|
|
|
// PURGE the consumed queue -- the binary's explicit teardown
|
|
// (FUN_00417858(iter, 1)). The old "cleared as the iterator is
|
|
// destroyed" note was WRONG: nothing purges automatically, so stale
|
|
// records re-applied every tick (observed live: counts 1->2->3->4,
|
|
// damage double-counted). The nodes are heap plugs from
|
|
// AddDamageMessage -> delete them here.
|
|
for (damageWalk.First(); damageWalk.GetCurrent() != 0; damageWalk.First())
|
|
{
|
|
DamageInformation *consumed = damageWalk.GetCurrent();
|
|
damageWalk.Remove();
|
|
delete consumed;
|
|
}
|
|
|
|
// Reset common record and empty the damage chain for the next frame.
|
|
commonDamageInformation.entityHit = 0; // this[0x39]
|
|
commonDamageInformation.damageZoneIndex = -1; // this[0x3A]
|
|
commonDamageInformation.impactPoint = Point3D::Identity; // this[0x3B]
|
|
}
|
|
|
|
|
|
//#############################################################################
|
|
// SendQueuedExplosions @0049baa0 (file-local helper)
|
|
//#############################################################################
|
|
//
|
|
// Drains the weaponExplosions chain, emitting one renderer command per queued
|
|
// explosion (ClassID 0x5C, sub-type 3) at an increasing time step (0.1f per
|
|
// explosion) so multiple impacts in one frame are staggered. Submitted via
|
|
// the global graphics pipe (DAT_004efc94+0x60).
|
|
//
|
|
// NOTE: not declared in MESSMGR.HPP -- the header's protected
|
|
// CreateWeaponExplosions(terrain_hit, entity_hit, explode_position) is the
|
|
// per-explosion *queueing* helper (inlined into ConsolidateAndSendDamage
|
|
// above); this routine is the per-frame *flush* of the accumulated queue.
|
|
// Name is best-effort.
|
|
//
|
|
void
|
|
SubsystemMessageManager::SendQueuedExplosions()
|
|
{
|
|
Check(this);
|
|
|
|
VChainIteratorOf<ResourceIDPlug*, ResourceDescription::ResourceID>
|
|
explosionWalk(&weaponExplosions); // FUN_0049c314 over this+0xFC
|
|
|
|
// Base time was SystemClock Now() (FUN_00414b60); derive it from the last
|
|
// performance time so no global clock accessor is required here.
|
|
Scalar baseTime =
|
|
(Scalar)lastPerformance.ticks / (Scalar)SystemClock::GetTicksPerSecond();
|
|
Scalar step = 0.0f;
|
|
const Scalar stepIncrement = 0.1f; // local_c
|
|
|
|
// VChainIteratorOf has no IsDone(); GetCurrent() returns NULL past the end.
|
|
for (explosionWalk.First(); explosionWalk.GetCurrent() != 0; explosionWalk.Next())
|
|
{
|
|
ResourceDescription::ResourceID id = explosionWalk.GetValue(); // link+0x1C (the key)
|
|
|
|
//
|
|
// Each queued explosion is fired at an increasing time step (0.1s each)
|
|
// so multiple impacts in one frame stagger.
|
|
//
|
|
step += stepIncrement;
|
|
Scalar fireTime = baseTime + step;
|
|
|
|
SubmitExplosion(id, fireTime); // CROSS-FAMILY renderer submission
|
|
}
|
|
|
|
// PURGE the flushed queue (same explicit-teardown correction as the
|
|
// damage chain above; the plugs are heap nodes).
|
|
for (explosionWalk.First(); explosionWalk.GetCurrent() != 0; explosionWalk.First())
|
|
{
|
|
ResourceIDPlug *flushed = explosionWalk.GetCurrent();
|
|
explosionWalk.Remove();
|
|
delete flushed;
|
|
}
|
|
}
|
|
|
|
|
|
//#############################################################################
|
|
// ResolveExplosionID -- the real read (task #7 tail)
|
|
//#############################################################################
|
|
//
|
|
// The binary reads the FIRING weapon's explosion resource: roster[subsystemID]
|
|
// + 0x3E4 (the streamed ExplosionModelFile). The old stand-in returned
|
|
// terrainHitExplosionID and framed the record as "the damaged subsystem" --
|
|
// both wrong (records are the OWNER's inflicting weapons). Guarded: the
|
|
// binary's submitters are all weapons; our reachability is wider.
|
|
//
|
|
ResourceDescription::ResourceID
|
|
SubsystemMessageManager::ResolveExplosionID(Subsystem *subsystem)
|
|
{
|
|
if (subsystem != 0
|
|
&& subsystem->IsDerivedFrom(*MechWeapon::GetClassDerivations()))
|
|
{
|
|
return ((MechWeapon *)subsystem)->GetExplosionResourceID(); // +0x3E4
|
|
}
|
|
return ResourceDescription::NullResourceID;
|
|
}
|
|
|
|
//
|
|
// SubmitExplosion -- the real spawn (task #7 tail). The binary posts a
|
|
// Registry::MakeEntityMessage (id 3, class 0x31 Explosion, flags 0x100) at
|
|
// Now + 0.1s x queue-ordinal (the caller's fire_time) so multi-hit impacts
|
|
// fire sequentially. The port spawns through the established Explosion::Make
|
|
// port at the consolidated impact point. [T3: the 0.1s stagger is computed
|
|
// by the caller but not yet honoured -- timed entity posts need the registry
|
|
// queue; the visual difference on multi-weapon volleys is a subtle ripple.]
|
|
//
|
|
void
|
|
SubsystemMessageManager::SubmitExplosion(
|
|
ResourceDescription::ResourceID explosion_id,
|
|
Scalar /*fire_time*/
|
|
)
|
|
{
|
|
Check(this);
|
|
|
|
Origin o;
|
|
o.linearPosition = commonDamageInformation.impactPoint;
|
|
o.angularPosition = EulerAngles(Radian(0.0f), Radian(0.0f), Radian(0.0f));
|
|
|
|
Mech *owner = GetEntity();
|
|
EntityID hitID = (commonDamageInformation.entityHit != 0)
|
|
? commonDamageInformation.entityHit->GetEntityID()
|
|
: owner->GetEntityID();
|
|
|
|
Explosion::MakeMessage m(
|
|
Explosion::MakeMessageID, sizeof(Explosion::MakeMessage),
|
|
(Entity::ClassID)RegisteredClass::ExplosionClassID, EntityID::Null,
|
|
explosion_id, Explosion::DefaultFlags, o,
|
|
hitID, owner->GetEntityID());
|
|
Explosion *e = Explosion::Make(&m);
|
|
if (getenv("BT_FIRE_LOG"))
|
|
DEBUG_STREAM << "[boom] id=" << explosion_id
|
|
<< " at(" << o.linearPosition.x << "," << o.linearPosition.y
|
|
<< "," << o.linearPosition.z << ") made=" << (void *)e << std::endl;
|
|
if (e != 0)
|
|
{
|
|
Register_Object(e);
|
|
}
|
|
}
|
|
|
|
|
|
//#############################################################################
|
|
// DeathReset / ResetToInitialState
|
|
//#############################################################################
|
|
//
|
|
// vtable slot 10 @0049c3de is an empty body (the shipped build folds the
|
|
// trivial ResetToInitialState / DeathReset(Logical){} override to a bare
|
|
// "return"). Declared inline in MESSMGR.HPP (DeathReset); reproduced here for
|
|
// completeness.
|
|
//
|
|
// void SubsystemMessageManager::DeathReset(Logical) {} // inline, see header
|
|
|
|
|
|
//#############################################################################
|
|
// DamageInformation::~DamageInformation @0049c3e3
|
|
//#############################################################################
|
|
//
|
|
// The nested DamageInformation : Plug has a single virtual (its destructor,
|
|
// vtable @0050b9a8). Chains to ~Plug (FUN_00415ed8); the Plug has no owned
|
|
// resources beyond the base.
|
|
//
|
|
// SubsystemMessageManager::DamageInformation::~DamageInformation() {} // @0049c3e3
|
|
|
|
//###########################################################################
|
|
// CreateMessageManagerSubsystem -- factory bridge (task #7)
|
|
//
|
|
// mech.cpp's streamed-subsystem factory (case 0xBD3) calls this in the
|
|
// house complete-type-TU pattern (cf. CreateEmitterSubsystem et al).
|
|
//
|
|
Subsystem *CreateMessageManagerSubsystem(Mech *owner, int id, void *seg)
|
|
{
|
|
return new (Memory::Allocate(0x130)) SubsystemMessageManager(
|
|
owner, id,
|
|
(SubsystemMessageManager::SubsystemResource *)seg,
|
|
SubsystemMessageManager::DefaultData);
|
|
}
|