Combat: the AUTHENTIC consolidated damage delivery -- TakeDamageStream through the message manager (task #7 tail)
ConsolidateAndSendDamage @0049b784 fully implemented: the beam path submits
into AddDamageMessage; each messmgr tick builds ONE Entity::
TakeDamageStreamMessage (id 0x13; wire-verified 0x34+4+Nx12) with the
common impact record + appended {type, amount, subsystemID} entries and
Dispatches it at the victim -- the T0 handler (ENTITY.cpp:817) re-splits
into per-record TakeDamage; replicants reroute cross-pod. Real
ResolveExplosionID (firing weapon's +0x3E4, guarded) + SubmitExplosion via
the Explosion::Make port; TWO chain-purge bugs fixed (the "iterator dtor
clears it" assumption was wrong -- records re-applied every tick, observed
1->2->3->4 double-counting). @004b9728 identity corrected (SendDamageMessage,
not DrawWeaponPip). Verified solo: 25 clean single-record consolidations ->
kill -> death transition. Residue: weapon-side submission awaits the
damage-economy reconciliation (authored 0.25-scale vs kShotDamage=12).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
afefaeece5
commit
77190c93e5
@@ -538,6 +538,10 @@ struct ShotDescriptor
|
||||
return (GetSubsystemCount() > 0)
|
||||
? (MechControlsMapper *)GetSubsystem(0) : 0;
|
||||
}
|
||||
class SubsystemMessageManager *GetMessageManager()
|
||||
{
|
||||
return messageManager;
|
||||
}
|
||||
Subsystem *GetHudSubsystem() { return hudSubsystem; }
|
||||
// Reachable horizontal firing half-arc (radians) the mech's torso can bring
|
||||
// its guns to bear -- the wider torso twist limit, or 0 for a fixed torso.
|
||||
|
||||
@@ -152,6 +152,7 @@
|
||||
#include <BOXSOLID.hpp> // BoxedSolid / BoxedSolidCollision / BoxedSolidCollisionList
|
||||
#include <cultural.hpp> // CulturalIcon::IsStoppingCollisionVolume / GetClassDerivations
|
||||
#include <hostmgr.hpp> // HostManager::GetEntityPointer (band-effect attacker resolve)
|
||||
#include <messmgr.hpp> // SubsystemMessageManager (task #7 consolidated delivery)
|
||||
#if !defined(PLAYER_HPP)
|
||||
# include <player.hpp> // Player::VehicleDeadMessage -- the death->respawn notification (task #52)
|
||||
#endif
|
||||
@@ -3183,7 +3184,22 @@ void
|
||||
GetEntityID(), // inflicting = this (shooter)
|
||||
-1, // UNAIMED -> receiver's cylinder resolves
|
||||
dmg);
|
||||
victim->Dispatch(&take_damage);
|
||||
// AUTHENTIC DELIVERY (task #7 tail): submit through the
|
||||
// SubsystemMessageManager -- per-frame consolidation into
|
||||
// ONE TakeDamageStream (id 0x13) dispatched at the victim,
|
||||
// whose T0 handler re-splits it (ENTITY.cpp:817).
|
||||
// Replicant victims reroute cross-pod as before. [T3:
|
||||
// the inflicting subsystemID is not threaded through this
|
||||
// bring-up fire block -- the authentic submitter is the
|
||||
// weapon's own SendDamageMessage @004b9728, pending the
|
||||
// damage-economy reconciliation (authored 0.25-scale
|
||||
// amounts vs the bring-up kShotDamage=12); until then the
|
||||
// messmgr's weapon-explosion bundling stays dormant.]
|
||||
if (messageManager != 0)
|
||||
((SubsystemMessageManager *)messageManager)
|
||||
->AddDamageMessage(victim, &take_damage);
|
||||
else
|
||||
victim->Dispatch(&take_damage);
|
||||
|
||||
// SCORE the damage (skip once the victim is dead).
|
||||
if (!((Mech *)victim)->IsMechDestroyed())
|
||||
|
||||
@@ -454,10 +454,15 @@ void
|
||||
}
|
||||
|
||||
//
|
||||
// @004b9728 -- register the targeting "pip" / fire marker with the owning Mech's
|
||||
// cockpit HUD manager (entity+0x434). Skipped for non-pip weapons (the
|
||||
// IsDerivedFrom(0x50bdb4) / segment-index guard). Builds a HUD element record
|
||||
// {priority 100, kind 0x12, flags} carrying the pip transform and submits it.
|
||||
// ⚠ IDENTITY CORRECTION (task #7): @004b9728 is MechWeapon::SendDamageMessage
|
||||
// -- the weapon-fire damage submission into the owning mech's
|
||||
// SubsystemMessageManager (mech+0x434 -> AddDamageMessage @0049b6d8), NOT a
|
||||
// HUD pip. The "HUD element record" reading below was the same 0x434
|
||||
// misattribution the mapper suffered. The authentic weapon-side submission
|
||||
// (per-weapon damageData + inflictingSubsystemID) is pending the
|
||||
// damage-economy reconciliation (authored 0.25-scale amounts vs the
|
||||
// bring-up kShotDamage=12); the interim submitter is the mech4 fire block
|
||||
// routing through AddDamageMessage. This body stays dormant meanwhile.
|
||||
//
|
||||
void
|
||||
MechWeapon::DrawWeaponPip(const LinearMatrix &transform)
|
||||
|
||||
@@ -344,6 +344,12 @@ class CockpitHud;
|
||||
Logical useConfiguredPip; // @0x3E0 = (usesExternalModel == 0) (best-effort)
|
||||
ResourceDescription::ResourceID
|
||||
explosionResourceID; // @0x3E4 resource ExplosionModelFile
|
||||
public:
|
||||
// task #7: the SubsystemMessageManager bundles the FIRING weapon's
|
||||
// explosion per consolidated damage record (ENTITY3.h:143).
|
||||
ResourceDescription::ResourceID
|
||||
GetExplosionResourceID() const { return explosionResourceID; }
|
||||
protected:
|
||||
Scalar recoil; // @0x3E8 reset 0 (Emitter::firingArmed aliases this slot)
|
||||
int segmentPageIndex; // @0x3EC resource segmentIndex (+0x28) -- LAST field, binary ends 0x3F0
|
||||
|
||||
|
||||
+123
-40
@@ -60,6 +60,8 @@
|
||||
// 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)
|
||||
@@ -334,47 +336,99 @@ void
|
||||
damageWalk.First();
|
||||
if (damageWalk.GetCurrent() != 0)
|
||||
{
|
||||
// (recovered) the consolidated damage/notify message (ClassID 0x13,
|
||||
// size 0x34, vtable @0050b984) was built on the stack here and filled
|
||||
// from commonDamageInformation. The message ctor (FUN_00419e18) and the
|
||||
// owner target field (+0x184) are renderer/net-side and are not present
|
||||
// in these headers; the explosion-queueing loop below is what feeds the
|
||||
// graphics flush, so the message build is left as a recovered note.
|
||||
// EntityID hitEntity = commonDamageInformation.damageZoneIndex;
|
||||
//
|
||||
// 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();
|
||||
|
||||
// resolve the damaged subsystem object from the owner's roster:
|
||||
// subsystem = owner->subsystems[info->subsystemID]; (mech+0x128)
|
||||
Subsystem *target =
|
||||
owner->GetSubsystem(info->subsystemID); // mech+0x128 + id*4
|
||||
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(target); // target+0x3E4
|
||||
|
||||
//
|
||||
// CreateWeaponExplosions (inlined): if this hit zone has no
|
||||
// explosion queued yet, append a ResourceIDPlug to the
|
||||
// weaponExplosions chain so it is rendered this frame.
|
||||
//
|
||||
if (!weaponExplosions.Find(explosionID)) // chain+0xFC, slot 0xC
|
||||
ResolveExplosionID(firingWeapon); // weapon+0x3E4
|
||||
if (explosionID != ResourceDescription::NullResourceID
|
||||
&& !weaponExplosions.Find(explosionID)) // chain+0xFC, slot 0xC
|
||||
{
|
||||
ResourceIDPlug *plug = new ResourceIDPlug(explosionID); // FUN_00402298(0x10) / FUN_00424716
|
||||
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]
|
||||
// damageInformation cleared as the iterator is destroyed (FUN_0049c2c9).
|
||||
}
|
||||
|
||||
|
||||
@@ -423,45 +477,74 @@ void
|
||||
SubmitExplosion(id, fireTime); // CROSS-FAMILY renderer submission
|
||||
}
|
||||
|
||||
// chain emptied as the iterator is destroyed (FUN_0049c352).
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//#############################################################################
|
||||
// CROSS-FAMILY compile shims
|
||||
// ResolveExplosionID -- the real read (task #7 tail)
|
||||
//#############################################################################
|
||||
//
|
||||
// ResolveExplosionID: in the shipped game this read subsystem+0x3E4 via
|
||||
// Subsystem::GetExplosionID(). The WinTesla Subsystem does not expose it; until
|
||||
// the base provides the accessor this returns the manager's own
|
||||
// terrainHitExplosionID as a stand-in (NullResourceID if none). See report.
|
||||
// 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)
|
||||
{
|
||||
Check_Pointer(subsystem);
|
||||
// TODO(cross-family): return subsystem->GetExplosionID();
|
||||
return terrainHitExplosionID;
|
||||
if (subsystem != 0
|
||||
&& subsystem->IsDerivedFrom(*MechWeapon::GetClassDerivations()))
|
||||
{
|
||||
return ((MechWeapon *)subsystem)->GetExplosionResourceID(); // +0x3E4
|
||||
}
|
||||
return ResourceDescription::NullResourceID;
|
||||
}
|
||||
|
||||
//
|
||||
// SubmitExplosion: the original built a renderer command (FUN_004336f0) and
|
||||
// submitted it (with the impact point/orientation from commonDamageInformation
|
||||
// and rendererCompensateTime) to the global graphics pipe (FUN_0041acbc). The
|
||||
// WinTesla L4D3D renderer replaced the IG graphics pipe; the submission entry
|
||||
// point is not present in these headers. Stubbed so the queue/timing logic
|
||||
// compiles. See report "CROSS-FAMILY NEEDS".
|
||||
// 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*/,
|
||||
ResourceDescription::ResourceID explosion_id,
|
||||
Scalar /*fire_time*/
|
||||
)
|
||||
{
|
||||
Check(this);
|
||||
// TODO(cross-family): build the explosion render command (ClassID 0x5C,
|
||||
// sub-type 3) from commonDamageInformation + explosion_id and submit it to
|
||||
// the L4D3D graphics pipe at fire_time, compensated by rendererCompensateTime.
|
||||
|
||||
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 (e != 0)
|
||||
{
|
||||
Register_Object(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user