Merge remote-tracking branch 'origin/master' into glass-cockpit
This commit is contained in:
@@ -160,6 +160,15 @@
|
||||
void
|
||||
DumpAmmo();
|
||||
|
||||
// The feed alarm's current level (this+0x1A8) -- the linked weapon's
|
||||
// ProjectileWeaponSimulation @004bbd04 reads it raw off the resolved bin:
|
||||
// gate 2 @4bbd80 (level 2/3 or bin destroyed -> weapon alarm 7), the
|
||||
// Loading recoil-clamp Loaded test @4bbe25 (level 1 -> weapon Loaded), and
|
||||
// the post-fire Empty test @4bbf11 (level 2 -> weapon alarm 7). Named
|
||||
// accessor per the databinding rule (never a raw offset on our layout).
|
||||
int
|
||||
GetAmmoState() const { return ammoAlarm.GetLevel(); }
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Subsystem virtual overrides (vtable @0051286c)
|
||||
//
|
||||
|
||||
@@ -697,6 +697,31 @@ BTL4Application::SharedData
|
||||
<< (int)(gauge_renderer != NULL) << " -> ConfigureForModel(Init)\n" << std::flush;
|
||||
if (gauge_renderer != NULL)
|
||||
{
|
||||
//
|
||||
// GAUGE-TREE SURVIVAL ACROSS THE IN-SESSION RE-STREAM (Gitea #12 fix):
|
||||
// when the mechs are RE-CREATED pre-launch (host drop/rejoin re-stream),
|
||||
// this handler runs AGAIN for the new viewpoint mech -- but the
|
||||
// lobby-built gauge tree still holds attribute pointers into the FREED
|
||||
// first-stream mech; every widget then faults and the BT_DEV_GAUGES SEH
|
||||
// guard disables it permanently (GAUGE.cpp:618) = the frozen dev-gauges
|
||||
// window. The engine's own transition path is the Shutdown teardown
|
||||
// (Application::Shutdown -> gaugeRenderer->Shutdown() -> Remove(0),
|
||||
// APP.cpp:787 / GAUGREND.cpp:3264) followed by a lazy rebuild; the
|
||||
// re-stream bypasses it. So: tear the entity-bound tree down HERE
|
||||
// (alarms + gauges + entity grids -- see TearDownForViewpointRestream)
|
||||
// and let the ConfigureForModel below rebuild it against THIS mech.
|
||||
// A no-op on the first (lobby) build.
|
||||
//
|
||||
static int s_gaugeTreeBuilt = 0;
|
||||
if (s_gaugeTreeBuilt)
|
||||
{
|
||||
DEBUG_STREAM << "[gauge] viewpoint re-stream: tearing the gauge tree "
|
||||
"down for rebuild on entity " << (void *)viewing_entity
|
||||
<< "\n" << std::flush;
|
||||
gauge_renderer->TearDownForViewpointRestream();
|
||||
}
|
||||
s_gaugeTreeBuilt = 1;
|
||||
|
||||
gauge_renderer->ConfigureForModel("Init", viewing_entity); // FUN_0046ff64
|
||||
}
|
||||
|
||||
|
||||
@@ -73,6 +73,7 @@
|
||||
#if !defined(L4LAMP_HPP)
|
||||
# include <l4lamp.hpp>
|
||||
#endif
|
||||
#include <GAUGALRM.hpp> // GaugeAlarmManager (TearDownForViewpointRestream, Gitea #12)
|
||||
#if !defined(L4WARE_HPP)
|
||||
# include <l4ware.hpp>
|
||||
#endif
|
||||
@@ -269,6 +270,49 @@ Logical
|
||||
return GaugeRenderer::TestInstance(); // FUN_00447b60
|
||||
}
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
// TearDownForViewpointRestream (PORT FIX -- Gitea #12)
|
||||
//#############################################################################
|
||||
//
|
||||
// The in-session mech RE-STREAM (a pre-launch host drop/rejoin re-creates all
|
||||
// mechs at mission launch) frees the mech the LOBBY-built gauge tree was bound
|
||||
// to; every gauge Execute then AVs on its dangling attribute pointers and the
|
||||
// BT_DEV_GAUGES SEH guard (Gauge::GuardedExecute, GAUGE.cpp:618) disables each
|
||||
// one PERMANENTLY (rate=0, nothing rebinds) -- the frozen dev-gauges window of
|
||||
// incident #12. The AUTHENTIC engine flow only tears the tree down at mission
|
||||
// transitions (Application::Shutdown -> gaugeRenderer->Shutdown() ->
|
||||
// ShutdownImplementation -> Remove(0), APP.cpp:787 / GAUGREND.cpp:3264); the
|
||||
// re-stream path bypasses it. This method performs the ENTITY-BOUND half of
|
||||
// that same ShutdownImplementation sequence, in the same order:
|
||||
// gaugeAlarmManager->RemoveAllAlarms() (alarms hold entity/subsystem ptrs)
|
||||
// Remove(0) (delete every gauge, active+inactive)
|
||||
// movingEntities/staticEntities.Clear() (raw Entity* grids for radar/map)
|
||||
// and deliberately SKIPS the mission-scoped steps -- warehouse purge, graphics
|
||||
// port destruction, lampManager->RemoveAllLamps() (the L4Lamps are owned by the
|
||||
// CONTROLS layer, which registers &lamp->automaticValue into its buttonGroup --
|
||||
// deleting them mid-session would dangle the button mappings; the mech-state
|
||||
// "lamps" like AnimatedSubsystemLamp are Gauges and go with Remove(0)). The
|
||||
// caller (MakeViewpointEntity) immediately re-runs ConfigureForModel("Init",
|
||||
// <new mech>) against the still-initialized interpreter/warehouse, so the tree
|
||||
// rebuilds bound to the NEW mech -- the gauge renderer builds lazily by design.
|
||||
//
|
||||
void
|
||||
BTL4GaugeRenderer::TearDownForViewpointRestream()
|
||||
{
|
||||
Check(this);
|
||||
|
||||
if (gaugeAlarmManager != NULL)
|
||||
{
|
||||
Check(gaugeAlarmManager);
|
||||
gaugeAlarmManager->RemoveAllAlarms();
|
||||
}
|
||||
Remove(0); // FUN_00447fc0(this, 0) -- all gauges
|
||||
movingEntities.Clear();
|
||||
staticEntities.Clear();
|
||||
Check_Fpu();
|
||||
}
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
// NotifyOfNewInterestingEntity
|
||||
|
||||
@@ -54,6 +54,23 @@
|
||||
NotifyOfNewInterestingEntity(Entity *entity);
|
||||
void
|
||||
NotifyOfBecomingUninterestingEntity(Entity *entity);
|
||||
|
||||
//
|
||||
// PORT FIX (Gitea #12 -- NOT in the surviving 1995 header): tear the
|
||||
// built gauge tree down when the viewpoint mech is RE-STREAMED
|
||||
// in-session (pre-launch host drop/rejoin re-creates every mech; the
|
||||
// lobby-built tree's attribute pointers dangle and each widget is
|
||||
// permanently DISABLED by the BT_DEV_GAUGES SEH guard, GAUGE.cpp:618).
|
||||
// Performs the entity-bound half of the engine's mission-transition
|
||||
// teardown (GaugeRenderer::ShutdownImplementation, GAUGREND.cpp:3264):
|
||||
// alarms + all gauges + the entity grids. Warehouse / graphics ports /
|
||||
// interpreter / controls lamps stay -- entity-independent, reused by
|
||||
// the immediate ConfigureForModel rebuild. Called from
|
||||
// BTL4Application::MakeViewpointEntity before ConfigureForModel;
|
||||
// a no-op on the first (lobby) build.
|
||||
//
|
||||
void
|
||||
TearDownForViewpointRestream();
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -288,16 +288,41 @@
|
||||
// Messaging Support
|
||||
//
|
||||
public:
|
||||
//
|
||||
// GLASS INPUT-AUDIT FIX (2026-07-20): these ids are PINNED to the
|
||||
// binary numbering. The old chain started at
|
||||
// L4MechControlsMapper::NextMessageID (=0x19), which registered every
|
||||
// RIO handler at 0x19..0x2a -- but the streamed "L4" .CTL EventMapping
|
||||
// records carry the BINARY's ids, and the binary RIO table @0051dd30
|
||||
// (section_dump) RE-REGISTERS the MechControlsMapper aux/zoom ids
|
||||
// 3..0x13 with the RIO override handlers (that is the whole point of
|
||||
// the derived table: same message, real body instead of the base FAIL
|
||||
// trap). With the shifted ids every streamed MFD-bank/zoom button
|
||||
// press found only the base ConfigureMappableMessageHandler ("[FAIL]
|
||||
// Unhandled button mapping") -- ~38 of the 72 pod panel buttons dead
|
||||
// (and, on a real pod with BT_BUTTON_TRAP, an abort()). Hotbox is
|
||||
// 0x1a in the binary (table entry @0051de98); the ctor/handler pair
|
||||
// was previously self-consistent at the wrong value (0x2a), so it
|
||||
// worked by accident -- now it matches the binary.
|
||||
// (Faithful-fidelity note: the binary RIO table also carries its OWN
|
||||
// Keypress id 0x19 -> @004d2514; our ctor still registers the shared
|
||||
// L4 Keypress (0x17, @004d1bf0) -- see the Gitea backlog issue.)
|
||||
//
|
||||
enum {
|
||||
Aux1QuadMessageID = L4MechControlsMapper::NextMessageID, // 3
|
||||
Aux1QuadMessageID = MechControlsMapper::Aux1QuadMessageID, // 3
|
||||
Aux1Eng1MessageID, Aux1Eng2MessageID, Aux1Eng3MessageID, Aux1Eng4MessageID,
|
||||
Aux2QuadMessageID, Aux2Eng1MessageID, Aux2Eng2MessageID, Aux2Eng3MessageID, Aux2Eng4MessageID,
|
||||
Aux3QuadMessageID, Aux3Eng1MessageID, Aux3Eng2MessageID, Aux3Eng3MessageID, Aux3Eng4MessageID,
|
||||
ZoomInMessageID, // 0x12
|
||||
ZoomOutMessageID, // 0x13
|
||||
HotboxMessageID, // pilot-target select
|
||||
HotboxMessageID = 0x1a, // pilot-target select (@0051de98)
|
||||
NextMessageID
|
||||
};
|
||||
static_assert(Aux1QuadMessageID == 0x03, "RIO Aux1Quad must be the binary id 3 (table @0051dd30)");
|
||||
static_assert(Aux3Eng4MessageID == 0x11, "RIO Aux3Eng4 must be the binary id 0x11");
|
||||
static_assert(ZoomInMessageID == 0x12 && ZoomOutMessageID == 0x13,
|
||||
"RIO ZoomIn/ZoomOut must be the binary ids 0x12/0x13");
|
||||
static_assert(HotboxMessageID == 0x1a, "RIO Hotbox must be the binary id 0x1a (@0051de98)");
|
||||
|
||||
void
|
||||
Aux1QuadMessageHandler(ReceiverDataMessageOf<ControlsButton> *message), // @004d22fc
|
||||
|
||||
@@ -212,8 +212,16 @@ LWord Emitter::GetFlags() { return simulationFlags; }
|
||||
// the emitter family (task #10) -- an overheated emitter resets its firing
|
||||
// state and holds currentLevel at 0 until it cools below FailureTemperature.
|
||||
int Emitter::GetFaultState() { return heatAlarm.GetLevel(); }
|
||||
void Emitter::SetDirty() { simulationFlags |= 0x1; }
|
||||
void Emitter::ClearDirty() { simulationFlags &= ~0x2; }
|
||||
// Gitea #12 / gotcha #20: SetDirty() { simulationFlags |= 0x1 } RETIRED -- bit 0
|
||||
// of simulationFlags is the engine DelayWatchersFlag (SIMULATE.h:170): once set,
|
||||
// PerformAndWatch skips this subsystem's ExecuteWatchers (the AUDIO watchers)
|
||||
// FOREVER. The binary sites it transcribed are two DIFFERENT writes:
|
||||
// `or word [this+0x18], 1` == updateModel |= 1 == engine ForceUpdate()
|
||||
// (FireWeapon tail @4bafaa, ResetToInitialState tail @4ba55d)
|
||||
// `or dword [this+0x28], 2` == DontExecuteFlag == engine ExecuteOnUpdate()
|
||||
// (ServiceDischarge idle @4ba99a + beam-end @4ba943)
|
||||
// Call sites now use the engine calls directly.
|
||||
void Emitter::ClearDirty() { simulationFlags &= ~0x2; } // AlwaysExecute (@004ba65c write path)
|
||||
|
||||
|
||||
//#############################################################################
|
||||
@@ -344,7 +352,7 @@ void
|
||||
? *(Entity **)((char *)owner + 0x388) : 0; // 0x474 <- mech target entity
|
||||
targetLocalFlag = 1; // 0x470
|
||||
|
||||
SetDirty(); // this+6 |= 1 (needs replication)
|
||||
ForceUpdate(); // @4bafaa `or word [this+0x18],1` (updateModel -- needs replication)
|
||||
}
|
||||
|
||||
//
|
||||
@@ -400,9 +408,12 @@ void
|
||||
}
|
||||
targetWithinRange = UpdateTargeting(); // @004b9bdc -> this+0x34c
|
||||
|
||||
// hard failure: powered-off, faulted, or the OWNING MECH destroyed (@004baa88
|
||||
// checks IsDestroyed on the owner -- a dead mech's weapons drop everything)
|
||||
if (GetFlags() == 1 || GetFaultState() == 2 || BTMechDestroyed((Entity *)owner))
|
||||
// hard failure @4baab9: weapon DESTROYED (`simulationState@0x40 == 1` -- the
|
||||
// old `GetFlags() == 1` was the gotcha-#20 mis-transcription onto
|
||||
// simulationFlags@0x28, a latent kill-switch), own heatAlarm at FailureHeat
|
||||
// (this+0x184 == 2), or the owning mech disabled (FUN_0049fb54) -- a dead
|
||||
// mech's weapons drop everything.
|
||||
if (simulationState == 1 || GetFaultState() == 2 || BTMechDestroyed((Entity *)owner))
|
||||
{
|
||||
ResetFiringState(); // @004ba9a8
|
||||
currentLevel = 0.0f; // 0x414
|
||||
@@ -541,7 +552,7 @@ void
|
||||
{
|
||||
if (firingActive == 0) // 0x418
|
||||
{
|
||||
SetDirty(); // this+0x28 |= 2
|
||||
ExecuteOnUpdate(); // @4ba99a `or dword [this+0x28],2` (DontExecuteFlag)
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -554,7 +565,7 @@ void
|
||||
weaponAlarm.SetLevel(2); // Loaded
|
||||
beamFlag = 0; // 0x46c
|
||||
dischargeTimer = dischargeTime; // 0x440 = 0x43c
|
||||
SetDirty();
|
||||
ExecuteOnUpdate(); // @4ba943 `or dword [this+0x28],2`
|
||||
// replicate the beam END: one final Emitter update record turns the
|
||||
// peer's copy off (the 1995 flush wrote idle-flagged subsystems too).
|
||||
ForceUpdate();
|
||||
@@ -835,7 +846,7 @@ void
|
||||
beamScale = Vector3D(1.0f, 1.0f, 1.0f); // 0x42c
|
||||
beamOrientation = EulerAngles(Radian(0.0f), Radian(0.0f), Radian(0.0f)); // 0x41c
|
||||
firingActive = 0; // 0x418
|
||||
SetDirty(); // this+6 |= 1
|
||||
ForceUpdate(); // @4ba55d `or word [this+0x18],1` (updateModel)
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
@@ -142,11 +142,15 @@ class NotationFile;
|
||||
protected:
|
||||
// State / flag accessors the recovered bodies read (mapped onto the
|
||||
// MechWeapon weaponAlarm + Simulation flags).
|
||||
// (Gitea #12 / gotcha #20: the old SetDirty() { simulationFlags |= 0x1 }
|
||||
// helper is RETIRED -- it conflated TWO different binary writes and its
|
||||
// bit 0 is the engine DelayWatchersFlag (mutes the audio watchers
|
||||
// forever). The binary's `or word [this+0x18],1` sites are the engine
|
||||
// ForceUpdate(); its `or dword [this+0x28],2` sites are ExecuteOnUpdate().)
|
||||
int GetWeaponState();
|
||||
int GetVoltageState();
|
||||
LWord GetFlags();
|
||||
int GetFaultState();
|
||||
void SetDirty();
|
||||
void ClearDirty();
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -2698,6 +2698,28 @@ void
|
||||
// button (releases back to the forward view). Rear-mounted
|
||||
// weapons (blackhawk/owens back racks) fire only in it.
|
||||
gBTLookBehind = gBTInput.lookBehind;
|
||||
// Gitea #12 scripted verify (BT_LOOK_TEST=<frame>): from the
|
||||
// given frame, HOLD the rear-view button for 300 frames /
|
||||
// release 300, repeating -- drives the authentic look-state
|
||||
// chain (mapper -> BTCommitLookState -> viewFireEnable) so
|
||||
// the recovered denied-shot blip (front weapons stay Loaded,
|
||||
// launch NOTHING while looking back) is verifiable headless.
|
||||
{
|
||||
static int sLookTestFrom = -2;
|
||||
if (sLookTestFrom == -2)
|
||||
{
|
||||
const char *lt = getenv("BT_LOOK_TEST");
|
||||
sLookTestFrom = (lt != 0) ? atoi(lt) : -1;
|
||||
}
|
||||
if (sLookTestFrom >= 0)
|
||||
{
|
||||
static int sLookFrame = 0;
|
||||
++sLookFrame;
|
||||
if (sLookFrame >= sLookTestFrom
|
||||
&& (((sLookFrame - sLookTestFrom) / 300) % 2) == 0)
|
||||
gBTLookBehind = 1;
|
||||
}
|
||||
}
|
||||
static float sTwist = 0.0f;
|
||||
if (gBTInput.twistAbsolute)
|
||||
{
|
||||
|
||||
@@ -557,6 +557,24 @@ Logical
|
||||
return edge;
|
||||
}
|
||||
|
||||
//
|
||||
// @004b9c9c -- vtable slot 17 (vtbl+0x44): the recharge-dial writer (Gitea #12).
|
||||
// Disasm (tools/disas2.py 0x4b9c9c):
|
||||
// fld [eax+0x3dc] ; rechargeRate
|
||||
// fsub [eax+0x3e8] ; - recoil
|
||||
// fdiv [eax+0x3dc] ; / rechargeRate
|
||||
// fstp [eax+0x320] ; -> rechargeLevel
|
||||
// ProjectileWeaponSimulation @004bbd04 dispatches this from its Loading(3) and
|
||||
// unavailable(7) cases, so the launcher-panel recharge dial (SegmentArc270 on
|
||||
// PercentDone/rechargeLevel) is authentically ANIMATED for projectile weapons.
|
||||
// Emitter overrides the slot with ComputeOutputVoltage @004ba738.
|
||||
//
|
||||
void
|
||||
MechWeapon::ComputeOutputVoltage()
|
||||
{
|
||||
rechargeLevel = (rechargeRate - recoil) / rechargeRate; // @0x320 = (@0x3DC - @0x3E8) / @0x3DC
|
||||
}
|
||||
|
||||
//
|
||||
// @004b9cbc -- copy the owning Mech's current target position (entity+0x1c4).
|
||||
//
|
||||
|
||||
@@ -241,6 +241,17 @@ class CockpitHud;
|
||||
virtual void
|
||||
FireWeapon(); // @004b95ec (abstract stub)
|
||||
|
||||
// @004b9c9c -- vtable slot 17 (vtbl+0x44): the recharge-dial writer.
|
||||
// rechargeLevel@0x320 = (rechargeRate@0x3DC - recoil@0x3E8) / rechargeRate
|
||||
// (disasm: fld [eax+0x3dc]; fsub [eax+0x3e8]; fdiv [eax+0x3dc]; fstp [eax+0x320]).
|
||||
// ProjectileWeaponSimulation @004bbd04 calls it from the Loading(3) and
|
||||
// unavailable(7) cases -- the launcher recharge dial is authentically LIVE
|
||||
// (Gitea #12; the old "authentically static" claim was wrong). Emitter
|
||||
// overrides this slot with ComputeOutputVoltage @004ba738 (same output
|
||||
// field, charge-driven instead of recoil-driven).
|
||||
virtual void
|
||||
ComputeOutputVoltage(); // slot 17, @004b9c9c
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Subsystem virtual overrides (slots on vtable @00511d2c)
|
||||
//
|
||||
@@ -346,7 +357,8 @@ class CockpitHud;
|
||||
void *WithinRangePtr() { return &targetWithinRange; }
|
||||
void *WeaponAlarmPtr() { return &weaponAlarm; }
|
||||
void *SimulationStatePtr() { return &simulationState; } // attr 1 ("SimulationState" -- damage state; 1 = Destroyed)
|
||||
Scalar *RechargeLevelPtr() { return &rechargeLevel; } // the recharge-dial source (1.0 == fully charged); STATIC on projectile weapons
|
||||
Scalar *RechargeLevelPtr() { return &rechargeLevel; } // the recharge-dial source (1.0 == fully charged); LIVE on BOTH families
|
||||
// (projectile weapons write it via slot 17 @004b9c9c — Gitea #12)
|
||||
// The reticle pip's AUTHENTIC "loaded" source: attr 0x1C WeaponState @0x350 (the
|
||||
// weaponAlarm StateIndicator level == 2 => Loaded). Unlike rechargeLevel this
|
||||
// cycles for BOTH families -- emitters Loading(3)->Loaded(2)->Firing(0), and
|
||||
|
||||
@@ -267,11 +267,13 @@ void MissileLauncher::FireWeapon()
|
||||
{
|
||||
Check(this);
|
||||
|
||||
// (task #68) the view gate: a weapon disabled in the CURRENT look view
|
||||
// (rear rack in the forward view, anything in a side view) does not fire
|
||||
// -- the same +0x3E0 gate the emitter's Loaded->Firing transition reads.
|
||||
if (!viewFireEnable)
|
||||
return;
|
||||
// Gitea #12: this body is heat + spawn ONLY, per the recovered @004bcc60
|
||||
// (part_013.c:8741+): NO viewFireEnable gate, NO ammo pull, NO recoil set.
|
||||
// All of that lives in the CALLER -- the recovered ProjectileWeaponSimulation
|
||||
// Loaded case (@4bbec2 view/target denial blip, @4bbee6 bin->FeedAmmo,
|
||||
// @4bbf51 recoil). The old early-returns here, under a caller that cycled
|
||||
// the alarm unconditionally, faked a full firing pip cycle on every denied
|
||||
// shot -- the "missiles cycle but never launch" incident mechanic.
|
||||
|
||||
// THE FIRING HEAT (task #9): heatCostToFire RAW into the launcher's own
|
||||
// pendingHeat (pre-scaled 1e7-unit resources: SRM6 = 5e7, LRM15 = 6.5e7)
|
||||
@@ -281,14 +283,6 @@ void MissileLauncher::FireWeapon()
|
||||
pendingHeat += heatCostToFire; // HeatSink @0x1C8, raw
|
||||
}
|
||||
|
||||
// A salvo draws one ammo unit from the linked bin; a dry / not-ready bin
|
||||
// latches NoAmmo (ConsumeRound) and aborts the launch.
|
||||
if (!ConsumeRound()) // FUN_004bd4f4 (AmmoBin::FeedAmmo)
|
||||
return;
|
||||
|
||||
// Begin the per-shot recharge cooldown (recoil bleeds to 0 before ReadyToFire).
|
||||
recoil = rechargeRate;
|
||||
|
||||
// Resolve the muzzle / lead geometry (seeds each flying missile).
|
||||
Point3D muzzle;
|
||||
GetMuzzlePoint(muzzle); // @004b9948
|
||||
@@ -328,19 +322,16 @@ void MissileLauncher::FireWeapon()
|
||||
|
||||
// Salvo replication (missile-visibility wave): bump the fire counter + stamp
|
||||
// the aim point; the extended update record carries both to peer nodes.
|
||||
// The CALLER's Loaded case makes the two `updateModel |= 1` marks
|
||||
// (@4bbf05/@4bbf4c == ForceUpdate) that serialize the record.
|
||||
// Gitea #12 / gotcha #20: the old `simulationFlags |= 0x1` here set the
|
||||
// engine DelayWatchersFlag -- permanently muting this launcher's audio
|
||||
// watchers after the first salvo; removed.
|
||||
{
|
||||
BTSalvoState &s = BTSalvoOf(this);
|
||||
++s.fired;
|
||||
s.target = targetPos;
|
||||
}
|
||||
|
||||
simulationFlags |= 0x1; // replication-dirty
|
||||
// ENQUEUE the record (visibility fix): the dirty flag alone never
|
||||
// serializes -- ForceUpdate sets the updateModel bit that makes the next
|
||||
// PerformAndWatch write THIS subsystem's record into the mech's update
|
||||
// stream (the exact mechanism the Emitter's beam replication uses,
|
||||
// emitter.cpp:512). Without it the peer never saw a salvo record at all.
|
||||
ForceUpdate();
|
||||
Check_Fpu();
|
||||
}
|
||||
|
||||
|
||||
+168
-122
@@ -24,12 +24,14 @@
|
||||
// ResetToInitialState @004bbaf8, HandleMessage @004bcabc,
|
||||
// PrintState @004bc6c8, CreateStreamedSubsystem @004bc7cc,
|
||||
// UpdateEject @004bbb50, DrawFiringCharge @004bbc78,
|
||||
// CheckForJam @004bbfcc, ComputeTimeOfFlight @004bc06c.
|
||||
// CheckForJam @004bbfcc, ComputeTimeOfFlight @004bc06c,
|
||||
// ProjectileWeaponSimulation @004bbd04 (Performance -- FULLY RECOVERED
|
||||
// by capstone disasm, Gitea #12 2026-07-19; see context/decomp-reference.md
|
||||
// s5 -- the old RivetGun-modeled body is retired).
|
||||
// best-effort (vtable slot proven, function prologue present, BODY NOT
|
||||
// recovered by the decompiler -- bodies below are reconstructed from context
|
||||
// and clearly marked):
|
||||
// FireWeapon @004bc104 (slot 18), ProjectileWeaponSimulation @004bbd04
|
||||
// (Performance), GetStatusFlags @004bbf88 (slot 12),
|
||||
// FireWeapon @004bc104 (slot 18), GetStatusFlags @004bbf88 (slot 12),
|
||||
// UpdateWeaponState @004bbc20 (slot 16).
|
||||
// excluded (belong to sibling/derived classes, NOT ProjectileWeapon):
|
||||
// Emitter family @004bb120/@004bb888/@004ba4d0-@004bb478 (energy weapons),
|
||||
@@ -228,16 +230,18 @@ Logical
|
||||
return True;
|
||||
}
|
||||
|
||||
//
|
||||
// ConsumeRound -- pull one round from the linked AmmoBin (FUN_004bd4f4
|
||||
// AmmoBin::FeedAmmo). Returns True when a round was dispensed; otherwise raises
|
||||
// the NoAmmo alarm and returns False. Shared by both FireWeapon spawn paths.
|
||||
//
|
||||
// Panel bridge (AFC100-counter fix, 2026-07-12): resolve the weapon's ammo
|
||||
// bin through the TYPED connection -- the gauge cluster's hand-rolled raw walk
|
||||
// (*(plug+8) twice at subsystem+0x43c) assumed the 1995 connection-object
|
||||
// internals and returned NULL/garbage on our layout, so the panel's ammo
|
||||
// digits never bound (fired shells consumed rounds invisibly).
|
||||
//
|
||||
// (Gitea #12: the old `ConsumeRound()` helper is RETIRED -- the binary has no
|
||||
// such method. The ammo pull is `bin->FeedAmmo()` @004bd4f4 called by the
|
||||
// SIMULATION's Loaded case @4bbee6, and a failed pull leaves the weapon Loaded
|
||||
// SILENTLY -- the helper's SetLevel(NoAmmo) on failure was port invention; the
|
||||
// authentic NoAmmo pin is gate 2 of the state machine, from the BIN's alarm.)
|
||||
void *
|
||||
BTWeaponAmmoBin(void *weapon)
|
||||
{
|
||||
@@ -245,18 +249,6 @@ void *
|
||||
? (void *)((ProjectileWeapon *)weapon)->ammoBinLink.Resolve() : 0;
|
||||
}
|
||||
|
||||
Logical
|
||||
ProjectileWeapon::ConsumeRound()
|
||||
{
|
||||
AmmoBin *bin = (AmmoBin*)ammoBinLink.Resolve(); // FUN_00417ab4(this+0x43c)
|
||||
if (bin != 0 && bin->FeedAmmo() != 0) // FUN_004bd4f4 -- dispensed?
|
||||
{
|
||||
return True;
|
||||
}
|
||||
weaponAlarm.SetLevel(NoAmmoState); // FUN_0041bbd8(this+0x350, 7)
|
||||
return False;
|
||||
}
|
||||
|
||||
|
||||
//#############################################################################
|
||||
// Construction / Destruction
|
||||
@@ -403,7 +395,11 @@ void
|
||||
percentOfEject = 0.0f; // this[0xfe] = 0
|
||||
ejectState = -1; // this[0x100] = -1
|
||||
|
||||
simulationFlags |= 0x1; // this[6] |= 1 (state dirty)
|
||||
// @4bbb47: `or word ptr [this+0x18], 1` == updateModel |= 1 == the engine
|
||||
// ForceUpdate() (replication mark). The old transcription onto
|
||||
// `simulationFlags |= 0x1` set the engine DelayWatchersFlag instead --
|
||||
// permanently muting this subsystem's audio watchers (gotcha #20, Gitea #12).
|
||||
ForceUpdate();
|
||||
}
|
||||
|
||||
//
|
||||
@@ -589,102 +585,159 @@ Scalar
|
||||
//
|
||||
|
||||
//
|
||||
// @004bbd04 -- Performance: per-frame ProjectileWeapon update. Body NOT
|
||||
// recovered by the decompiler (prologue confirmed at 0x4bbd04). From context it
|
||||
// sequences DrawFiringCharge / UpdateEject / CheckForJam / ComputeTimeOfFlight
|
||||
// and dispatches FireWeapon (vtable slot 18) when the trigger + charge + ammo
|
||||
// conditions are met.
|
||||
// @004bbd04 -- Performance: per-frame ProjectileWeapon update. FULLY RECOVERED
|
||||
// (capstone disasm via tools/disas2.py, Gitea #12, 2026-07-19 -- every branch
|
||||
// below cites its binary address). The old RivetGun-modeled body is retired;
|
||||
// its divergences were the #12 incident mechanics: denied shots faked a full
|
||||
// Loaded->Firing->Loading pip cycle (binary: a 4-blip that STAYS Loaded, no
|
||||
// ammo pull), the fault gate read simulationFlags (binary: simulationState),
|
||||
// recoil bled every frame unconditionally and ran to -114 (binary: Loading-only
|
||||
// + clamp at 0), and slot 17 (the recharge-dial writer) was never called.
|
||||
//
|
||||
void
|
||||
ProjectileWeapon::ProjectileWeaponSimulation(Scalar time_slice)
|
||||
{
|
||||
Check(this);
|
||||
|
||||
// 0. The PoweredSubsystem step FIRST -- disasm @004bbd12 (task #10):
|
||||
// `call 0x4b0bd0` opens the body, exactly like the emitter sibling. It
|
||||
// runs HeatSink::HeatSinkSimulation (absorb pendingHeat -> temperature ->
|
||||
// conduct to the authored condenser) and the electrical state machine.
|
||||
// Without it the launcher's firing heat (task #9) accumulated in
|
||||
// pendingHeat forever and never reached the mech's thermal network.
|
||||
PoweredSubsystem::PoweredSubsystemSimulation(time_slice); // @004b0bd0
|
||||
// @4bbd12: the PoweredSubsystem step FIRST (task #10) -- HeatSink absorb ->
|
||||
// temperature -> conduction, and the electrical state machine.
|
||||
PoweredSubsystem::PoweredSubsystemSimulation(time_slice); // call 0x4b0bd0
|
||||
|
||||
// 0b. The FAULT gate -- disasm @004bbd36-004bbd6e (task #10): flags==1, the
|
||||
// weapon's own heatAlarm at FailureHeat (this+0x184 == 2 -- THE consumer
|
||||
// of the authored FailureTemperature for the ballistic family), or the
|
||||
// owning mech destroyed -> pin recoil to the full rechargeRate and latch
|
||||
// alarm state 7 (unavailable). No return: the frame continues.
|
||||
if (simulationFlags == 1
|
||||
|| heatAlarm.GetLevel() == HeatSink::FailureHeat // this+0x184 == 2
|
||||
// @4bbd1b: the trigger edge is sampled ONCE, before the gates ([ebp-4]).
|
||||
// AUTHENTIC TRIGGER (task #5): fireImpulse = the TriggerState attribute the
|
||||
// streamed controls map binds to the pod fire buttons.
|
||||
Logical trigger = CheckFireEdge(); // call 0x4b9608
|
||||
|
||||
// @4bbd28: advance any in-progress magazine eject.
|
||||
UpdateEject(time_slice); // call 0x4bbb50
|
||||
|
||||
// GATE 1 @4bbd36-4bbd6e: weapon DESTROYED (`simulationState@0x40 == 1` --
|
||||
// NOT simulationFlags, the historic mis-transcription of gotcha #20), own
|
||||
// heatAlarm at FailureHeat (this+0x184 == 2 -- THE consumer of the authored
|
||||
// FailureTemperature for the ballistic family), or the owning mech disabled
|
||||
// (FUN_0049fb54) -> pin recoil at the full rechargeRate + latch alarm 7.
|
||||
// No return: the frame continues into the state machine.
|
||||
if (simulationState == 1 // [this+0x40] == 1 (destroyed)
|
||||
|| heatAlarm.GetLevel() == HeatSink::FailureHeat // [this+0x184] == 2
|
||||
|| (owner != 0 && owner->IsDerivedFrom(*Mech::GetClassDerivations())
|
||||
&& ((Mech *)owner)->IsDisabled())) // FUN_0049fb54
|
||||
&& ((Mech *)owner)->IsDisabled())) // FUN_0049fb54(owner)
|
||||
{
|
||||
recoil = rechargeRate; // this+0x3E8 = this+0x3DC
|
||||
weaponAlarm.SetLevel(7); // FUN_0041bbd8(this+0x350, 7)
|
||||
recoil = rechargeRate; // [this+0x3E8] = [this+0x3DC]
|
||||
weaponAlarm.SetLevel(7); // call 0x41bbd8(this+0x350, 7)
|
||||
}
|
||||
|
||||
// 1. Recover the firing charge (recoil counts down toward 0 in
|
||||
// DrawFiringCharge) and advance any in-progress magazine eject.
|
||||
DrawFiringCharge(time_slice); // @004bbc78 -- bleed recoil (+0x3E8)
|
||||
UpdateEject(time_slice); // @004bbb50 -- idle unless ejectState==0
|
||||
|
||||
// 2. AUTHENTIC TRIGGER (task #5): fireImpulse = the TriggerState attribute
|
||||
// the streamed controls map binds to the pod fire buttons (see emitter.cpp
|
||||
// note). The gBTMissileTrigger bypass is retired.
|
||||
Logical trigger = CheckFireEdge(); // MechWeapon @004b9608
|
||||
// GATE 2 @4bbd71-4bbda6: the linked AmmoBin. Feed alarm Empty(2)/level-3 or
|
||||
// bin destroyed -> re-pin alarm 7 EVERY FRAME while dry. (The binary
|
||||
// dereferences the resolved bin UNGUARDED @4bbd80; the port keeps a null
|
||||
// guard -- shipped content always links a bin, the ctor logs when it can't.)
|
||||
AmmoBin *bin = (AmmoBin *)ammoBinLink.Resolve(); // call 0x417ab4(this+0x43c)
|
||||
if (bin != 0
|
||||
&& (bin->GetAmmoState() == 2 || bin->GetAmmoState() == 3 // [bin+0x1A8]
|
||||
|| bin->GetSimulationState() == 1)) // [bin+0x40] (destroyed)
|
||||
{
|
||||
weaponAlarm.SetLevel(7);
|
||||
}
|
||||
|
||||
static int s_pwtick = 0;
|
||||
if (getenv("BT_PROJ_LOG") && (((++s_pwtick) % 240) == 0 || trigger))
|
||||
DEBUG_STREAM << "[projweap] tick " << GetName() << " trig=" << (int)trigger
|
||||
<< " trigState=" << (float)fireImpulse << " ready=" << (int)ReadyToFire()
|
||||
<< " state=" << weaponAlarm.GetState() << " recoil=" << recoil << " x\n" << std::flush;
|
||||
<< " trigState=" << (float)fireImpulse
|
||||
<< " state=" << weaponAlarm.GetState() << " recoil=" << recoil
|
||||
<< " binState=" << (bin != 0 ? bin->GetAmmoState() : -1) << "\n" << std::flush;
|
||||
|
||||
// 3. Firing state machine (weaponAlarm @0x350). Modelled on the RP analog
|
||||
// RivetGun::RivetGunSimulation (WEAPSYS.cpp): the Jammed / NoAmmo latches
|
||||
// persist and only escalate on a trigger pull; the default (ready) branch
|
||||
// rolls the heat-scaled jam chance and -- on a clean shot with charge and
|
||||
// ammo -- dispatches FireWeapon through the vtable (slot 18) so the
|
||||
// MissileLauncher override spawns Missiles.
|
||||
switch (weaponAlarm.GetState()) // *(this+0x364)
|
||||
// THE FIRING STATE MACHINE @4bbda9: switch on weaponAlarm state ([this+0x364]).
|
||||
// Only states 2/3/5/7 carry a case body; 0/1/4/6 are transient audio blips.
|
||||
switch (weaponAlarm.GetState())
|
||||
{
|
||||
case JammedState: // 5 -- cleared only by ResetToInitialState
|
||||
case 2: // Loaded @4bbec2 -- armed; everything happens on the trigger edge
|
||||
if (trigger)
|
||||
weaponAlarm.SetLevel(TriggerDuringJamState); // 6
|
||||
break;
|
||||
|
||||
case NoAmmoState: // 7 -- re-assert on a dry trigger pull
|
||||
if (trigger)
|
||||
weaponAlarm.SetLevel(NoAmmoState);
|
||||
break;
|
||||
|
||||
default: // Loaded / Loading / Firing / idle
|
||||
if (trigger && ReadyToFire())
|
||||
{
|
||||
if (CheckForJam()) // @004bbfcc -- heat-scaled jam roll
|
||||
if (viewFireEnable != 0 && HasActiveTarget()) // [this+0x3E0] && [owner+0x388]
|
||||
{
|
||||
weaponAlarm.SetLevel(JammedState); // 5
|
||||
break;
|
||||
// @4bbee6: the ammo pull happens HERE, in the caller -- a failed
|
||||
// pull (bin feeding/not-ready) just stays Loaded, silently.
|
||||
if (bin != 0 && bin->FeedAmmo() != 0) // call 0x4bd4f4(bin)
|
||||
{
|
||||
weaponAlarm.SetLevel(0); // @4bbef4 -> Firing
|
||||
ForceUpdate(); // @4bbf05 `or word [this+0x18],1` (updateModel)
|
||||
FireWeapon(); // @4bbf0d vtbl+0x48 (slot 18)
|
||||
if (bin->GetAmmoState() == 2) // @4bbf11 bin went Empty on the pull
|
||||
weaponAlarm.SetLevel(7); // -> unavailable (gate 2 re-pins it)
|
||||
else if (CheckForJam()) // @4bbf2a call 0x4bbfcc (heat-scaled roll)
|
||||
weaponAlarm.SetLevel(JammedState); // @4bbf34 -> 5
|
||||
else
|
||||
weaponAlarm.SetLevel(3); // @4bbf41 -> Loading (reload)
|
||||
ForceUpdate(); // @4bbf4c second updateModel mark
|
||||
recoil = rechargeRate; // @4bbf51 begin the recharge cooldown
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// @4bbf5f THE DENIAL BLIP (Gitea #12 fix): a shot denied by the
|
||||
// look-view gate or by having NO TARGET does SetLevel(4);
|
||||
// SetLevel(2) -- a one-frame audio blip, the pip STAYS LOADED and
|
||||
// NO ammo is pulled. (The old port routed these gates through
|
||||
// FireWeapon early-returns while the caller cycled the full
|
||||
// Firing->Loading alarm anyway -- a denied missile shot faked a
|
||||
// perfect firing cycle and launched nothing: the reported
|
||||
// "missiles cycle but never launch" phenomenology, verbatim.)
|
||||
weaponAlarm.SetLevel(4);
|
||||
weaponAlarm.SetLevel(2);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
// refresh the lead solution (updates leadPosition / targeting), then
|
||||
// fire. FireWeapon() consumes a round + spawns the projectile/missile
|
||||
// and (on a dry bin) latches NoAmmo itself.
|
||||
ComputeTimeOfFlight(); // @004bc06c
|
||||
FireWeapon(); // vtable slot 18 -> spawn path
|
||||
if (weaponAlarm.GetState() != NoAmmoState && weaponAlarm.GetState() != JammedState)
|
||||
weaponAlarm.SetLevel(0); // Firing
|
||||
}
|
||||
else
|
||||
case 3: // Loading @4bbdd2 -- recharging toward Loaded
|
||||
if (trigger)
|
||||
{
|
||||
// Idle/reload: drive the ready state so the WeaponState audio is correct.
|
||||
// The recon set Firing(0)/Loading(3)/Jammed/NoAmmo but NEVER a ready level,
|
||||
// so at idle the launcher sat in Loading(3) forever and its WeaponState
|
||||
// audio looped MissileLoading indefinitely (the loop stops only when the
|
||||
// alarm LEAVES the loading state). Reflect readiness: Loaded(2, charge +
|
||||
// ammo up = silent ready) once ReadyToFire, else Loading(3, reloading) --
|
||||
// SetLevel fires audio only on a real change, so this just yields the
|
||||
// natural Firing -> Loading(reload) -> Loaded(ready) cycle. [T2]
|
||||
weaponAlarm.SetLevel(ReadyToFire() ? 2 : 3);
|
||||
weaponAlarm.SetLevel(4); // @4bbdd9 TriggerDuringLoad blip
|
||||
weaponAlarm.SetLevel(3); // @4bbdea back to Loading
|
||||
}
|
||||
if (electricalStateAlarm.GetLevel() == 4) // @4bbdf5 [this+0x278] == Ready
|
||||
{
|
||||
// @4bbe04: recoil bleeds ONLY here (Loading + electrical Ready) --
|
||||
// the old port bled it unconditionally every frame with no clamp,
|
||||
// running it to -114 across a mission.
|
||||
DrawFiringCharge(time_slice); // call 0x4bbc78
|
||||
if (recoil < 0.0f) // @4bbe0c fcomp 0.0 (_DAT_004bbf84)
|
||||
{
|
||||
recoil = 0.0f; // @4bbe1d clamp AT zero
|
||||
if (bin != 0 && bin->GetAmmoState() == 1) // @4bbe25 bin Loaded (round chambered)
|
||||
weaponAlarm.SetLevel(2); // @4bbe30 -> Loaded
|
||||
}
|
||||
}
|
||||
ComputeOutputVoltage(); // @4bbe44 vtbl+0x44 (slot 17 @4b9c9c):
|
||||
// rechargeLevel = (rate - recoil)/rate
|
||||
// -- the recharge dial ANIMATES
|
||||
break;
|
||||
|
||||
case 5: // Jammed @4bbe8e -- cleared only by ResetToInitialState / msg 2
|
||||
if (trigger)
|
||||
{
|
||||
weaponAlarm.SetLevel(TriggerDuringJamState); // @4bbe95 blip 6
|
||||
weaponAlarm.SetLevel(JammedState); // @4bbea6 back to 5
|
||||
}
|
||||
recoil = rechargeRate; // @4bbeb1 held at full recoil
|
||||
break;
|
||||
|
||||
case 7: // unavailable/NoAmmo @4bbe4d -- THE ROACH MOTEL (binary-faithful:
|
||||
// the case re-asserts 7 UNCONDITIONALLY; nothing in the machine
|
||||
// ever leaves it. Entered on: weapon destroyed, FailureHeat, mech
|
||||
// disabled (gate 1) or bin empty/destroyed (gate 2). Recovery
|
||||
// paths that EXIST in the binary: ResetToInitialState (slot 10,
|
||||
// mission reset) and an inbound update record overwriting the
|
||||
// alarm on a replicant. A launcher that trips FailureHeat
|
||||
// mid-mission stays dead until mission end -- harsh but authentic;
|
||||
// whether FailureHeat is reachable at authentic play intensity is
|
||||
// the open heat-economy question (open-questions.md, MP section).
|
||||
if (trigger)
|
||||
weaponAlarm.SetLevel(1); // @4bbe54 dry-trigger blip
|
||||
weaponAlarm.SetLevel(7); // @4bbe65 re-assert
|
||||
recoil = rechargeRate; // @4bbe76
|
||||
ComputeOutputVoltage(); // @4bbe85 vtbl+0x44 -- dial pinned at 0
|
||||
break;
|
||||
|
||||
default: // 0/1/4/6 @4bbdcd: no case body (transient audio-blip states)
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -695,18 +748,25 @@ void
|
||||
// @004bc104 -- slot 18 FireWeapon (overrides MechWeapon's pure-virtual trap
|
||||
// @004ba45c). Body NOT recovered by the decompiler (prologue confirmed at
|
||||
// 0x4bc104, immediately following the 1.0f literal @0x4bc100). Spawns the
|
||||
// generic projectile/tracer and starts the eject cycle. MissileLauncher
|
||||
// overrides this again at @004bcc60 to launch Missile entities.
|
||||
// generic projectile/tracer. MissileLauncher overrides this again at @004bcc60
|
||||
// to launch Missile entities.
|
||||
//
|
||||
// Gitea #12: the view/target gate, the ammo pull and the recoil set are
|
||||
// STRIPPED from this body -- they belong to the CALLER (the recovered
|
||||
// ProjectileWeaponSimulation Loaded case @4bbec2/@4bbee6/@4bbf51; the sibling
|
||||
// @004bcc60 confirms a FireWeapon body is heat + spawn ONLY). The old
|
||||
// early-returns here, combined with the caller's unconditional alarm cycling,
|
||||
// were the "denied shot fakes a full firing cycle" incident mechanic.
|
||||
//
|
||||
void
|
||||
ProjectileWeapon::FireWeapon()
|
||||
{
|
||||
Check(this);
|
||||
|
||||
// (task #68) the view gate: a weapon disabled in the CURRENT look view
|
||||
// does not fire (see mislanch.cpp / the emitter's Loaded->Firing gate).
|
||||
if (!viewFireEnable)
|
||||
return;
|
||||
// Refresh the lead solution (leadPosition / targeting) for the spawn below
|
||||
// (@004bc06c; the recovered sim no longer calls it -- best-effort placement
|
||||
// inside the unrecovered @4bc104 body, which is where the lead data is used).
|
||||
ComputeTimeOfFlight(); // @004bc06c
|
||||
|
||||
// THE FIRING HEAT (task #9; was "a separate pre-existing gap"): the
|
||||
// binary adds heatCostToFire RAW to the weapon's own pendingHeat --
|
||||
@@ -717,10 +777,10 @@ void
|
||||
pendingHeat += heatCostToFire; // HeatSink @0x1C8, raw
|
||||
}
|
||||
|
||||
// Binary @4bc136-4bc19c (task #56): the firing RECOIL kick, BEFORE the ammo
|
||||
// pull (no early-out precedes it in the binary). Gate: per-shot damage >
|
||||
// 3.0f (@0x4bc3f4) && the owner mech has a gyro. Direction (0, 0.6, -1.5)
|
||||
// (0x3f19999a/0xbfc00000), magnitude = damageAmount / 16 (@0x4bc3f8).
|
||||
// Binary @4bc136-4bc19c (task #56): the firing RECOIL kick. Gate: per-shot
|
||||
// damage > 3.0f (@0x4bc3f4) && the owner mech has a gyro. Direction
|
||||
// (0, 0.6, -1.5) (0x3f19999a/0xbfc00000), magnitude = damageAmount / 16
|
||||
// (@0x4bc3f8).
|
||||
if (damageData.damageAmount > 3.0f && owner != 0)
|
||||
{
|
||||
extern void GyroApplyDamageImpulse(Subsystem *, Scalar, Scalar, Scalar, Scalar);
|
||||
@@ -730,16 +790,6 @@ void
|
||||
damageData.damageAmount * 0.0625f);
|
||||
}
|
||||
|
||||
// Pull a round; a dry / not-ready bin latches NoAmmo and aborts the shot.
|
||||
if (!ConsumeRound()) // FUN_004bd4f4 (AmmoBin::FeedAmmo)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Begin the per-shot recharge cooldown (recoil bleeds back down to 0 in
|
||||
// DrawFiringCharge before the weapon is ReadyToFire again).
|
||||
recoil = rechargeRate; // this[0xfa] = this[0xf7]
|
||||
|
||||
// Resolve the live muzzle.
|
||||
Point3D muzzle;
|
||||
GetMuzzlePoint(muzzle); // MechWeapon @004b9948
|
||||
@@ -779,21 +829,17 @@ void
|
||||
subsystemID /*messmgr explosion bundling at impact (task #7)*/);
|
||||
|
||||
// task #61: mark this shot for REPLICATION so the peer sees the enemy's
|
||||
// cannon. ++fireCounter + ForceUpdate() -- IDENTICAL to the missile path
|
||||
// (mislanch.cpp:316/326) and the emitter beam: the master's per-frame
|
||||
// subsystem serialize writes this weapon's record (WriteUpdateRecord),
|
||||
// Entity::UpdateMessageHandler routes it to the peer's replicant, and the
|
||||
// replicant's ReadUpdateRecord mirrors the shot. (The old code set only
|
||||
// `simulationFlags |= 0x1` -- the +0x28 instance flag, NOT the updateModel
|
||||
// bit WriteSimulationUpdate walks -- so the AC record never serialized: the
|
||||
// root cause of the invisible enemy autocannon.)
|
||||
// cannon. ++fireCounter; the CALLER's Loaded case makes the two
|
||||
// `updateModel |= 1` marks (@4bbf05/@4bbf4c == ForceUpdate) that serialize
|
||||
// this weapon's record (WriteUpdateRecord) to the peer's replicant.
|
||||
// Gitea #12 / gotcha #20: the old extra `simulationFlags |= 0x1` here set
|
||||
// the engine DelayWatchersFlag -- permanently muting this weapon's audio
|
||||
// watchers after the first shot; removed.
|
||||
{
|
||||
BTAcFireState &fs = BTAcFireOf(this);
|
||||
++fs.fired;
|
||||
fs.target = targetPos;
|
||||
}
|
||||
simulationFlags |= 0x1; // replication-dirty (state flag)
|
||||
ForceUpdate(); // set the updateModel bit -> serialize the record
|
||||
Check_Fpu();
|
||||
}
|
||||
|
||||
|
||||
@@ -151,10 +151,14 @@ class NotationFile;
|
||||
activePerformance = (Simulation::Performance)performance;
|
||||
}
|
||||
|
||||
// @004bbd04 -- ProjectileWeapon per-frame update (Performance). Body not
|
||||
// recovered by the decompiler (function prologue confirmed at 0x4bbd04);
|
||||
// drives UpdateEject / DrawFiringCharge / CheckForJam / ComputeTimeOfFlight
|
||||
// and triggers FireWeapon via the vtable.
|
||||
// @004bbd04 -- ProjectileWeapon per-frame update (Performance). FULLY
|
||||
// RECOVERED (capstone disasm, Gitea #12): base sim -> trigger edge ->
|
||||
// UpdateEject -> fault gates (destroyed/FailureHeat/mech-disabled; bin
|
||||
// empty/destroyed -> alarm 7) -> the Loaded/Loading/Jammed/7 state
|
||||
// machine. The ammo pull (bin->FeedAmmo @4bd4f4), the view/target
|
||||
// denial blip, the updateModel replication marks and the recoil set all
|
||||
// live HERE, not in FireWeapon; slot 17 (@004b9c9c) animates the
|
||||
// recharge dial from the Loading/7 cases.
|
||||
void
|
||||
ProjectileWeaponSimulation(Scalar time_slice);
|
||||
|
||||
@@ -276,12 +280,11 @@ class NotationFile;
|
||||
Logical
|
||||
LiveFireEnabled() const; // controls "live fire" flag
|
||||
|
||||
// Resolve the linked AmmoBin and pull one round (AmmoBin::FeedAmmo).
|
||||
// Returns True when a round was dispensed; on a dry / not-ready / missing
|
||||
// bin it raises the NoAmmo alarm and returns False. Shared by the
|
||||
// ProjectileWeapon and MissileLauncher FireWeapon spawn paths.
|
||||
Logical
|
||||
ConsumeRound();
|
||||
// (Gitea #12: the old ConsumeRound() helper is RETIRED -- the binary has
|
||||
// no such method. The ammo pull is bin->FeedAmmo() @4bd4f4, called by
|
||||
// the recovered ProjectileWeaponSimulation Loaded case @4bbee6; a failed
|
||||
// pull stays Loaded silently, and the NoAmmo(7) pin comes from the BIN's
|
||||
// alarm via the sim's gate 2 -- never from the weapon side.)
|
||||
|
||||
// True when the weapon has recovered its firing charge (recoil counts
|
||||
// down toward 0 in DrawFiringCharge; 0 == ready) and is not mid-eject.
|
||||
|
||||
Reference in New Issue
Block a user