THE SALVO DAMAGE (#95). Players measured an LRM 15 landing "3ish points". The logs agreed: [projectile] IMPACT damage=3.33333 (Oracle, LRM15) and 3.5 (Rajel, LRM10). Those are right PER MISSILE -- 50/15 and 35/10 -- and the bench shows why the salvo still under-delivers: each launcher pushes N rounds of which exactly ONE carries damage. Two individually-correct changes composed into an N-fold shortfall: * The ctor does what the binary's MissileLauncher ctor does (@0x3ac/@0x3d4): damageData.burstCount = missileCount; damageData.damageAmount /= missileCount; The record holds the PER-MISSILE amount plus the count; the arcade reconstitutes amount x burstCount when it applies the hit. * Task #62 then correctly stopped the port applying the hit once per visual round (that was ~missileCount-x too lethal) by damaging only the lead round -- but handed it the already-divided amount. Our DamageZone::TakeDamage is `damageLevel += amount * scale` and drops burstCount, so the salvo delivered amount/missileCount. Since the port collapses the cluster to one damaging round, multiply the count back in there. Bench: an SRM6 salvo now lands amt=35 (the authored total) taking a zone 0 -> 0.556, where it previously landed 5.83. THE DOUBLE EXPLOSION (#84). Oracle: "missile appear to register hit explosions twice, once where target was and again where the target is." There are two spawn sites: BTSpawnRoundDetonation at the round's own impact point, and the message manager's bundled explosion at the CONSOLIDATED point a frame later. The duplicate was known and thought harmless -- "among a rippled volley it is invisible" -- which held only while a salvo landed N detonations. A projectile now marks its weapon (MarkRoundDetonated) and the consolidation skips queueing a second blast for it; direct-fire weapons never mark, so lasers/AC keep the bundled explosion they rely on. Bench: 4 missile impacts -> 4 SKIPPED, while 11 direct-fire hits still queue normally. SWEPT CONTACT (#84 tail). Contact was a 10-unit sphere sampled only at the END of each step. With the authored thruster live (#84) field rounds arrive at v=955 -- a ~16 unit step at 60fps, larger than the radius -- so samples can straddle the target. (Pre-#84 rounds flew ~100-300 = a 1.7-5 unit step and could never skip it: the velocity fix exposed this, it did not cause it.) Now tests the whole segment travelled and bursts at the point of NEAREST APPROACH, which also stops the detonation being flung past the target at speed. RETRACTION: I posted tunnelling as the leading explanation for the lost salvo. The bench disproves it -- zero fizzles, and the "missing" rounds are the dmg=0 visual rounds of the cluster, which never registered damage by design. The sweep is kept as speed-independent robustness, not as the #95 fix. Bench: scratchpad/night8/salvo.sh (BT_PROJ_LOG + BT_FIRE_LOG). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
272 lines
8.7 KiB
C++
272 lines
8.7 KiB
C++
//===========================================================================//
|
|
// File: messmgr.hh //
|
|
// 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 //
|
|
//===========================================================================//
|
|
//
|
|
// GROUND TRUTH: this header is the surviving MESSMGR.HPP
|
|
// (410SRC/Telsa40/Rel410/CODE/BT/BT/MESSMGR.HPP), reproduced verbatim.
|
|
// The only additions are the //@offset reconstruction comments tying each
|
|
// data member to the byte offset observed in the shipped object (see
|
|
// messmgr.cpp for the originating @ADDR evidence). Declarations are
|
|
// UNCHANGED.
|
|
//
|
|
// Class summary (recovered):
|
|
// SubsystemMessageManager : public Subsystem
|
|
// vtable @0050b954 ClassID 0xBD3 ("SubsystemMessageManagerClassID")
|
|
// ctor @0049bca4 dtor @0049bd7c CreateStreamedSubsystem @0049be10
|
|
// object layout >= 0x130 bytes; new members begin at +0xE4.
|
|
//
|
|
#if !defined (MESSMGR_HPP)
|
|
# define MESSMGR_HPP
|
|
|
|
#if !defined (SUBSYSTM_HPP)
|
|
# include <subsystm.hpp>
|
|
#endif
|
|
|
|
#if !defined (RESOURCE_HPP)
|
|
# include <resource.hpp>
|
|
#endif
|
|
|
|
#if !defined (VCHAIN_HPP)
|
|
# include <vchain.hpp>
|
|
#endif
|
|
|
|
#if !defined (PLUG_HPP)
|
|
# include <plug.hpp>
|
|
#endif
|
|
|
|
#if !defined(ENTITYID_HPP)
|
|
# include <entityid.hpp>
|
|
#endif
|
|
|
|
class Entity;
|
|
class Mech;
|
|
class Entity__TakeDamageMessage;
|
|
|
|
//##########################################################################
|
|
//########## SubsystemMessageManager::SubsystemResource ###############
|
|
//##########################################################################
|
|
//
|
|
// Parsed by CreateStreamedSubsystem @0049be10. Stamps ClassID 0xBD3 and
|
|
// model-record size 0x38 at resource +0x20/+0x24, then reads:
|
|
// +0x30 terrainHitExplosionID ("TerrainHitExplosion", a resource lookup)
|
|
// +0x34 rendererCompensateTime ("RendererCompensateTime", a Scalar)
|
|
//
|
|
struct SubsystemMessageManager__SubsystemResource :
|
|
public Subsystem::SubsystemResource
|
|
{
|
|
ResourceDescription::ResourceID
|
|
terrainHitExplosionID; // resource +0x30
|
|
|
|
Scalar
|
|
rendererCompensateTime; // resource +0x34
|
|
};
|
|
|
|
//##########################################################################
|
|
//##################### SubsystemMessageManager ###################
|
|
//##########################################################################
|
|
class
|
|
SubsystemMessageManager :
|
|
public Subsystem
|
|
|
|
{
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Shared Data Support
|
|
//
|
|
public:
|
|
static Derivation ClassDerivations; // static @0050b6cc (BSS)
|
|
static SharedData DefaultData; // default Performance ptr @0050b6fc -> 0049b784
|
|
//
|
|
// Added to satisfy the real Simulation__SharedData 4-arg ctor
|
|
// (derivation, message-handlers, attribute-index, state-count). The
|
|
// shipped record carried only the derivation + default Performance; the
|
|
// handler set / attribute index are empty for this class.
|
|
//
|
|
static Receiver::MessageHandlerSet MessageHandlers;
|
|
static AttributeIndexSet AttributeIndex;
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Death and Damage Support
|
|
//
|
|
|
|
public:
|
|
|
|
void DeathReset(Logical) {}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Model Support
|
|
//
|
|
protected:
|
|
|
|
struct CommonDamageInformation
|
|
{
|
|
Entity
|
|
*entityHit; //@0xE4 (ctor/reset -> 0)
|
|
|
|
int
|
|
damageZoneIndex; //@0xE8 (ctor/reset -> -1)
|
|
|
|
Point3D
|
|
impactPoint; //@0xEC (Point3D, 3 Scalars)
|
|
|
|
} commonDamageInformation; //@0xE4
|
|
|
|
public:
|
|
|
|
struct DamageInformation
|
|
: public Plug // Plug vtable @0050b9a8, dtor @0049c3e3, size 0x14
|
|
{
|
|
Enumeration
|
|
damageType; //@0x0C (= TakeDamageMessage+0x2C)
|
|
|
|
int
|
|
subsystemID; //@0x10 (= TakeDamageMessage+0x5C)
|
|
};
|
|
|
|
typedef void
|
|
(SubsystemMessageManager::*Performance)(Scalar time_slice);
|
|
|
|
void
|
|
SetPerformance(Performance performance)
|
|
{
|
|
Check(this);
|
|
activePerformance = (Simulation::Performance)performance;
|
|
}
|
|
void
|
|
ConsolidateAndSendDamage(Scalar time_slice); // Performance @0049b784
|
|
|
|
Mech*
|
|
GetEntity()
|
|
{Check(this); return Cast_Object(Mech*, Subsystem::GetEntity());}
|
|
|
|
protected:
|
|
|
|
Scalar
|
|
rendererCompensateTime; //@0xF8 (resource +0x34)
|
|
|
|
typedef PlugOf<ResourceDescription::ResourceID>
|
|
ResourceIDPlug;
|
|
|
|
VChainOf<ResourceIDPlug*, ResourceDescription::ResourceID>
|
|
weaponExplosions; //@0xFC (VChain hdr 0x18 bytes)
|
|
|
|
VChainOf<DamageInformation*, Scalar> damageInformation; //@0x114 (VChain hdr 0x18 bytes)
|
|
|
|
ResourceDescription::ResourceID
|
|
terrainHitExplosionID; //@0x12C (resource +0x30)
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// PORT-ONLY -- appended AFTER every layout-locked member (issue #84).
|
|
//
|
|
// A projectile spawns its OWN per-round detonation at its exact impact point
|
|
// (BTSpawnRoundDetonation), which is the arcade's rippling volley look. It then
|
|
// routes its damage through this manager, which ALSO queues the firing weapon's
|
|
// explosion -- a second blast, at the bundle's CONSOLIDATED impact point and on
|
|
// a later frame. Two explosions, two places: "once where the target was and
|
|
// again where the target is" (Oracle, 4.11.674).
|
|
//
|
|
// That duplicate was known and thought harmless -- "among a rippled volley it is
|
|
// invisible" -- which held while a salvo landed N detonations. It stopped being
|
|
// true when the cluster collapsed to ONE damaging round.
|
|
//
|
|
// So: a weapon that already detonated its own round this frame is marked here,
|
|
// and the consolidation skips queueing its explosion. Direct-fire weapons
|
|
// (lasers/AC) never mark, so they keep the bundled explosion they rely on.
|
|
// Cleared every flush. Marked by roster index -- exact, because a weapon is
|
|
// either a projectile launcher or an emitter, never both in one frame.
|
|
//
|
|
public:
|
|
void
|
|
MarkRoundDetonated(int subsystem_id);
|
|
Logical
|
|
DidRoundDetonate(int subsystem_id) const;
|
|
protected:
|
|
enum { kMaxSelfDetonated = 16 };
|
|
int selfDetonated[kMaxSelfDetonated];
|
|
int selfDetonatedCount;
|
|
|
|
void
|
|
CreateWeaponExplosions(
|
|
Logical terrain_hit,
|
|
EntityID entity_hit,
|
|
Point3D explode_position
|
|
); // inlined into ConsolidateAndSendDamage @0049b784
|
|
|
|
//
|
|
// @0049baa0 -- per-frame flush of the accumulated weaponExplosions queue
|
|
// (own method; not a streamed override).
|
|
//
|
|
void
|
|
SendQueuedExplosions();
|
|
|
|
//
|
|
// CROSS-FAMILY shim. In the original the per-subsystem explosion resource
|
|
// id is read off the engine Subsystem (Subsystem::GetExplosionID(),
|
|
// subsystem+0x3E4). The WinTesla Subsystem does not expose it yet, so we
|
|
// resolve through this local helper until the base provides it. See
|
|
// report "CROSS-FAMILY NEEDS".
|
|
//
|
|
ResourceDescription::ResourceID
|
|
ResolveExplosionID(Subsystem *subsystem);
|
|
|
|
//
|
|
// CROSS-FAMILY shim. The original built a renderer command (FUN_004336f0)
|
|
// and submitted it to the global graphics pipe (theGraphicsPipe->Submit,
|
|
// FUN_0041acbc). WinTesla replaced the IG graphics pipe with L4D3D and the
|
|
// submission entry point is not present in these headers; isolate the
|
|
// missing call here so the queueing/timing logic compiles.
|
|
//
|
|
void
|
|
SubmitExplosion(
|
|
ResourceDescription::ResourceID explosion_id,
|
|
Scalar fire_time
|
|
);
|
|
|
|
public:
|
|
|
|
void
|
|
AddDamageMessage(
|
|
Entity *damaged_entity,
|
|
Entity__TakeDamageMessage *message
|
|
); //@0049b6d8
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Construction and Destruction Support
|
|
//
|
|
public:
|
|
|
|
typedef SubsystemMessageManager__SubsystemResource SubsystemResource;
|
|
|
|
SubsystemMessageManager(
|
|
Mech *owner,
|
|
int subsystem_ID,
|
|
SubsystemResource *sub_res,
|
|
SharedData &shared_data=DefaultData
|
|
); // ctor @0049bca4
|
|
|
|
~SubsystemMessageManager(); // dtor @0049bd7c (vtable slot 0)
|
|
|
|
Logical
|
|
TestInstance() const;
|
|
|
|
static Logical
|
|
CreateStreamedSubsystem(
|
|
ResourceFile *resource_file,
|
|
NotationFile *model_file,
|
|
const char *model_name,
|
|
const char *subsystem_name,
|
|
SubsystemResource *subsystem_resource,
|
|
NotationFile *subsystem_file,
|
|
const ResourceDirectories *directories
|
|
); // @0049be10 (ClassID 0xBD3)
|
|
};
|
|
#endif
|