BT410 Phase 5.3.24: the death -> respawn cycle -- mechs DIE and COME BACK
The full circuit, workflow-researched (5 parallel dossiers over BT411 + the engine) and adversarially reviewed (3 lenses) before landing: Mech side: the once-per-death transition in Simulate (freeze, motion kill, DeathShutdown sweep, MASTER-only VehicleDead(-1) to the player link); Mech::Reset @0049fb74 (reposition + heal every hull zone + the roster DeathReset sweep + PreRun -- the reset-based respawn that REUSES the entity); dead-owner terms in both weapon hard gates (wrecks fall silent). Player side: the VehicleDead(-1) branch (deathPending dedup, deathCount bump+stamp, scenario-role life debit, +5s re-post -> the engine drop-zone hunt) and the DropZoneReply respawn branch (reset the dead mech at the replied drop zone; probes on a live mech are moot). Subsystem side: the engine base virtual DeathReset implemented across the family -- MechSubsystem (zone heal + DestroyedState clear), MechWeapon (full powered/thermal restore + fresh Loading cycle), AmmoBin (restock from the ctor-cached count), HeatSink/HeatWatcher/PowerWatcher/Generator/Sensor/ MissileLauncher. Review catches fixed before commit: AmmoBin restocked through the FREED padBuffer pointer (use-after-free -> initialAmmoCount; MechSubsystem:: resource is now documented as never-deref-post-ctor); died-hot weapons respawned at FailureHeat (now full thermal re-init); Generator tap counts desynced across reset (preserved); Sensor skipped the base heal; PowerWatcher inherited a statically-bound reset; cooling toggle + connect mode restored. Verified: kill -> wreck (0 shots while dead) -> +5s -> drop-zone hunt -> HEALED + placed at a real drop zone -> 134 shots after respawn incl. 12 SRM (restock proof); unowned enemy wreck settles silently; fight/smoke/novice regressions green, zero faults. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,82 +1,104 @@
|
||||
//===========================================================================//
|
||||
// File: ammobin.cpp //
|
||||
// Project: BattleTech Brick: Mech weapons //
|
||||
// Contents: AmmoBin -- ammunition store with cook-off heat //
|
||||
//---------------------------------------------------------------------------//
|
||||
// Copyright (C) 1995, Virtual World Entertainment, Inc. //
|
||||
// All Rights reserved worldwide //
|
||||
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL //
|
||||
//===========================================================================//
|
||||
|
||||
#include <bt.hpp>
|
||||
#pragma hdrstop
|
||||
|
||||
#if !defined(AMMOBIN_HPP)
|
||||
# include <ammobin.hpp>
|
||||
#endif
|
||||
|
||||
#if !defined(MECH_HPP)
|
||||
# include <mech.hpp>
|
||||
#endif
|
||||
|
||||
Derivation
|
||||
AmmoBin::ClassDerivations(
|
||||
HeatWatcher::ClassDerivations,
|
||||
"AmmoBin"
|
||||
);
|
||||
|
||||
AmmoBin::SharedData
|
||||
AmmoBin::DefaultData(
|
||||
AmmoBin::ClassDerivations,
|
||||
Subsystem::MessageHandlers,
|
||||
Subsystem::AttributeIndex,
|
||||
Subsystem::StateCount
|
||||
);
|
||||
|
||||
AmmoBin::AmmoBin(
|
||||
Mech *owner,
|
||||
int subsystem_ID,
|
||||
SubsystemResource *resource,
|
||||
SharedData &shared_data
|
||||
):
|
||||
HeatWatcher(owner, subsystem_ID, resource, shared_data),
|
||||
ammoAlarm(6)
|
||||
{
|
||||
Check(owner);
|
||||
Check_Pointer(resource);
|
||||
|
||||
ammoCount = resource->ammoCount;
|
||||
initialAmmoCount = resource->ammoCount;
|
||||
feedRate = resource->feedRate;
|
||||
feedTimer = 0.0f;
|
||||
ammoClassID = resource->ammoClassID;
|
||||
ammoModelFile = resource->ammoModelFile;
|
||||
explosionModelFile = resource->explosionModelFile;
|
||||
heatPerRound = resource->heatPerRound;
|
||||
cookOffArmed = 0;
|
||||
cookOffTime = 0;
|
||||
reserved = 0;
|
||||
|
||||
for (int i = 0; i < 12; ++i)
|
||||
{
|
||||
cookOffState[i] = 0;
|
||||
}
|
||||
|
||||
Check_Fpu();
|
||||
}
|
||||
|
||||
AmmoBin::~AmmoBin()
|
||||
{
|
||||
}
|
||||
|
||||
Logical
|
||||
AmmoBin::TestClass(Mech &)
|
||||
{
|
||||
return True;
|
||||
}
|
||||
|
||||
Logical
|
||||
AmmoBin::TestInstance() const
|
||||
{
|
||||
return IsDerivedFrom(ClassDerivations);
|
||||
}
|
||||
//===========================================================================//
|
||||
// File: ammobin.cpp //
|
||||
// Project: BattleTech Brick: Mech weapons //
|
||||
// Contents: AmmoBin -- ammunition store with cook-off heat //
|
||||
//---------------------------------------------------------------------------//
|
||||
// Copyright (C) 1995, Virtual World Entertainment, Inc. //
|
||||
// All Rights reserved worldwide //
|
||||
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL //
|
||||
//===========================================================================//
|
||||
|
||||
#include <bt.hpp>
|
||||
#pragma hdrstop
|
||||
|
||||
#if !defined(AMMOBIN_HPP)
|
||||
# include <ammobin.hpp>
|
||||
#endif
|
||||
|
||||
#if !defined(MECH_HPP)
|
||||
# include <mech.hpp>
|
||||
#endif
|
||||
|
||||
Derivation
|
||||
AmmoBin::ClassDerivations(
|
||||
HeatWatcher::ClassDerivations,
|
||||
"AmmoBin"
|
||||
);
|
||||
|
||||
AmmoBin::SharedData
|
||||
AmmoBin::DefaultData(
|
||||
AmmoBin::ClassDerivations,
|
||||
Subsystem::MessageHandlers,
|
||||
Subsystem::AttributeIndex,
|
||||
Subsystem::StateCount
|
||||
);
|
||||
|
||||
AmmoBin::AmmoBin(
|
||||
Mech *owner,
|
||||
int subsystem_ID,
|
||||
SubsystemResource *resource,
|
||||
SharedData &shared_data
|
||||
):
|
||||
HeatWatcher(owner, subsystem_ID, resource, shared_data),
|
||||
ammoAlarm(6)
|
||||
{
|
||||
Check(owner);
|
||||
Check_Pointer(resource);
|
||||
|
||||
ammoCount = resource->ammoCount;
|
||||
initialAmmoCount = resource->ammoCount;
|
||||
feedRate = resource->feedRate;
|
||||
feedTimer = 0.0f;
|
||||
ammoClassID = resource->ammoClassID;
|
||||
ammoModelFile = resource->ammoModelFile;
|
||||
explosionModelFile = resource->explosionModelFile;
|
||||
heatPerRound = resource->heatPerRound;
|
||||
cookOffArmed = 0;
|
||||
cookOffTime = 0;
|
||||
reserved = 0;
|
||||
|
||||
for (int i = 0; i < 12; ++i)
|
||||
{
|
||||
cookOffState[i] = 0;
|
||||
}
|
||||
|
||||
Check_Fpu();
|
||||
}
|
||||
|
||||
AmmoBin::~AmmoBin()
|
||||
{
|
||||
}
|
||||
|
||||
Logical
|
||||
AmmoBin::TestClass(Mech &)
|
||||
{
|
||||
return True;
|
||||
}
|
||||
|
||||
Logical
|
||||
AmmoBin::TestInstance() const
|
||||
{
|
||||
return IsDerivedFrom(ClassDerivations);
|
||||
}
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
// DeathReset -- the respawn restock: base heal + refill from the authored
|
||||
// resource count (the launchers' NoAmmo state releases in their own reset).
|
||||
//#############################################################################
|
||||
//
|
||||
void
|
||||
AmmoBin::DeathReset(Logical full_reset)
|
||||
{
|
||||
Check(this);
|
||||
|
||||
HeatWatcher::DeathReset(full_reset);
|
||||
|
||||
//
|
||||
// Restock from the CTOR-CACHED authored count -- NEVER through the
|
||||
// `resource` member: it points into the Mech ctor's padded stream copy,
|
||||
// which is delete[]d before the ctor returns (a respawn-time deref
|
||||
// reads freed heap -- caught by the 5.3.24 adversarial review).
|
||||
//
|
||||
ammoCount = initialAmmoCount;
|
||||
}
|
||||
|
||||
@@ -1,112 +1,118 @@
|
||||
//===========================================================================//
|
||||
// File: ammobin.hpp //
|
||||
// Project: BattleTech Brick: Mech weapons //
|
||||
// Contents: AmmoBin -- an ammunition store (with cook-off heat) //
|
||||
//---------------------------------------------------------------------------//
|
||||
// Copyright (C) 1995, Virtual World Entertainment, Inc. //
|
||||
// All Rights reserved worldwide //
|
||||
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL //
|
||||
//===========================================================================//
|
||||
|
||||
#if !defined(AMMOBIN_HPP)
|
||||
# define AMMOBIN_HPP
|
||||
|
||||
# if !defined(HEAT_HPP)
|
||||
# include <heat.hpp>
|
||||
# endif
|
||||
|
||||
//##################### Forward Class Declarations #######################
|
||||
class Mech;
|
||||
|
||||
//###########################################################################
|
||||
//###################### AmmoBin Model Resource ########################
|
||||
//###########################################################################
|
||||
|
||||
struct AmmoBin__SubsystemResource:
|
||||
public HeatWatcher::SubsystemResource
|
||||
{
|
||||
int ammoClassID;
|
||||
int ammoModelFile;
|
||||
int explosionModelFile;
|
||||
int ammoCount;
|
||||
Scalar feedRate;
|
||||
Scalar heatPerRound;
|
||||
};
|
||||
|
||||
//###########################################################################
|
||||
//############################## AmmoBin ###############################
|
||||
//###########################################################################
|
||||
|
||||
class AmmoBin:
|
||||
public HeatWatcher
|
||||
{
|
||||
public:
|
||||
static Derivation ClassDerivations;
|
||||
static SharedData DefaultData;
|
||||
|
||||
static Logical
|
||||
TestClass(Mech &);
|
||||
Logical
|
||||
TestInstance() const;
|
||||
|
||||
int GetAmmoCount() const { return ammoCount; }
|
||||
|
||||
//
|
||||
// The feed interface the ballistic fire path consumes. Ammo states
|
||||
// (the 1995 feed alarm): 1 = a round is chambered/ready, 2 = EMPTY.
|
||||
// FeedAmmo pulls one round (the launcher calls it on a granted shot);
|
||||
// returns 0 when dry. (Feed-rate pacing + the cook-off heat source
|
||||
// join with the heat wave; the launcher's own recharge already paces
|
||||
// shots.)
|
||||
//
|
||||
enum {
|
||||
AmmoLoadedState = 1,
|
||||
AmmoEmptyState = 2
|
||||
};
|
||||
int
|
||||
GetAmmoState() const
|
||||
{ Check(this); return (ammoCount > 0) ? AmmoLoadedState : AmmoEmptyState; }
|
||||
int
|
||||
FeedAmmo()
|
||||
{
|
||||
Check(this);
|
||||
if (ammoCount <= 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
--ammoCount;
|
||||
return 1;
|
||||
}
|
||||
|
||||
public:
|
||||
typedef AmmoBin__SubsystemResource SubsystemResource;
|
||||
|
||||
AmmoBin(
|
||||
Mech *owner,
|
||||
int subsystem_ID,
|
||||
SubsystemResource *subsystem_resource,
|
||||
SharedData &shared_data = DefaultData
|
||||
);
|
||||
~AmmoBin();
|
||||
|
||||
protected:
|
||||
int ammoCount;
|
||||
int initialAmmoCount;
|
||||
Scalar feedTimer;
|
||||
Scalar feedRate;
|
||||
int ammoClassID;
|
||||
int ammoModelFile;
|
||||
int explosionModelFile;
|
||||
Scalar heatPerRound;
|
||||
int cookOffArmed;
|
||||
int cookOffTime;
|
||||
int reserved;
|
||||
AlarmIndicator ammoAlarm;
|
||||
//
|
||||
// Cook-off heat-source registration state (advanced by the heat sim).
|
||||
// Reserved until the per-frame path is reconstructed.
|
||||
//
|
||||
int cookOffState[12];
|
||||
};
|
||||
|
||||
#endif
|
||||
//===========================================================================//
|
||||
// File: ammobin.hpp //
|
||||
// Project: BattleTech Brick: Mech weapons //
|
||||
// Contents: AmmoBin -- an ammunition store (with cook-off heat) //
|
||||
//---------------------------------------------------------------------------//
|
||||
// Copyright (C) 1995, Virtual World Entertainment, Inc. //
|
||||
// All Rights reserved worldwide //
|
||||
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL //
|
||||
//===========================================================================//
|
||||
|
||||
#if !defined(AMMOBIN_HPP)
|
||||
# define AMMOBIN_HPP
|
||||
|
||||
# if !defined(HEAT_HPP)
|
||||
# include <heat.hpp>
|
||||
# endif
|
||||
|
||||
//##################### Forward Class Declarations #######################
|
||||
class Mech;
|
||||
|
||||
//###########################################################################
|
||||
//###################### AmmoBin Model Resource ########################
|
||||
//###########################################################################
|
||||
|
||||
struct AmmoBin__SubsystemResource:
|
||||
public HeatWatcher::SubsystemResource
|
||||
{
|
||||
int ammoClassID;
|
||||
int ammoModelFile;
|
||||
int explosionModelFile;
|
||||
int ammoCount;
|
||||
Scalar feedRate;
|
||||
Scalar heatPerRound;
|
||||
};
|
||||
|
||||
//###########################################################################
|
||||
//############################## AmmoBin ###############################
|
||||
//###########################################################################
|
||||
|
||||
class AmmoBin:
|
||||
public HeatWatcher
|
||||
{
|
||||
public:
|
||||
static Derivation ClassDerivations;
|
||||
static SharedData DefaultData;
|
||||
|
||||
static Logical
|
||||
TestClass(Mech &);
|
||||
Logical
|
||||
TestInstance() const;
|
||||
|
||||
int GetAmmoCount() const { return ammoCount; }
|
||||
|
||||
//
|
||||
// The feed interface the ballistic fire path consumes. Ammo states
|
||||
// (the 1995 feed alarm): 1 = a round is chambered/ready, 2 = EMPTY.
|
||||
// FeedAmmo pulls one round (the launcher calls it on a granted shot);
|
||||
// returns 0 when dry. (Feed-rate pacing + the cook-off heat source
|
||||
// join with the heat wave; the launcher's own recharge already paces
|
||||
// shots.)
|
||||
//
|
||||
enum {
|
||||
AmmoLoadedState = 1,
|
||||
AmmoEmptyState = 2
|
||||
};
|
||||
int
|
||||
GetAmmoState() const
|
||||
{ Check(this); return (ammoCount > 0) ? AmmoLoadedState : AmmoEmptyState; }
|
||||
//
|
||||
// Respawn restock: refill the magazine from the authored count.
|
||||
//
|
||||
virtual void
|
||||
DeathReset(Logical full_reset);
|
||||
|
||||
int
|
||||
FeedAmmo()
|
||||
{
|
||||
Check(this);
|
||||
if (ammoCount <= 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
--ammoCount;
|
||||
return 1;
|
||||
}
|
||||
|
||||
public:
|
||||
typedef AmmoBin__SubsystemResource SubsystemResource;
|
||||
|
||||
AmmoBin(
|
||||
Mech *owner,
|
||||
int subsystem_ID,
|
||||
SubsystemResource *subsystem_resource,
|
||||
SharedData &shared_data = DefaultData
|
||||
);
|
||||
~AmmoBin();
|
||||
|
||||
protected:
|
||||
int ammoCount;
|
||||
int initialAmmoCount;
|
||||
Scalar feedTimer;
|
||||
Scalar feedRate;
|
||||
int ammoClassID;
|
||||
int ammoModelFile;
|
||||
int explosionModelFile;
|
||||
Scalar heatPerRound;
|
||||
int cookOffArmed;
|
||||
int cookOffTime;
|
||||
int reserved;
|
||||
AlarmIndicator ammoAlarm;
|
||||
//
|
||||
// Cook-off heat-source registration state (advanced by the heat sim).
|
||||
// Reserved until the per-frame path is reconstructed.
|
||||
//
|
||||
int cookOffState[12];
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -179,3 +179,17 @@ The harness also sets the enemy's target back to the player: under
|
||||
BT_FORCE_FIRE the enemy returns fire (its gates read dev-permissive with no
|
||||
player link), so the damage cascade gets a live incoming side — used to
|
||||
verify the player's own weapons fall silent when destroyed.
|
||||
|
||||
## 5.3.24 — the VehicleDead(-1) cycle + respawn branch LIVE
|
||||
|
||||
The -1 immediate-death branch (binary @004c05c4): MissionEndingState swallow,
|
||||
deathPending dedup (one death one cycle), ++deathCount + stamp into the
|
||||
message (the base handler's identity gate), scenario-role life debit
|
||||
(NULL-safe), +5s re-post (RespawnDelay @004c0830) -> the engine base handler
|
||||
runs the drop-zone hunt -> reply -> the respawn branch resets the SAME mech
|
||||
(reset-based respawn; severing was the "two mechs" glitch family). The >=0
|
||||
re-posts/probes are MOOT once the mech lives again (clear deathPending,
|
||||
return -- ends the 2s probe loop). Out-of-lives -> mission review post =
|
||||
scoring wave. The fresh-spawn branch does NOT yet run the shared
|
||||
Reset/translocation-alarm tail (mech already placed by the MakeMessage;
|
||||
joins the cockpit wave).
|
||||
|
||||
@@ -460,7 +460,8 @@ void
|
||||
// resumes from Loading. (The owning-mech-disabled half joins with the
|
||||
// damage wave.)
|
||||
//
|
||||
if (GetSimulationState() == 1 || GetHeatState() == FailureHeat)
|
||||
if (GetSimulationState() == 1 || GetHeatState() == FailureHeat
|
||||
|| (owner != NULL && owner->IsMechDestroyed()))
|
||||
{
|
||||
if (getenv("BT_MECH_LOG") && GetWeaponState() != LoadingState)
|
||||
{
|
||||
|
||||
+1115
-1083
File diff suppressed because it is too large
Load Diff
+508
-495
File diff suppressed because it is too large
Load Diff
@@ -54,6 +54,7 @@
|
||||
#include <segment.hpp> // EntitySegment -- the skeleton segment table
|
||||
#include <mechdmg.hpp> // Mech::DamageZone -- the hull zone fill (Pass 3)
|
||||
#include <dmgtable.hpp> // DamageLookupTable -- the cylinder hit table
|
||||
#include <player.hpp> // Player::VehicleDeadMessage -- the death notification
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
@@ -189,6 +190,7 @@ Mech::Mech(
|
||||
targetEntity = NULL;
|
||||
lastInflictingID = EntityID::Null;
|
||||
damageLookupTable = NULL;
|
||||
deathTransitionDone = 0;
|
||||
|
||||
//
|
||||
// Look-view angles: defaults until the GameModel read below overrides them
|
||||
@@ -687,6 +689,80 @@ void
|
||||
subsystemArray[0] = subsystem;
|
||||
}
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
// Reset -- the respawn heal-and-move (binary @0049fb74). REUSES the same
|
||||
// entity (the sever-and-recreate respawn was the source of the 1995 port's
|
||||
// "two mechs / camera inside / on-fire respawn" glitch family):
|
||||
// 1. reposition at the drop zone (origin + transform + update base)
|
||||
// 2. kill all motion (a respawn is a TELEPORT, no dead-reckon lerp back)
|
||||
// 3. clear the death latch (the alarm trigger + the once-per-death flag)
|
||||
// 4. heal every hull zone (structure 0, intact skin, not burning) --
|
||||
// the crit-allotment accountant (damagePercentageUsed) PERSISTS by
|
||||
// design: nothing in the recovered binary resets it across lives
|
||||
// (BT411-observed; spent crit budgets stay spent)
|
||||
// 5. sweep the roster through DeathReset (heat/power/ammo/charge restore)
|
||||
// 6. revalidate for the sim (PreRun).
|
||||
// The ForceUpdate(0x1f) re-broadcast + warp/alarm effects join the render /
|
||||
// cockpit waves.
|
||||
//#############################################################################
|
||||
//
|
||||
void
|
||||
Mech::Reset(const Origin &origin, Logical full_reset)
|
||||
{
|
||||
Check(this);
|
||||
|
||||
localOrigin = origin;
|
||||
localToWorld = origin;
|
||||
updateOrigin = origin;
|
||||
|
||||
worldLinearVelocity = Vector3D(0.0f, 0.0f, 0.0f);
|
||||
localVelocity = Motion::Identity;
|
||||
updateVelocity.linearMotion = Vector3D(0.0f, 0.0f, 0.0f);
|
||||
updateVelocity.angularMotion = Vector3D(0.0f, 0.0f, 0.0f);
|
||||
currentBodySpeed = 0.0f;
|
||||
bodyTargetSpeed = 0.0f;
|
||||
|
||||
statusAlarm.SetLevel(0);
|
||||
deathTransitionDone = 0;
|
||||
|
||||
{
|
||||
for (int dz = 0; dz < damageZoneCount; ++dz)
|
||||
{
|
||||
if (damageZones[dz] != NULL)
|
||||
{
|
||||
::DamageZone *zone = (::DamageZone *)damageZones[dz];
|
||||
zone->damageLevel = 0.0f;
|
||||
zone->SetGraphicState(0);
|
||||
zone->SetDamageZoneState(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
for (int ss = 0; ss < subsystemCount; ++ss)
|
||||
{
|
||||
if (subsystemArray[ss] != NULL)
|
||||
{
|
||||
subsystemArray[ss]->DeathReset(full_reset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SetPreRunFlag();
|
||||
|
||||
if (getenv("BT_MECH_LOG"))
|
||||
{
|
||||
DEBUG_STREAM << "[reset] mech " << GetEntityID()
|
||||
<< " HEALED + placed at ("
|
||||
<< origin.linearPosition.x << ","
|
||||
<< origin.linearPosition.y << ","
|
||||
<< origin.linearPosition.z << ")" << endl << flush;
|
||||
}
|
||||
|
||||
Check_Fpu();
|
||||
}
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
// CurrentTorsoTwist -- the live torso twist for the cylinder table's
|
||||
@@ -886,6 +962,71 @@ void
|
||||
{
|
||||
Check(this);
|
||||
|
||||
//
|
||||
//-----------------------------------------------------------------------
|
||||
// DEATH (binary UpdateDeathState @mech4, the once-per-death transition):
|
||||
// a destroyed mech FREEZES -- no drive, no eyepoint, no dev harness (the
|
||||
// weapon hard gates silence the guns state-side). The one-shot: sweep
|
||||
// the roster through DeathShutdown(1) and dispatch the VehicleDead
|
||||
// death notification (deathCount = -1, the ctor default) to the owning
|
||||
// player -- only the owner master has a live playerLink; an unowned
|
||||
// wreck (the dev enemy) just settles.
|
||||
//-----------------------------------------------------------------------
|
||||
//
|
||||
if (IsMechDestroyed())
|
||||
{
|
||||
if (!deathTransitionDone)
|
||||
{
|
||||
deathTransitionDone = 1;
|
||||
|
||||
//
|
||||
// A wreck is STILL: kill the broadcastable motion so replicant
|
||||
// wrecks don't dead-reckon away at the last drive vector.
|
||||
//
|
||||
worldLinearVelocity = Vector3D(0.0f, 0.0f, 0.0f);
|
||||
updateVelocity.linearMotion = Vector3D(0.0f, 0.0f, 0.0f);
|
||||
updateVelocity.angularMotion = Vector3D(0.0f, 0.0f, 0.0f);
|
||||
currentBodySpeed = 0.0f;
|
||||
bodyTargetSpeed = 0.0f;
|
||||
|
||||
for (int ds = 0; ds < subsystemCount; ++ds)
|
||||
{
|
||||
if (subsystemArray[ds] != NULL)
|
||||
{
|
||||
subsystemArray[ds]->DeathShutdown(1);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// The death notification is MASTER-only: our
|
||||
// InitializePlayerLink binds replicant mechs to replicant
|
||||
// players too, and a replicant dispatch would run a second,
|
||||
// non-authoritative death cycle on every remote pod.
|
||||
//
|
||||
Player *pilot = (GetInstance() != Entity::ReplicantInstance)
|
||||
? GetPlayerLink() : NULL;
|
||||
if (pilot != NULL)
|
||||
{
|
||||
Player::VehicleDeadMessage
|
||||
dead(
|
||||
Player::VehicleDeadMessageID,
|
||||
sizeof(Player::VehicleDeadMessage)
|
||||
);
|
||||
pilot->Dispatch(&dead);
|
||||
}
|
||||
|
||||
if (getenv("BT_MECH_LOG"))
|
||||
{
|
||||
DEBUG_STREAM << "[death] mech " << GetEntityID()
|
||||
<< " WRECKED (pilot "
|
||||
<< ((pilot != NULL) ? "notified" : "none")
|
||||
<< ")" << endl << flush;
|
||||
}
|
||||
}
|
||||
Check_Fpu();
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
//-----------------------------------------------------------------------
|
||||
// Read the control-mapper locomotion demands. The mapper lives at roster
|
||||
|
||||
@@ -224,6 +224,15 @@
|
||||
void
|
||||
Simulate(Scalar time_slice);
|
||||
|
||||
//
|
||||
// The respawn heal-and-move (binary @0049fb74): reposition the SAME
|
||||
// entity at the drop-zone origin, kill all motion, clear the death
|
||||
// latch, heal every hull zone and sweep the roster through
|
||||
// DeathReset. Shared by the initial drop-in and the respawn.
|
||||
//
|
||||
void
|
||||
Reset(const Origin &origin, Logical full_reset);
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Resource creation
|
||||
//
|
||||
@@ -446,6 +455,8 @@
|
||||
Entity *targetEntity;
|
||||
EntityID lastInflictingID; // binary mech+0x43c -- last
|
||||
// attacker (zone LOD router)
|
||||
int deathTransitionDone; // the once-per-death latch
|
||||
// (binary movementMode 2||9)
|
||||
DamageLookupTable *damageLookupTable; // binary mech+0x444 -- the
|
||||
// cylinder hit-location table
|
||||
|
||||
@@ -462,7 +473,7 @@
|
||||
// mech2/mech3/mech4 simulation. Reserved until that path is
|
||||
// reconstructed with named fields.
|
||||
//
|
||||
int reservedState[198];
|
||||
int reservedState[197];
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -393,3 +393,37 @@ Found while verifying the novice-mode heat lockout end-to-end.
|
||||
- `friend class Mech__DamageZone` (BT411 mech.hpp:301 — authentic): the
|
||||
per-zone damage code walks the roster / segment table / statusAlarm.
|
||||
- `IsMechDestroyed()` = statusAlarm (the @0x714 body-graphic alarm) >= 9.
|
||||
|
||||
## 5.3.24 — the death -> respawn cycle (workflow-researched + adversarially reviewed)
|
||||
|
||||
- Once-per-death transition at the top of Mech::Simulate (binary
|
||||
UpdateDeathState): a destroyed mech FREEZES (early return -- no drive,
|
||||
eyepoint, or harness), zeroes broadcastable motion (replicant wrecks must
|
||||
not dead-reckon away), sweeps the roster through DeathShutdown(1), and --
|
||||
MASTER-only (our InitializePlayerLink binds replicants too, unlike the
|
||||
binary) -- dispatches Player::VehicleDeadMessage (deathCount = -1, the
|
||||
ctor default) to the player link. An unowned wreck (dev enemy) settles
|
||||
silently. deathTransitionDone = the binary's movementMode 2||9 latch
|
||||
(gait wave replaces it).
|
||||
- Mech::Reset(origin, full) (binary @0049fb74): REUSES the same entity --
|
||||
reposition + motion kill + latch clear + hull-zone heal + the roster
|
||||
DeathReset sweep + SetPreRunFlag. The crit-allotment accountant
|
||||
(damagePercentageUsed) PERSISTS by design (BT411-observed: nothing in the
|
||||
recovered binary resets it). ForceUpdate(0x1f)/warp/alarm = render+cockpit
|
||||
waves. Deferred: in-flight Seekers keep the raw target pointer across a
|
||||
respawn (a long-range round can chase the healed mech to the drop zone --
|
||||
matches the recovered seeker's shape; a life-check joins the missile wave).
|
||||
- Weapon hard gates gained the dead-owner term (emitter @4baab9 third
|
||||
clause): wrecks fall silent state-side. Dead-TARGET beam cut = render wave.
|
||||
- ADVERSARIAL REVIEW CATCHES (3-lens workflow, all fixed): AmmoBin restock
|
||||
read the FREED padBuffer through MechSubsystem::resource (use-after-free;
|
||||
now initialAmmoCount -- **resource is a LANDMINE: never deref post-ctor**);
|
||||
MechWeapon::DeathReset now chains the FULL powered/thermal restore (died-
|
||||
hot mechs no longer respawn at FailureHeat); Sensor chains the base heal;
|
||||
Generator preserves currentTapCount across reset (consumers stay tapped);
|
||||
PowerWatcher got its own DeathReset (non-virtual ResetToInitialState
|
||||
binds statically); cooling toggle + modeAlarm restored.
|
||||
- VERIFIED (killfight, BT_FORCE_ZONE=10): kill -> [vdead] +5s post -> engine
|
||||
drop-zone hunt -> AssignDropZone -> Reply(deathCount) -> [reset] at a REAL
|
||||
drop zone -> [respawn]; 0 shots while dead, 134 after respawn, 12 SRM
|
||||
fires post-respawn (restock proof); enemy wreck stays silent; zero faults.
|
||||
|
||||
@@ -1,310 +1,338 @@
|
||||
//===========================================================================//
|
||||
// File: mechsub.cpp //
|
||||
// Project: BattleTech Brick: Mech subsystems //
|
||||
// Contents: Implementation details for the MechSubsystem base //
|
||||
//---------------------------------------------------------------------------//
|
||||
// Copyright (C) 1995, Virtual World Entertainment, Inc. //
|
||||
// All Rights reserved worldwide //
|
||||
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL //
|
||||
//===========================================================================//
|
||||
|
||||
#include <bt.hpp>
|
||||
#pragma hdrstop
|
||||
|
||||
#if !defined(MECHSUB_HPP)
|
||||
# include <mechsub.hpp>
|
||||
#endif
|
||||
|
||||
#if !defined(MECH_HPP)
|
||||
# include <mech.hpp>
|
||||
#endif
|
||||
|
||||
#if !defined(DAMAGE_HPP)
|
||||
# include <damage.hpp>
|
||||
#endif
|
||||
|
||||
#if !defined(BTPLAYER_HPP)
|
||||
# include <btplayer.hpp>
|
||||
#endif
|
||||
|
||||
#if !defined(BTMSSN_HPP)
|
||||
# include <btmssn.hpp>
|
||||
#endif
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
// Shared data support -- MechSubsystem adds no message handlers or attributes
|
||||
// that are boot-critical, so it reuses the base Subsystem sets (which are the
|
||||
// inherited Simulation sets).
|
||||
//#############################################################################
|
||||
//
|
||||
Derivation
|
||||
MechSubsystem::ClassDerivations(
|
||||
Subsystem::ClassDerivations,
|
||||
"MechSubsystem"
|
||||
);
|
||||
|
||||
MechSubsystem::SharedData
|
||||
MechSubsystem::DefaultData(
|
||||
MechSubsystem::ClassDerivations,
|
||||
Subsystem::MessageHandlers,
|
||||
Subsystem::AttributeIndex,
|
||||
Subsystem::StateCount
|
||||
);
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
// Light constructor (name + classID). For a subsystem created without a
|
||||
// streamed resource; builds a trivial (armour-free) damage zone named by the
|
||||
// base.
|
||||
//#############################################################################
|
||||
//
|
||||
MechSubsystem::MechSubsystem(
|
||||
Mech *owner,
|
||||
int subsystem_ID,
|
||||
const char *subsystem_name,
|
||||
RegisteredClass::ClassID class_id,
|
||||
SharedData &shared_data
|
||||
):
|
||||
Subsystem(owner, subsystem_ID, subsystem_name, class_id, shared_data)
|
||||
{
|
||||
this->owner = owner;
|
||||
vitalSubsystem = False;
|
||||
alarmModel = ResourceDescription::NullResourceID;
|
||||
criticalReference = 0.0f;
|
||||
collisionCriticalHitWeight = 0.0f;
|
||||
printSimulationState = False;
|
||||
configureActivePress = -1;
|
||||
resource = NULL;
|
||||
statusAlarm.SetLevel(0);
|
||||
|
||||
//
|
||||
// The base Subsystem ctor leaves damageZone NULL; give this subsystem a
|
||||
// trivial (armour-free) zone.
|
||||
//
|
||||
damageZone = new DamageZone(owner, 0);
|
||||
Register_Object(damageZone);
|
||||
}
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
// Resource constructor. Chains the base Subsystem resource ctor and seeds the
|
||||
// mech-specific fields from the subsystem resource.
|
||||
//
|
||||
// The 4.10 damage model is per-damage-TYPE: the damage zone owns
|
||||
// defaultArmorPoints + damageScale[5] and streams them itself (a
|
||||
// Mech__DamageZone built from the model's damage-zone stream). Wiring that
|
||||
// stream through from the resource is deferred (see MECHSUB.NOTES.md); until
|
||||
// then the subsystem gets a trivial armour-free zone so it constructs and takes
|
||||
// damage with default (unscaled) behaviour.
|
||||
//#############################################################################
|
||||
//
|
||||
MechSubsystem::MechSubsystem(
|
||||
Mech *owner,
|
||||
int subsystem_ID,
|
||||
SubsystemResource *subsystem_resource,
|
||||
SharedData &shared_data
|
||||
):
|
||||
Subsystem(owner, subsystem_ID, subsystem_resource, shared_data)
|
||||
{
|
||||
Check_Pointer(subsystem_resource);
|
||||
|
||||
this->owner = owner;
|
||||
resource = subsystem_resource;
|
||||
vitalSubsystem = (subsystem_resource->vitalSubsystemIndex == 1);
|
||||
alarmModel = subsystem_resource->alarmModel;
|
||||
printSimulationState = subsystem_resource->printSimulationState;
|
||||
criticalReference = subsystem_resource->criticalReference;
|
||||
collisionCriticalHitWeight = subsystem_resource->collisionCriticalHitWeight;
|
||||
configureActivePress = -1;
|
||||
statusAlarm.SetLevel(0);
|
||||
|
||||
//
|
||||
// The subsystem's PRIVATE damage zone (binary @4abf28: `new
|
||||
// DamageZone(this, 0)` -- owned by the SUBSYSTEM, index 0, NOT an entry
|
||||
// in the entity's hull-zone array). The resource authors its armor the
|
||||
// same way the hull stream does -- structureReference points +
|
||||
// armorByFacing[5] per-type values -- normalized with the same rule as
|
||||
// Mech::DamageZone (scale = 1/(raw x points); already-normalized cells
|
||||
// pass through). This is what makes critical hits MEASURABLE
|
||||
// (ApplyDamageAndMeasure) and the crit-allotment push meaningful.
|
||||
//
|
||||
damageZone = new DamageZone(this, 0);
|
||||
Register_Object(damageZone);
|
||||
damageZone->damageZoneName = GetName();
|
||||
if (subsystem_resource->structureReference > 0.0f)
|
||||
{
|
||||
damageZone->defaultArmorPoints = subsystem_resource->structureReference;
|
||||
for (int tt = 0; tt < Damage::DamageTypeCount; ++tt)
|
||||
{
|
||||
Scalar raw = subsystem_resource->armorByFacing[tt];
|
||||
Scalar even = 1.0f / subsystem_resource->structureReference;
|
||||
Scalar diff = raw - even;
|
||||
if (diff < 0.0f)
|
||||
{
|
||||
diff = -diff;
|
||||
}
|
||||
if (diff > 1.0e-4f && raw > 0.0f)
|
||||
{
|
||||
damageZone->damageScale[tt] =
|
||||
1.0f / (raw * subsystem_resource->structureReference);
|
||||
}
|
||||
else
|
||||
{
|
||||
damageZone->damageScale[tt] = raw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
//#############################################################################
|
||||
//
|
||||
MechSubsystem::~MechSubsystem()
|
||||
{
|
||||
if (damageZone)
|
||||
{
|
||||
Unregister_Object(damageZone);
|
||||
delete damageZone;
|
||||
damageZone = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
//#############################################################################
|
||||
//
|
||||
Logical
|
||||
MechSubsystem::TestClass(Mech &)
|
||||
{
|
||||
return True;
|
||||
}
|
||||
|
||||
Logical
|
||||
MechSubsystem::TestInstance() const
|
||||
{
|
||||
return IsDerivedFrom(ClassDerivations);
|
||||
}
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
// Damage support -- the 4.10 damage zone tracks damageLevel in [0,1]
|
||||
// (0 = intact, 1 = destroyed).
|
||||
//#############################################################################
|
||||
//
|
||||
Scalar
|
||||
MechSubsystem::GetSubsystemDamageLevel() const
|
||||
{
|
||||
Check(this);
|
||||
return damageZone ? damageZone->damageLevel : 0.0f;
|
||||
}
|
||||
|
||||
void
|
||||
MechSubsystem::SetSubsystemDamageLevel(Scalar level)
|
||||
{
|
||||
Check(this);
|
||||
if (damageZone)
|
||||
{
|
||||
damageZone->damageLevel = level;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
MechSubsystem::ForceCriticalFailure()
|
||||
{
|
||||
Check(this);
|
||||
statusAlarm.SetLevel(MechSubsystem::Destroyed);
|
||||
//
|
||||
// DestroyedState is the hard-failure gate the weapon simulations poll
|
||||
// (`GetSimulationState() == 1`): a destroyed weapon drops its charge /
|
||||
// pins the unavailable alarm from the next frame on.
|
||||
//
|
||||
SetSimulationState(DestroyedState);
|
||||
if (damageZone)
|
||||
{
|
||||
damageZone->SetDamageZoneState(DamageZone::BurningState);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
// ApplyDamageAndMeasure (binary @4ac07c): apply through the virtual
|
||||
// TakeDamage and report the zone-level delta -- the critical accountant
|
||||
// (Mech::DamageZone::CriticalHit) charges the delta against the entry's
|
||||
// damagePercentage allotment.
|
||||
//#############################################################################
|
||||
//
|
||||
Scalar
|
||||
MechSubsystem::ApplyDamageAndMeasure(Damage &damage)
|
||||
{
|
||||
Check(this);
|
||||
Scalar before = (damageZone != NULL) ? damageZone->damageLevel : 0.0f;
|
||||
TakeDamage(damage);
|
||||
Scalar after = (damageZone != NULL) ? damageZone->damageLevel : 0.0f;
|
||||
return after - before;
|
||||
}
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
// TakeDamage Forward to the damage zone (per-type scaling happens there).
|
||||
//#############################################################################
|
||||
//
|
||||
void
|
||||
MechSubsystem::TakeDamage(Damage &damage)
|
||||
{
|
||||
Check(this);
|
||||
Check_Pointer(&damage);
|
||||
if (damageZone)
|
||||
{
|
||||
damageZone->TakeDamage(damage);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
// CreateStreamedSubsystem Model-load-time construction of the subsystem
|
||||
// resource + its damage-zone stream. Not yet reconstructed (see
|
||||
// MECHSUB.NOTES.md -- the 4.10 per-type damage-zone stream format).
|
||||
//#############################################################################
|
||||
//
|
||||
int
|
||||
MechSubsystem::CreateStreamedSubsystem(
|
||||
NotationFile *,
|
||||
const char *,
|
||||
const char *,
|
||||
SubsystemResource *,
|
||||
NotationFile *,
|
||||
const ResourceDirectories *,
|
||||
int
|
||||
)
|
||||
{
|
||||
Fail("MechSubsystem::CreateStreamedSubsystem -- mechsub.cpp not yet reconstructed");
|
||||
return 0;
|
||||
}
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
// NoviceLockout -- the novice-experience lockout predicate (binary @4ac9c8):
|
||||
// owner mech -> Entity::playerLink -> the BTPlayer's experience level == 0.
|
||||
// Locks the advanced cockpit controls (condenser valves, coolant flush,
|
||||
// cooling toggles). An unlinked mech reads UNLOCKED (dev-permissive).
|
||||
//#############################################################################
|
||||
//
|
||||
Logical
|
||||
MechSubsystem::NoviceLockout()
|
||||
{
|
||||
Check(this);
|
||||
|
||||
if (owner == NULL)
|
||||
{
|
||||
return False;
|
||||
}
|
||||
BTPlayer *player = Cast_Object(BTPlayer *, owner->GetPlayerLink());
|
||||
if (player == NULL)
|
||||
{
|
||||
return False;
|
||||
}
|
||||
return (player->GetExperienceLevel() == BTMission::NoviceMode)
|
||||
? True : False;
|
||||
}
|
||||
//===========================================================================//
|
||||
// File: mechsub.cpp //
|
||||
// Project: BattleTech Brick: Mech subsystems //
|
||||
// Contents: Implementation details for the MechSubsystem base //
|
||||
//---------------------------------------------------------------------------//
|
||||
// Copyright (C) 1995, Virtual World Entertainment, Inc. //
|
||||
// All Rights reserved worldwide //
|
||||
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL //
|
||||
//===========================================================================//
|
||||
|
||||
#include <bt.hpp>
|
||||
#pragma hdrstop
|
||||
|
||||
#if !defined(MECHSUB_HPP)
|
||||
# include <mechsub.hpp>
|
||||
#endif
|
||||
|
||||
#if !defined(MECH_HPP)
|
||||
# include <mech.hpp>
|
||||
#endif
|
||||
|
||||
#if !defined(DAMAGE_HPP)
|
||||
# include <damage.hpp>
|
||||
#endif
|
||||
|
||||
#if !defined(BTPLAYER_HPP)
|
||||
# include <btplayer.hpp>
|
||||
#endif
|
||||
|
||||
#if !defined(BTMSSN_HPP)
|
||||
# include <btmssn.hpp>
|
||||
#endif
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
// Shared data support -- MechSubsystem adds no message handlers or attributes
|
||||
// that are boot-critical, so it reuses the base Subsystem sets (which are the
|
||||
// inherited Simulation sets).
|
||||
//#############################################################################
|
||||
//
|
||||
Derivation
|
||||
MechSubsystem::ClassDerivations(
|
||||
Subsystem::ClassDerivations,
|
||||
"MechSubsystem"
|
||||
);
|
||||
|
||||
MechSubsystem::SharedData
|
||||
MechSubsystem::DefaultData(
|
||||
MechSubsystem::ClassDerivations,
|
||||
Subsystem::MessageHandlers,
|
||||
Subsystem::AttributeIndex,
|
||||
Subsystem::StateCount
|
||||
);
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
// Light constructor (name + classID). For a subsystem created without a
|
||||
// streamed resource; builds a trivial (armour-free) damage zone named by the
|
||||
// base.
|
||||
//#############################################################################
|
||||
//
|
||||
MechSubsystem::MechSubsystem(
|
||||
Mech *owner,
|
||||
int subsystem_ID,
|
||||
const char *subsystem_name,
|
||||
RegisteredClass::ClassID class_id,
|
||||
SharedData &shared_data
|
||||
):
|
||||
Subsystem(owner, subsystem_ID, subsystem_name, class_id, shared_data)
|
||||
{
|
||||
this->owner = owner;
|
||||
vitalSubsystem = False;
|
||||
alarmModel = ResourceDescription::NullResourceID;
|
||||
criticalReference = 0.0f;
|
||||
collisionCriticalHitWeight = 0.0f;
|
||||
printSimulationState = False;
|
||||
configureActivePress = -1;
|
||||
resource = NULL;
|
||||
statusAlarm.SetLevel(0);
|
||||
|
||||
//
|
||||
// The base Subsystem ctor leaves damageZone NULL; give this subsystem a
|
||||
// trivial (armour-free) zone.
|
||||
//
|
||||
damageZone = new DamageZone(owner, 0);
|
||||
Register_Object(damageZone);
|
||||
}
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
// Resource constructor. Chains the base Subsystem resource ctor and seeds the
|
||||
// mech-specific fields from the subsystem resource.
|
||||
//
|
||||
// The 4.10 damage model is per-damage-TYPE: the damage zone owns
|
||||
// defaultArmorPoints + damageScale[5] and streams them itself (a
|
||||
// Mech__DamageZone built from the model's damage-zone stream). Wiring that
|
||||
// stream through from the resource is deferred (see MECHSUB.NOTES.md); until
|
||||
// then the subsystem gets a trivial armour-free zone so it constructs and takes
|
||||
// damage with default (unscaled) behaviour.
|
||||
//#############################################################################
|
||||
//
|
||||
MechSubsystem::MechSubsystem(
|
||||
Mech *owner,
|
||||
int subsystem_ID,
|
||||
SubsystemResource *subsystem_resource,
|
||||
SharedData &shared_data
|
||||
):
|
||||
Subsystem(owner, subsystem_ID, subsystem_resource, shared_data)
|
||||
{
|
||||
Check_Pointer(subsystem_resource);
|
||||
|
||||
this->owner = owner;
|
||||
resource = subsystem_resource;
|
||||
vitalSubsystem = (subsystem_resource->vitalSubsystemIndex == 1);
|
||||
alarmModel = subsystem_resource->alarmModel;
|
||||
printSimulationState = subsystem_resource->printSimulationState;
|
||||
criticalReference = subsystem_resource->criticalReference;
|
||||
collisionCriticalHitWeight = subsystem_resource->collisionCriticalHitWeight;
|
||||
configureActivePress = -1;
|
||||
statusAlarm.SetLevel(0);
|
||||
|
||||
//
|
||||
// The subsystem's PRIVATE damage zone (binary @4abf28: `new
|
||||
// DamageZone(this, 0)` -- owned by the SUBSYSTEM, index 0, NOT an entry
|
||||
// in the entity's hull-zone array). The resource authors its armor the
|
||||
// same way the hull stream does -- structureReference points +
|
||||
// armorByFacing[5] per-type values -- normalized with the same rule as
|
||||
// Mech::DamageZone (scale = 1/(raw x points); already-normalized cells
|
||||
// pass through). This is what makes critical hits MEASURABLE
|
||||
// (ApplyDamageAndMeasure) and the crit-allotment push meaningful.
|
||||
//
|
||||
damageZone = new DamageZone(this, 0);
|
||||
Register_Object(damageZone);
|
||||
damageZone->damageZoneName = GetName();
|
||||
if (subsystem_resource->structureReference > 0.0f)
|
||||
{
|
||||
damageZone->defaultArmorPoints = subsystem_resource->structureReference;
|
||||
for (int tt = 0; tt < Damage::DamageTypeCount; ++tt)
|
||||
{
|
||||
Scalar raw = subsystem_resource->armorByFacing[tt];
|
||||
Scalar even = 1.0f / subsystem_resource->structureReference;
|
||||
Scalar diff = raw - even;
|
||||
if (diff < 0.0f)
|
||||
{
|
||||
diff = -diff;
|
||||
}
|
||||
if (diff > 1.0e-4f && raw > 0.0f)
|
||||
{
|
||||
damageZone->damageScale[tt] =
|
||||
1.0f / (raw * subsystem_resource->structureReference);
|
||||
}
|
||||
else
|
||||
{
|
||||
damageZone->damageScale[tt] = raw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
//#############################################################################
|
||||
//
|
||||
MechSubsystem::~MechSubsystem()
|
||||
{
|
||||
if (damageZone)
|
||||
{
|
||||
Unregister_Object(damageZone);
|
||||
delete damageZone;
|
||||
damageZone = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
//#############################################################################
|
||||
//
|
||||
Logical
|
||||
MechSubsystem::TestClass(Mech &)
|
||||
{
|
||||
return True;
|
||||
}
|
||||
|
||||
Logical
|
||||
MechSubsystem::TestInstance() const
|
||||
{
|
||||
return IsDerivedFrom(ClassDerivations);
|
||||
}
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
// Damage support -- the 4.10 damage zone tracks damageLevel in [0,1]
|
||||
// (0 = intact, 1 = destroyed).
|
||||
//#############################################################################
|
||||
//
|
||||
Scalar
|
||||
MechSubsystem::GetSubsystemDamageLevel() const
|
||||
{
|
||||
Check(this);
|
||||
return damageZone ? damageZone->damageLevel : 0.0f;
|
||||
}
|
||||
|
||||
void
|
||||
MechSubsystem::SetSubsystemDamageLevel(Scalar level)
|
||||
{
|
||||
Check(this);
|
||||
if (damageZone)
|
||||
{
|
||||
damageZone->damageLevel = level;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
MechSubsystem::ForceCriticalFailure()
|
||||
{
|
||||
Check(this);
|
||||
//
|
||||
// Binary @4ac8xx family: the status alarm's DESTROYED display level is 1
|
||||
// (the TechStatusType enum's Destroyed=0 is the display CATEGORY, not
|
||||
// the alarm level -- BT411 disasm: "+0x2C: 1 = Destroyed").
|
||||
//
|
||||
statusAlarm.SetLevel(1);
|
||||
//
|
||||
// DestroyedState is the hard-failure gate the weapon simulations poll
|
||||
// (`GetSimulationState() == 1`): a destroyed weapon drops its charge /
|
||||
// pins the unavailable alarm from the next frame on.
|
||||
//
|
||||
SetSimulationState(DestroyedState);
|
||||
if (damageZone)
|
||||
{
|
||||
damageZone->SetDamageZoneState(DamageZone::BurningState);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
// DeathReset -- the respawn restore (the engine base virtual pair's reset
|
||||
// half; Mech::Reset @0049fb74 loops every subsystem through it). The base:
|
||||
// heal the private zone, clear the Destroyed display level and -- the
|
||||
// load-bearing part -- clear DestroyedState so the weapon hard gates
|
||||
// (GetSimulationState() == 1) release.
|
||||
//#############################################################################
|
||||
//
|
||||
void
|
||||
MechSubsystem::DeathReset(Logical /*full_reset*/)
|
||||
{
|
||||
Check(this);
|
||||
|
||||
if (damageZone != NULL)
|
||||
{
|
||||
damageZone->damageLevel = 0.0f;
|
||||
damageZone->SetDamageZoneState(0);
|
||||
}
|
||||
statusAlarm.SetLevel(0);
|
||||
SetSimulationState(Simulation::DefaultState);
|
||||
}
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
// ApplyDamageAndMeasure (binary @4ac07c): apply through the virtual
|
||||
// TakeDamage and report the zone-level delta -- the critical accountant
|
||||
// (Mech::DamageZone::CriticalHit) charges the delta against the entry's
|
||||
// damagePercentage allotment.
|
||||
//#############################################################################
|
||||
//
|
||||
Scalar
|
||||
MechSubsystem::ApplyDamageAndMeasure(Damage &damage)
|
||||
{
|
||||
Check(this);
|
||||
Scalar before = (damageZone != NULL) ? damageZone->damageLevel : 0.0f;
|
||||
TakeDamage(damage);
|
||||
Scalar after = (damageZone != NULL) ? damageZone->damageLevel : 0.0f;
|
||||
return after - before;
|
||||
}
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
// TakeDamage Forward to the damage zone (per-type scaling happens there).
|
||||
//#############################################################################
|
||||
//
|
||||
void
|
||||
MechSubsystem::TakeDamage(Damage &damage)
|
||||
{
|
||||
Check(this);
|
||||
Check_Pointer(&damage);
|
||||
if (damageZone)
|
||||
{
|
||||
damageZone->TakeDamage(damage);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
// CreateStreamedSubsystem Model-load-time construction of the subsystem
|
||||
// resource + its damage-zone stream. Not yet reconstructed (see
|
||||
// MECHSUB.NOTES.md -- the 4.10 per-type damage-zone stream format).
|
||||
//#############################################################################
|
||||
//
|
||||
int
|
||||
MechSubsystem::CreateStreamedSubsystem(
|
||||
NotationFile *,
|
||||
const char *,
|
||||
const char *,
|
||||
SubsystemResource *,
|
||||
NotationFile *,
|
||||
const ResourceDirectories *,
|
||||
int
|
||||
)
|
||||
{
|
||||
Fail("MechSubsystem::CreateStreamedSubsystem -- mechsub.cpp not yet reconstructed");
|
||||
return 0;
|
||||
}
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
// NoviceLockout -- the novice-experience lockout predicate (binary @4ac9c8):
|
||||
// owner mech -> Entity::playerLink -> the BTPlayer's experience level == 0.
|
||||
// Locks the advanced cockpit controls (condenser valves, coolant flush,
|
||||
// cooling toggles). An unlinked mech reads UNLOCKED (dev-permissive).
|
||||
//#############################################################################
|
||||
//
|
||||
Logical
|
||||
MechSubsystem::NoviceLockout()
|
||||
{
|
||||
Check(this);
|
||||
|
||||
if (owner == NULL)
|
||||
{
|
||||
return False;
|
||||
}
|
||||
BTPlayer *player = Cast_Object(BTPlayer *, owner->GetPlayerLink());
|
||||
if (player == NULL)
|
||||
{
|
||||
return False;
|
||||
}
|
||||
return (player->GetExperienceLevel() == BTMission::NoviceMode)
|
||||
? True : False;
|
||||
}
|
||||
|
||||
@@ -1,192 +1,202 @@
|
||||
//===========================================================================//
|
||||
// File: mechsub.hpp //
|
||||
// Project: BattleTech //
|
||||
// Contents: Implementation details for mech subsystems //
|
||||
//---------------------------------------------------------------------------//
|
||||
// Date Who Modification //
|
||||
// -------- --- ---------------------------------------------------------- //
|
||||
// //
|
||||
//---------------------------------------------------------------------------//
|
||||
// Copyright (C) 1995, Virtual World Entertainment, Inc. //
|
||||
// All Rights reserved worldwide //
|
||||
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL //
|
||||
//===========================================================================//
|
||||
|
||||
#if !defined(MECHSUB_HPP)
|
||||
# define MECHSUB_HPP
|
||||
|
||||
# if !defined(SUBSYSTM_HPP)
|
||||
# include <subsystm.hpp>
|
||||
# endif
|
||||
|
||||
# if !defined(ALARM_HPP)
|
||||
# include <alarm.hpp>
|
||||
# endif
|
||||
|
||||
//##################### Forward Class Declarations #######################
|
||||
class Mech;
|
||||
class Damage;
|
||||
|
||||
//###########################################################################
|
||||
//#################### MechSubsystem Model Resource #####################
|
||||
//###########################################################################
|
||||
|
||||
struct MechSubsystem__SubsystemResource:
|
||||
public Subsystem::SubsystemResource
|
||||
{
|
||||
Scalar armorByFacing[5];
|
||||
Scalar structureReference;
|
||||
int vitalSubsystemIndex;
|
||||
char videoObjectName[128];
|
||||
ResourceDescription::ResourceID
|
||||
alarmModel;
|
||||
int alarmModelReserved[2];
|
||||
int printSimulationState;
|
||||
Scalar collisionCriticalHitWeight;
|
||||
Scalar criticalReference;
|
||||
};
|
||||
|
||||
//###########################################################################
|
||||
//########################## MechSubsystem ##############################
|
||||
//###########################################################################
|
||||
|
||||
class MechSubsystem:
|
||||
public Subsystem
|
||||
{
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Shared Data Support
|
||||
//
|
||||
public:
|
||||
static Derivation ClassDerivations;
|
||||
static SharedData DefaultData;
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Status model
|
||||
//
|
||||
public:
|
||||
enum TechStatusType {
|
||||
Destroyed = 0,
|
||||
Damaged = 1,
|
||||
CoolantLeaking = 2,
|
||||
Overheating = 3,
|
||||
AmmoBurning = 4,
|
||||
Jammed = 5,
|
||||
BadPower = 6,
|
||||
TechStatusTypeCount
|
||||
};
|
||||
|
||||
//
|
||||
// The subsystem SIMULATION states (binary PrintState @4ac8c0 string
|
||||
// pool: "Destroyed" @0050df7f, "Exploding" @0050df8c). DestroyedState
|
||||
// (1) is the hard-failure gate every weapon simulation checks
|
||||
// (`GetSimulationState() == 1` -- emitter @4baab9, ballistic @4bbd36).
|
||||
//
|
||||
enum {
|
||||
DestroyedState = Simulation::DefaultState + 1,
|
||||
ExplodingState,
|
||||
MechSubsystemStateCount
|
||||
};
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Test Class Support
|
||||
//
|
||||
public:
|
||||
static Logical
|
||||
TestClass(Mech &);
|
||||
Logical
|
||||
TestInstance() const;
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Construction and Destruction
|
||||
//
|
||||
public:
|
||||
typedef MechSubsystem__SubsystemResource SubsystemResource;
|
||||
|
||||
MechSubsystem(
|
||||
Mech *owner,
|
||||
int subsystem_ID,
|
||||
const char *subsystem_name,
|
||||
RegisteredClass::ClassID class_id,
|
||||
SharedData &shared_data = DefaultData
|
||||
);
|
||||
|
||||
MechSubsystem(
|
||||
Mech *owner,
|
||||
int subsystem_ID,
|
||||
SubsystemResource *subsystem_resource,
|
||||
SharedData &shared_data = DefaultData
|
||||
);
|
||||
|
||||
~MechSubsystem();
|
||||
|
||||
static int
|
||||
CreateStreamedSubsystem(
|
||||
NotationFile *model_file,
|
||||
const char *model_name,
|
||||
const char *subsystem_name,
|
||||
SubsystemResource *subsystem_resource,
|
||||
NotationFile *subsystem_file,
|
||||
const ResourceDirectories *directories,
|
||||
int passes = 1
|
||||
);
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Damage support
|
||||
//
|
||||
public:
|
||||
virtual void
|
||||
TakeDamage(Damage &damage);
|
||||
|
||||
Scalar
|
||||
GetSubsystemDamageLevel() const;
|
||||
void
|
||||
SetSubsystemDamageLevel(Scalar level);
|
||||
void
|
||||
ForceCriticalFailure();
|
||||
|
||||
//
|
||||
// Critical-hit application (binary @4ac07c): run the damage through
|
||||
// TakeDamage and return how much the subsystem's own zone level
|
||||
// actually moved (the crit accountant charges it against the entry's
|
||||
// damagePercentage allotment).
|
||||
//
|
||||
Scalar
|
||||
ApplyDamageAndMeasure(Damage &damage);
|
||||
|
||||
Logical
|
||||
IsVitalSubsystem() const { Check(this); return vitalSubsystem; }
|
||||
|
||||
//
|
||||
// The NOVICE-experience lockout predicate (binary @4ac9c8): a novice
|
||||
// pilot's advanced cockpit controls (condenser valves, coolant flush,
|
||||
// cooling toggles) are locked out. Owner mech -> playerLink ->
|
||||
// experience level == novice. Unlinked mechs read UNLOCKED.
|
||||
//
|
||||
Logical
|
||||
NoviceLockout();
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Local Data
|
||||
//
|
||||
protected:
|
||||
Mech
|
||||
*owner;
|
||||
AlarmIndicator
|
||||
statusAlarm;
|
||||
Logical
|
||||
vitalSubsystem;
|
||||
ResourceDescription::ResourceID
|
||||
alarmModel;
|
||||
Scalar
|
||||
criticalReference;
|
||||
Scalar
|
||||
collisionCriticalHitWeight;
|
||||
Logical
|
||||
printSimulationState;
|
||||
int
|
||||
configureActivePress;
|
||||
SubsystemResource
|
||||
*resource;
|
||||
};
|
||||
|
||||
#endif
|
||||
//===========================================================================//
|
||||
// File: mechsub.hpp //
|
||||
// Project: BattleTech //
|
||||
// Contents: Implementation details for mech subsystems //
|
||||
//---------------------------------------------------------------------------//
|
||||
// Date Who Modification //
|
||||
// -------- --- ---------------------------------------------------------- //
|
||||
// //
|
||||
//---------------------------------------------------------------------------//
|
||||
// Copyright (C) 1995, Virtual World Entertainment, Inc. //
|
||||
// All Rights reserved worldwide //
|
||||
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL //
|
||||
//===========================================================================//
|
||||
|
||||
#if !defined(MECHSUB_HPP)
|
||||
# define MECHSUB_HPP
|
||||
|
||||
# if !defined(SUBSYSTM_HPP)
|
||||
# include <subsystm.hpp>
|
||||
# endif
|
||||
|
||||
# if !defined(ALARM_HPP)
|
||||
# include <alarm.hpp>
|
||||
# endif
|
||||
|
||||
//##################### Forward Class Declarations #######################
|
||||
class Mech;
|
||||
class Damage;
|
||||
|
||||
//###########################################################################
|
||||
//#################### MechSubsystem Model Resource #####################
|
||||
//###########################################################################
|
||||
|
||||
struct MechSubsystem__SubsystemResource:
|
||||
public Subsystem::SubsystemResource
|
||||
{
|
||||
Scalar armorByFacing[5];
|
||||
Scalar structureReference;
|
||||
int vitalSubsystemIndex;
|
||||
char videoObjectName[128];
|
||||
ResourceDescription::ResourceID
|
||||
alarmModel;
|
||||
int alarmModelReserved[2];
|
||||
int printSimulationState;
|
||||
Scalar collisionCriticalHitWeight;
|
||||
Scalar criticalReference;
|
||||
};
|
||||
|
||||
//###########################################################################
|
||||
//########################## MechSubsystem ##############################
|
||||
//###########################################################################
|
||||
|
||||
class MechSubsystem:
|
||||
public Subsystem
|
||||
{
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Shared Data Support
|
||||
//
|
||||
public:
|
||||
static Derivation ClassDerivations;
|
||||
static SharedData DefaultData;
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Status model
|
||||
//
|
||||
public:
|
||||
enum TechStatusType {
|
||||
Destroyed = 0,
|
||||
Damaged = 1,
|
||||
CoolantLeaking = 2,
|
||||
Overheating = 3,
|
||||
AmmoBurning = 4,
|
||||
Jammed = 5,
|
||||
BadPower = 6,
|
||||
TechStatusTypeCount
|
||||
};
|
||||
|
||||
//
|
||||
// The subsystem SIMULATION states (binary PrintState @4ac8c0 string
|
||||
// pool: "Destroyed" @0050df7f, "Exploding" @0050df8c). DestroyedState
|
||||
// (1) is the hard-failure gate every weapon simulation checks
|
||||
// (`GetSimulationState() == 1` -- emitter @4baab9, ballistic @4bbd36).
|
||||
//
|
||||
enum {
|
||||
DestroyedState = Simulation::DefaultState + 1,
|
||||
ExplodingState,
|
||||
MechSubsystemStateCount
|
||||
};
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Test Class Support
|
||||
//
|
||||
public:
|
||||
static Logical
|
||||
TestClass(Mech &);
|
||||
Logical
|
||||
TestInstance() const;
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Construction and Destruction
|
||||
//
|
||||
public:
|
||||
typedef MechSubsystem__SubsystemResource SubsystemResource;
|
||||
|
||||
MechSubsystem(
|
||||
Mech *owner,
|
||||
int subsystem_ID,
|
||||
const char *subsystem_name,
|
||||
RegisteredClass::ClassID class_id,
|
||||
SharedData &shared_data = DefaultData
|
||||
);
|
||||
|
||||
MechSubsystem(
|
||||
Mech *owner,
|
||||
int subsystem_ID,
|
||||
SubsystemResource *subsystem_resource,
|
||||
SharedData &shared_data = DefaultData
|
||||
);
|
||||
|
||||
~MechSubsystem();
|
||||
|
||||
static int
|
||||
CreateStreamedSubsystem(
|
||||
NotationFile *model_file,
|
||||
const char *model_name,
|
||||
const char *subsystem_name,
|
||||
SubsystemResource *subsystem_resource,
|
||||
NotationFile *subsystem_file,
|
||||
const ResourceDirectories *directories,
|
||||
int passes = 1
|
||||
);
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Damage support
|
||||
//
|
||||
public:
|
||||
virtual void
|
||||
TakeDamage(Damage &damage);
|
||||
|
||||
Scalar
|
||||
GetSubsystemDamageLevel() const;
|
||||
void
|
||||
SetSubsystemDamageLevel(Scalar level);
|
||||
void
|
||||
ForceCriticalFailure();
|
||||
|
||||
//
|
||||
// Critical-hit application (binary @4ac07c): run the damage through
|
||||
// TakeDamage and return how much the subsystem's own zone level
|
||||
// actually moved (the crit accountant charges it against the entry's
|
||||
// damagePercentage allotment).
|
||||
//
|
||||
Scalar
|
||||
ApplyDamageAndMeasure(Damage &damage);
|
||||
|
||||
Logical
|
||||
IsVitalSubsystem() const { Check(this); return vitalSubsystem; }
|
||||
|
||||
//
|
||||
// The respawn restore (engine base virtual, SUBSYSTM.HPP:184 -- the
|
||||
// pair with DeathShutdown). Mech::Reset sweeps every roster slot;
|
||||
// the base heals the subsystem's private zone and clears the
|
||||
// Destroyed simulation state (the weapon hard gate). full_reset is
|
||||
// the binary's reset_command (every recovered override ignores it).
|
||||
//
|
||||
virtual void
|
||||
DeathReset(Logical full_reset);
|
||||
|
||||
//
|
||||
// The NOVICE-experience lockout predicate (binary @4ac9c8): a novice
|
||||
// pilot's advanced cockpit controls (condenser valves, coolant flush,
|
||||
// cooling toggles) are locked out. Owner mech -> playerLink ->
|
||||
// experience level == novice. Unlinked mechs read UNLOCKED.
|
||||
//
|
||||
Logical
|
||||
NoviceLockout();
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Local Data
|
||||
//
|
||||
protected:
|
||||
Mech
|
||||
*owner;
|
||||
AlarmIndicator
|
||||
statusAlarm;
|
||||
Logical
|
||||
vitalSubsystem;
|
||||
ResourceDescription::ResourceID
|
||||
alarmModel;
|
||||
Scalar
|
||||
criticalReference;
|
||||
Scalar
|
||||
collisionCriticalHitWeight;
|
||||
Logical
|
||||
printSimulationState;
|
||||
int
|
||||
configureActivePress;
|
||||
SubsystemResource
|
||||
*resource;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -203,6 +203,34 @@ MechWeapon::MechWeapon(
|
||||
Check_Fpu();
|
||||
}
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
// DeathReset -- the weapon half of the respawn restore.
|
||||
//#############################################################################
|
||||
//
|
||||
void
|
||||
MechWeapon::DeathReset(Logical full_reset)
|
||||
{
|
||||
Check(this);
|
||||
|
||||
//
|
||||
// The FULL powered-chain restore (base heal + thermal + electrical
|
||||
// re-init): a mech that died hot must NOT respawn with guns still at
|
||||
// FailureHeat (the hard gate + the jam roll read the carried
|
||||
// temperature). The electrical FSM re-enters Starting and lifts to
|
||||
// Ready on the next sim frame (startTimer holds startTime).
|
||||
//
|
||||
PoweredSubsystem::DeathReset(full_reset);
|
||||
|
||||
fireImpulse = 0.0f;
|
||||
previousFireImpulse = 0.0f;
|
||||
rechargeLevel = 0.0f;
|
||||
recoil = 0.0f;
|
||||
rangeToTarget = 0.0f;
|
||||
targetWithinRange = False;
|
||||
weaponAlarm.SetLevel(LoadingState);
|
||||
}
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
// The weapon-group config buttons (ids 9/10). STAGED: the binary bodies
|
||||
|
||||
@@ -1,263 +1,271 @@
|
||||
//===========================================================================//
|
||||
// File: mechweap.hpp //
|
||||
// Project: BattleTech //
|
||||
// Contents: Implementation details for mech weapons //
|
||||
//---------------------------------------------------------------------------//
|
||||
// Date Who Modification //
|
||||
// -------- --- ---------------------------------------------------------- //
|
||||
// //
|
||||
//---------------------------------------------------------------------------//
|
||||
// Copyright (C) 1995, Virtual World Entertainment, Inc. //
|
||||
// All Rights reserved worldwide //
|
||||
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL //
|
||||
//===========================================================================//
|
||||
|
||||
#if !defined(MECHWEAP_HPP)
|
||||
# define MECHWEAP_HPP
|
||||
|
||||
# if !defined(POWERSUB_HPP)
|
||||
# include <powersub.hpp>
|
||||
# endif
|
||||
|
||||
# if !defined(DAMAGE_HPP)
|
||||
# include <damage.hpp>
|
||||
# endif
|
||||
|
||||
# if !defined(ALARM_HPP)
|
||||
# include <alarm.hpp>
|
||||
# endif
|
||||
|
||||
//##################### Forward Class Declarations #######################
|
||||
class Mech;
|
||||
|
||||
//###########################################################################
|
||||
//###################### MechWeapon Model Resource ######################
|
||||
//###########################################################################
|
||||
|
||||
//
|
||||
// WIRE-VERIFIED layout (raw-stream dump vs the BT411 verified overlay):
|
||||
// eleven fields; the pip tail is pipPosition(int) + pipColor(3 floats) +
|
||||
// pipExtendedRange(int). bhk1 PPC reads: recharge 5.0s, range 900,
|
||||
// damage 12, type 4, heatCost 11, pipColor (0,0,1).
|
||||
//
|
||||
struct MechWeapon__SubsystemResource:
|
||||
public PoweredSubsystem::SubsystemResource
|
||||
{
|
||||
Scalar rechargeRate;
|
||||
Scalar weaponRange;
|
||||
ResourceDescription::ResourceID
|
||||
explosionModelFile;
|
||||
Scalar damageAmount;
|
||||
int damageType;
|
||||
Scalar heatCostToFire;
|
||||
int pipPosition;
|
||||
Scalar pipColor[3];
|
||||
int pipExtendedRange;
|
||||
};
|
||||
|
||||
//###########################################################################
|
||||
//############################ MechWeapon ###############################
|
||||
//###########################################################################
|
||||
|
||||
class MechWeapon:
|
||||
public PoweredSubsystem
|
||||
{
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Shared Data Support
|
||||
//
|
||||
public:
|
||||
static Derivation ClassDerivations;
|
||||
static SharedData DefaultData;
|
||||
static const HandlerEntry MessageHandlerEntries[];
|
||||
static MessageHandlerSet MessageHandlers;
|
||||
|
||||
//
|
||||
// The weapon-group config buttons (binary ids 9/10, chained after
|
||||
// the PoweredSubsystem generator panel 4-8).
|
||||
//
|
||||
enum {
|
||||
ConfigureMappablesMessageID = PoweredSubsystem::NextMessageID, // 9
|
||||
ChooseButtonMessageID, // 10
|
||||
NextMessageID // 11
|
||||
};
|
||||
|
||||
void
|
||||
ConfigureMappablesMessageHandler(ReceiverDataMessageOf<int> *message);
|
||||
void
|
||||
ChooseButtonMessageHandler(ReceiverDataMessageOf<int> *message);
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Attribute Support. The real IDs are PINNED to the 1995 binary numbering
|
||||
// (table @0x511890, ids 0x12..0x1C): the streamed per-mech control mappings
|
||||
// reference them by NUMBER -- TriggerState (0x13) is the fire-button binding.
|
||||
// Our parent chain publishes nothing past Subsystem::NextAttributeID (2)
|
||||
// yet, so pads bridge the gap 2..0x11 (they shrink to the authentic
|
||||
// 0x0F..0x11 once the mechsub/heat/powersub attribute waves publish --
|
||||
// SENSOR.HPP pins PoweredSubsystem::NextAttributeID at 0x0F). The pads are
|
||||
// LOAD-BEARING: AttributeIndexSet::Build leaves unlisted gap slots as
|
||||
// uninitialized garbage, so every ID up to the max must be covered.
|
||||
//
|
||||
public:
|
||||
enum {
|
||||
MechWeaponPadFirstAttributeID = Subsystem::NextAttributeID, // 2
|
||||
PercentDoneAttributeID = 0x12,
|
||||
TriggerStateAttributeID, // 0x13 -- the streamed fire-button binding
|
||||
DistanceToTargetAttributeID, // 0x14
|
||||
TargetWithinRangeAttributeID, // 0x15
|
||||
WeaponRangeAttributeID, // 0x16
|
||||
PipPositionAttributeID, // 0x17
|
||||
PipColorAttributeID, // 0x18
|
||||
PipExtendedRangeAttributeID, // 0x19
|
||||
EstimatedReadyTimeAttributeID, // 0x1A
|
||||
RearFiringAttributeID, // 0x1B
|
||||
WeaponStateAttributeID, // 0x1C (binary table end)
|
||||
NextAttributeID // 0x1D (the Emitter family starts here)
|
||||
};
|
||||
|
||||
static const IndexEntry AttributePointers[];
|
||||
static AttributeIndexSet AttributeIndex;
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Test Class Support
|
||||
//
|
||||
public:
|
||||
static Logical
|
||||
TestClass(Mech &);
|
||||
Logical
|
||||
TestInstance() const;
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Construction and Destruction
|
||||
//
|
||||
public:
|
||||
typedef MechWeapon__SubsystemResource SubsystemResource;
|
||||
|
||||
MechWeapon(
|
||||
Mech *owner,
|
||||
int subsystem_ID,
|
||||
SubsystemResource *subsystem_resource,
|
||||
SharedData &shared_data = DefaultData
|
||||
);
|
||||
|
||||
~MechWeapon();
|
||||
|
||||
static int
|
||||
CreateStreamedSubsystem(
|
||||
ResourceFile *resource_file,
|
||||
NotationFile *model_file,
|
||||
const char *model_name,
|
||||
const char *subsystem_name,
|
||||
SubsystemResource *subsystem_resource,
|
||||
NotationFile *subsystem_file,
|
||||
const ResourceDirectories *directories,
|
||||
int passes = 1
|
||||
);
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Weapon interface
|
||||
//
|
||||
public:
|
||||
virtual void
|
||||
FireWeapon();
|
||||
|
||||
void
|
||||
SendDamage(
|
||||
Entity *target,
|
||||
Damage &damage
|
||||
);
|
||||
|
||||
//
|
||||
// View-fire gating (the look-state commit re-arms these): a weapon
|
||||
// mounted on a back gun port (rearFiring) fires only into the LOOK-BACK
|
||||
// view; the rest fire only in the forward view.
|
||||
//
|
||||
Logical
|
||||
IsRearFiring() const { Check(this); return rearFiring; }
|
||||
Logical
|
||||
GetViewFireEnable() const { Check(this); return viewFireEnable; }
|
||||
void
|
||||
SetViewFireEnable(Logical enable){ Check(this); viewFireEnable = enable; }
|
||||
|
||||
//
|
||||
// Fire state machine. The weapon state is carried in the weapon alarm
|
||||
// level: 0 = Firing, 2 = Loaded (ready), 3 = Loading, 4 = the
|
||||
// trigger-during-load blip. CheckFireEdge is the rising-edge detector
|
||||
// on fireImpulse (binary @004b9608): TriggerState carries the raw
|
||||
// ControlsButton INT bit-copied by the streamed direct mapping, so the
|
||||
// compare works on the BIT PATTERNS as signed ints (sign-correct for
|
||||
// the button ints AND genuine floats), reproducing the binary's x87
|
||||
// semantics. ComputeOutputVoltage (@004b9c9c) is the recharge-dial
|
||||
// writer.
|
||||
//
|
||||
enum {
|
||||
FiringState = 0,
|
||||
DryTriggerState = 1,
|
||||
LoadedState = 2,
|
||||
LoadingState = 3,
|
||||
TriggerDuringLoadState = 4,
|
||||
JammedState = 5,
|
||||
TriggerDuringJamState = 6,
|
||||
NoAmmoState = 7,
|
||||
WeaponStateCount = 8
|
||||
};
|
||||
|
||||
unsigned
|
||||
GetWeaponState() { Check(this); return weaponAlarm.GetLevel(); }
|
||||
Logical
|
||||
CheckFireEdge();
|
||||
virtual void
|
||||
ComputeOutputVoltage(); // vtable slot 17; the Emitter overrides
|
||||
// with its charge-voltage form
|
||||
|
||||
//
|
||||
// Targeting: the owner mech's target slot gates the Loaded->Firing
|
||||
// transition (no target = the denial blip, no shot); UpdateTargeting
|
||||
// (binary @004b9bdc) refreshes rangeToTarget / targetWithinRange from
|
||||
// the target's position each frame.
|
||||
//
|
||||
Logical
|
||||
HasActiveTarget();
|
||||
Logical
|
||||
UpdateTargeting();
|
||||
|
||||
//
|
||||
// The muzzle world position (binary @004b9948): the MOUNT segment's
|
||||
// world frame -- rounds and beams leave FROM THE GUN, following the
|
||||
// torso twist. Falls back to the mech origin when the mount
|
||||
// segment doesn't resolve.
|
||||
//
|
||||
void
|
||||
GetMuzzlePoint(Point3D &point);
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Local Data
|
||||
//
|
||||
protected:
|
||||
Scalar rechargeRate;
|
||||
Scalar weaponRange;
|
||||
Scalar damageAmount;
|
||||
int damageType;
|
||||
Scalar heatCostToFire;
|
||||
ResourceDescription::ResourceID
|
||||
explosionResourceID; // binary @0x3e4 -- the projectile /
|
||||
// explosion GameModel this weapon spawns
|
||||
int targetWithinRange;
|
||||
Damage damageData;
|
||||
Logical rearFiring;
|
||||
Logical viewFireEnable;
|
||||
//
|
||||
// Fire-machine state (binary @0x31C/0x320/0x324/0x328/0x330/0x350/
|
||||
// 0x3A4/0x3E8): the trigger sample pair, the recharge dial, targeting
|
||||
// ranges, and the weapon-state alarm.
|
||||
//
|
||||
Scalar fireImpulse;
|
||||
Scalar previousFireImpulse;
|
||||
Scalar rechargeLevel;
|
||||
Scalar rangeToTarget;
|
||||
Scalar effectiveRange;
|
||||
int estimatedReadyTime;
|
||||
Scalar recoil;
|
||||
AlarmIndicator weaponAlarm;
|
||||
};
|
||||
|
||||
#endif
|
||||
//===========================================================================//
|
||||
// File: mechweap.hpp //
|
||||
// Project: BattleTech //
|
||||
// Contents: Implementation details for mech weapons //
|
||||
//---------------------------------------------------------------------------//
|
||||
// Date Who Modification //
|
||||
// -------- --- ---------------------------------------------------------- //
|
||||
// //
|
||||
//---------------------------------------------------------------------------//
|
||||
// Copyright (C) 1995, Virtual World Entertainment, Inc. //
|
||||
// All Rights reserved worldwide //
|
||||
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL //
|
||||
//===========================================================================//
|
||||
|
||||
#if !defined(MECHWEAP_HPP)
|
||||
# define MECHWEAP_HPP
|
||||
|
||||
# if !defined(POWERSUB_HPP)
|
||||
# include <powersub.hpp>
|
||||
# endif
|
||||
|
||||
# if !defined(DAMAGE_HPP)
|
||||
# include <damage.hpp>
|
||||
# endif
|
||||
|
||||
# if !defined(ALARM_HPP)
|
||||
# include <alarm.hpp>
|
||||
# endif
|
||||
|
||||
//##################### Forward Class Declarations #######################
|
||||
class Mech;
|
||||
|
||||
//###########################################################################
|
||||
//###################### MechWeapon Model Resource ######################
|
||||
//###########################################################################
|
||||
|
||||
//
|
||||
// WIRE-VERIFIED layout (raw-stream dump vs the BT411 verified overlay):
|
||||
// eleven fields; the pip tail is pipPosition(int) + pipColor(3 floats) +
|
||||
// pipExtendedRange(int). bhk1 PPC reads: recharge 5.0s, range 900,
|
||||
// damage 12, type 4, heatCost 11, pipColor (0,0,1).
|
||||
//
|
||||
struct MechWeapon__SubsystemResource:
|
||||
public PoweredSubsystem::SubsystemResource
|
||||
{
|
||||
Scalar rechargeRate;
|
||||
Scalar weaponRange;
|
||||
ResourceDescription::ResourceID
|
||||
explosionModelFile;
|
||||
Scalar damageAmount;
|
||||
int damageType;
|
||||
Scalar heatCostToFire;
|
||||
int pipPosition;
|
||||
Scalar pipColor[3];
|
||||
int pipExtendedRange;
|
||||
};
|
||||
|
||||
//###########################################################################
|
||||
//############################ MechWeapon ###############################
|
||||
//###########################################################################
|
||||
|
||||
class MechWeapon:
|
||||
public PoweredSubsystem
|
||||
{
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Shared Data Support
|
||||
//
|
||||
public:
|
||||
static Derivation ClassDerivations;
|
||||
static SharedData DefaultData;
|
||||
static const HandlerEntry MessageHandlerEntries[];
|
||||
static MessageHandlerSet MessageHandlers;
|
||||
|
||||
//
|
||||
// The weapon-group config buttons (binary ids 9/10, chained after
|
||||
// the PoweredSubsystem generator panel 4-8).
|
||||
//
|
||||
enum {
|
||||
ConfigureMappablesMessageID = PoweredSubsystem::NextMessageID, // 9
|
||||
ChooseButtonMessageID, // 10
|
||||
NextMessageID // 11
|
||||
};
|
||||
|
||||
void
|
||||
ConfigureMappablesMessageHandler(ReceiverDataMessageOf<int> *message);
|
||||
void
|
||||
ChooseButtonMessageHandler(ReceiverDataMessageOf<int> *message);
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Attribute Support. The real IDs are PINNED to the 1995 binary numbering
|
||||
// (table @0x511890, ids 0x12..0x1C): the streamed per-mech control mappings
|
||||
// reference them by NUMBER -- TriggerState (0x13) is the fire-button binding.
|
||||
// Our parent chain publishes nothing past Subsystem::NextAttributeID (2)
|
||||
// yet, so pads bridge the gap 2..0x11 (they shrink to the authentic
|
||||
// 0x0F..0x11 once the mechsub/heat/powersub attribute waves publish --
|
||||
// SENSOR.HPP pins PoweredSubsystem::NextAttributeID at 0x0F). The pads are
|
||||
// LOAD-BEARING: AttributeIndexSet::Build leaves unlisted gap slots as
|
||||
// uninitialized garbage, so every ID up to the max must be covered.
|
||||
//
|
||||
public:
|
||||
enum {
|
||||
MechWeaponPadFirstAttributeID = Subsystem::NextAttributeID, // 2
|
||||
PercentDoneAttributeID = 0x12,
|
||||
TriggerStateAttributeID, // 0x13 -- the streamed fire-button binding
|
||||
DistanceToTargetAttributeID, // 0x14
|
||||
TargetWithinRangeAttributeID, // 0x15
|
||||
WeaponRangeAttributeID, // 0x16
|
||||
PipPositionAttributeID, // 0x17
|
||||
PipColorAttributeID, // 0x18
|
||||
PipExtendedRangeAttributeID, // 0x19
|
||||
EstimatedReadyTimeAttributeID, // 0x1A
|
||||
RearFiringAttributeID, // 0x1B
|
||||
WeaponStateAttributeID, // 0x1C (binary table end)
|
||||
NextAttributeID // 0x1D (the Emitter family starts here)
|
||||
};
|
||||
|
||||
static const IndexEntry AttributePointers[];
|
||||
static AttributeIndexSet AttributeIndex;
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Test Class Support
|
||||
//
|
||||
public:
|
||||
static Logical
|
||||
TestClass(Mech &);
|
||||
Logical
|
||||
TestInstance() const;
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Construction and Destruction
|
||||
//
|
||||
public:
|
||||
typedef MechWeapon__SubsystemResource SubsystemResource;
|
||||
|
||||
MechWeapon(
|
||||
Mech *owner,
|
||||
int subsystem_ID,
|
||||
SubsystemResource *subsystem_resource,
|
||||
SharedData &shared_data = DefaultData
|
||||
);
|
||||
|
||||
~MechWeapon();
|
||||
|
||||
static int
|
||||
CreateStreamedSubsystem(
|
||||
ResourceFile *resource_file,
|
||||
NotationFile *model_file,
|
||||
const char *model_name,
|
||||
const char *subsystem_name,
|
||||
SubsystemResource *subsystem_resource,
|
||||
NotationFile *subsystem_file,
|
||||
const ResourceDirectories *directories,
|
||||
int passes = 1
|
||||
);
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Weapon interface
|
||||
//
|
||||
public:
|
||||
virtual void
|
||||
FireWeapon();
|
||||
|
||||
void
|
||||
SendDamage(
|
||||
Entity *target,
|
||||
Damage &damage
|
||||
);
|
||||
|
||||
//
|
||||
// View-fire gating (the look-state commit re-arms these): a weapon
|
||||
// mounted on a back gun port (rearFiring) fires only into the LOOK-BACK
|
||||
// view; the rest fire only in the forward view.
|
||||
//
|
||||
Logical
|
||||
IsRearFiring() const { Check(this); return rearFiring; }
|
||||
Logical
|
||||
GetViewFireEnable() const { Check(this); return viewFireEnable; }
|
||||
void
|
||||
SetViewFireEnable(Logical enable){ Check(this); viewFireEnable = enable; }
|
||||
|
||||
//
|
||||
// Fire state machine. The weapon state is carried in the weapon alarm
|
||||
// level: 0 = Firing, 2 = Loaded (ready), 3 = Loading, 4 = the
|
||||
// trigger-during-load blip. CheckFireEdge is the rising-edge detector
|
||||
// on fireImpulse (binary @004b9608): TriggerState carries the raw
|
||||
// ControlsButton INT bit-copied by the streamed direct mapping, so the
|
||||
// compare works on the BIT PATTERNS as signed ints (sign-correct for
|
||||
// the button ints AND genuine floats), reproducing the binary's x87
|
||||
// semantics. ComputeOutputVoltage (@004b9c9c) is the recharge-dial
|
||||
// writer.
|
||||
//
|
||||
enum {
|
||||
FiringState = 0,
|
||||
DryTriggerState = 1,
|
||||
LoadedState = 2,
|
||||
LoadingState = 3,
|
||||
TriggerDuringLoadState = 4,
|
||||
JammedState = 5,
|
||||
TriggerDuringJamState = 6,
|
||||
NoAmmoState = 7,
|
||||
WeaponStateCount = 8
|
||||
};
|
||||
|
||||
unsigned
|
||||
GetWeaponState() { Check(this); return weaponAlarm.GetLevel(); }
|
||||
Logical
|
||||
CheckFireEdge();
|
||||
virtual void
|
||||
ComputeOutputVoltage(); // vtable slot 17; the Emitter overrides
|
||||
// with its charge-voltage form
|
||||
|
||||
//
|
||||
// Targeting: the owner mech's target slot gates the Loaded->Firing
|
||||
// transition (no target = the denial blip, no shot); UpdateTargeting
|
||||
// (binary @004b9bdc) refreshes rangeToTarget / targetWithinRange from
|
||||
// the target's position each frame.
|
||||
//
|
||||
Logical
|
||||
HasActiveTarget();
|
||||
Logical
|
||||
UpdateTargeting();
|
||||
|
||||
//
|
||||
// Respawn restore: base heal + the fire machine back to a fresh
|
||||
// Loading cycle (charge/recoil/trigger cleared; jams and the NoAmmo
|
||||
// roach-motel release -- the bins restock in their own DeathReset).
|
||||
//
|
||||
virtual void
|
||||
DeathReset(Logical full_reset);
|
||||
|
||||
//
|
||||
// The muzzle world position (binary @004b9948): the MOUNT segment's
|
||||
// world frame -- rounds and beams leave FROM THE GUN, following the
|
||||
// torso twist. Falls back to the mech origin when the mount
|
||||
// segment doesn't resolve.
|
||||
//
|
||||
void
|
||||
GetMuzzlePoint(Point3D &point);
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Local Data
|
||||
//
|
||||
protected:
|
||||
Scalar rechargeRate;
|
||||
Scalar weaponRange;
|
||||
Scalar damageAmount;
|
||||
int damageType;
|
||||
Scalar heatCostToFire;
|
||||
ResourceDescription::ResourceID
|
||||
explosionResourceID; // binary @0x3e4 -- the projectile /
|
||||
// explosion GameModel this weapon spawns
|
||||
int targetWithinRange;
|
||||
Damage damageData;
|
||||
Logical rearFiring;
|
||||
Logical viewFireEnable;
|
||||
//
|
||||
// Fire-machine state (binary @0x31C/0x320/0x324/0x328/0x330/0x350/
|
||||
// 0x3A4/0x3E8): the trigger sample pair, the recharge dial, targeting
|
||||
// ranges, and the weapon-state alarm.
|
||||
//
|
||||
Scalar fireImpulse;
|
||||
Scalar previousFireImpulse;
|
||||
Scalar rechargeLevel;
|
||||
Scalar rangeToTarget;
|
||||
Scalar effectiveRange;
|
||||
int estimatedReadyTime;
|
||||
Scalar recoil;
|
||||
AlarmIndicator weaponAlarm;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,450 +1,455 @@
|
||||
//===========================================================================//
|
||||
// File: mislanch.cpp //
|
||||
// Project: BattleTech Brick: Mech weapons //
|
||||
// Contents: MissileLauncher -- a projectile weapon that fires missile salvos //
|
||||
//---------------------------------------------------------------------------//
|
||||
// Copyright (C) 1995, Virtual World Entertainment, Inc. //
|
||||
// All Rights reserved worldwide //
|
||||
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL //
|
||||
//===========================================================================//
|
||||
|
||||
#include <bt.hpp>
|
||||
#pragma hdrstop
|
||||
|
||||
#if !defined(MISLANCH_HPP)
|
||||
# include <mislanch.hpp>
|
||||
#endif
|
||||
|
||||
#if !defined(MECH_HPP)
|
||||
# include <mech.hpp>
|
||||
#endif
|
||||
|
||||
#if !defined(MISSILE_HPP)
|
||||
# include <missile.hpp>
|
||||
#endif
|
||||
|
||||
#if !defined(APP_HPP)
|
||||
# include <app.hpp>
|
||||
#endif
|
||||
|
||||
#if !defined(HOSTMGR_HPP)
|
||||
# include <hostmgr.hpp>
|
||||
#endif
|
||||
|
||||
//
|
||||
// STAGED launch speed (the binary derives it from the authored
|
||||
// MuzzleVelocity vector -- the muzzle wave).
|
||||
//
|
||||
static const Scalar kMissileLaunchSpeed = 150.0f; // u/s
|
||||
|
||||
Derivation
|
||||
MissileLauncher::ClassDerivations(
|
||||
ProjectileWeapon::ClassDerivations,
|
||||
"MissileLauncher"
|
||||
);
|
||||
|
||||
MissileLauncher::SharedData
|
||||
MissileLauncher::DefaultData(
|
||||
MissileLauncher::ClassDerivations,
|
||||
Subsystem::MessageHandlers,
|
||||
Subsystem::AttributeIndex,
|
||||
Subsystem::StateCount
|
||||
);
|
||||
|
||||
MissileLauncher::MissileLauncher(
|
||||
Mech *owner,
|
||||
int subsystem_ID,
|
||||
SubsystemResource *subsystem_resource,
|
||||
SharedData &default_data
|
||||
):
|
||||
ProjectileWeapon(owner, subsystem_ID, subsystem_resource, default_data)
|
||||
{
|
||||
Check(owner);
|
||||
Check_Pointer(subsystem_resource);
|
||||
|
||||
missileCount = subsystem_resource->missileCount;
|
||||
|
||||
//
|
||||
// A salvo delivers missileCount missiles; split the per-shot damage across
|
||||
// the salvo. (The per-missile launch mechanics live in FireWeapon, staged.)
|
||||
//
|
||||
if (missileCount > 0)
|
||||
{
|
||||
damageData.burstCount = missileCount;
|
||||
damageData.damageAmount = damageData.damageAmount / (Scalar)missileCount;
|
||||
}
|
||||
|
||||
Check_Fpu();
|
||||
}
|
||||
|
||||
MissileLauncher::~MissileLauncher()
|
||||
{
|
||||
}
|
||||
|
||||
Logical
|
||||
MissileLauncher::TestInstance() const
|
||||
{
|
||||
return IsDerivedFrom(ClassDerivations);
|
||||
}
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
// Damage / death.
|
||||
//#############################################################################
|
||||
//
|
||||
void
|
||||
MissileLauncher::TakeDamage(Damage &damage)
|
||||
{
|
||||
Check(this);
|
||||
ProjectileWeapon::TakeDamage(damage);
|
||||
}
|
||||
|
||||
void
|
||||
MissileLauncher::DeathReset(Logical /*full_reset*/)
|
||||
{
|
||||
Check(this);
|
||||
}
|
||||
|
||||
//
|
||||
// The missile-salvo discharge. Not yet reconstructed.
|
||||
//
|
||||
void
|
||||
MissileLauncher::FireWeapon()
|
||||
{
|
||||
Check(this);
|
||||
|
||||
//
|
||||
// PARTIAL (binary @004bcc60): the authentic body launches missileCount
|
||||
// Missile entities toward the owner's target (each carrying the salvo-split
|
||||
// damageData) and dumps the firing heat. The Missile entity spawn needs
|
||||
// the entity-spawn + targeting waves; the view/target gate, ammo pull and
|
||||
// recoil all live in the CALLER (ProjectileWeaponSimulation's Loaded case).
|
||||
//
|
||||
// Ballistic heatCostToFire is stored 1e7-unit-NATIVE in the stream (SRM6
|
||||
// reads 5.06e7 = +641K on its own sink -- the same design magnitude as the
|
||||
// PPC's +632K); dump it raw, under the heat-model experience gate (novice /
|
||||
// standard fire generates no heat -- authentic). (The EMITTER's authored
|
||||
// value is small and its 1e7 comes from the energy algebra.)
|
||||
if (HeatModelActive())
|
||||
{
|
||||
AddPendingHeat(heatCostToFire);
|
||||
}
|
||||
|
||||
//
|
||||
// The salvo launch (binary @004bcc60): ONE cluster Missile entity per
|
||||
// trigger, spawned through the Mover make machinery -- the MakeMessage's
|
||||
// resourceID is this weapon's projectile GameModel (explosionResourceID
|
||||
// @0x3e4), so the Mover base streams the authored mass/drag. The round
|
||||
// carries the salvo-split cluster damage record (burstCount =
|
||||
// missileCount rides into the gyro bounce + splash) and homes via its
|
||||
// Seeker; the proximity fuse delivers the damage ONCE (the arcade
|
||||
// economy, BT411 task #62). Muzzle = the mech origin STAGED (the
|
||||
// authored MuzzleVelocity arc through the MOUNT segment frame is the
|
||||
// muzzle wave). If the projectile model resource is absent the launch
|
||||
// falls back to the 5.3.16 instant-hit delivery.
|
||||
//
|
||||
if (targetWithinRange && owner != NULL
|
||||
&& owner->GetTargetEntity() != NULL
|
||||
&& getenv("BT_MISSILE_INSTANT") != NULL)
|
||||
{
|
||||
//
|
||||
// DEV opt-out (BT_MISSILE_INSTANT=1): the 5.3.18a instant-hit
|
||||
// cluster message, kept for A/B damage-economy checks. The flying
|
||||
// Missile below is the DEFAULT since 5.3.19 (teardown closed: the
|
||||
// Projectile ctor NULLs the inherited zone slots).
|
||||
//
|
||||
SendDamage(owner->GetTargetEntity(), damageData);
|
||||
}
|
||||
else if (targetWithinRange && owner != NULL
|
||||
&& owner->GetTargetEntity() != NULL)
|
||||
{
|
||||
//
|
||||
// The MakeMessage's resourceID must be a resource FAMILY id -- the
|
||||
// Mover base ctor runs its own SearchList(resourceID, GameModel),
|
||||
// and SearchList CRASHES (engine @0x414938) when the id isn't in
|
||||
// the top-level directory (member resources resolve only through
|
||||
// their family head). The streamed explosionModelFile is a small
|
||||
// INDEX (SRM6 reads 17) into a model list -- decoding it into the
|
||||
// real projectile family is the projectile-model brick. Until
|
||||
// then the round spawns on the OWNER's family (its GameModel
|
||||
// member provably streams every boot -- the mech model params);
|
||||
// our staged flight integration reads none of the Mover mass/drag.
|
||||
//
|
||||
{
|
||||
//
|
||||
// DEV one-shot (BT_MODEL_DUMP=1): raw-dump the mech's ModelList
|
||||
// stream -- the decode bench for the explosionModelFile INDEX
|
||||
// (SRM6 reads 17). The owner's make resourceID IS the
|
||||
// ModelList record (CreatePlayerVehicle passes
|
||||
// mech_res->resourceID).
|
||||
//
|
||||
{
|
||||
static int dumped = 0;
|
||||
if (!dumped && getenv("BT_MODEL_DUMP"))
|
||||
{
|
||||
dumped = 1;
|
||||
ResourceDescription *ml =
|
||||
application->GetResourceFile()->
|
||||
FindResourceDescription(owner->GetResourceID());
|
||||
if (ml != NULL)
|
||||
{
|
||||
ml->Lock();
|
||||
unsigned char *raw =
|
||||
(unsigned char *)ml->resourceAddress;
|
||||
int size = ml->resourceSize;
|
||||
DEBUG_STREAM << "[mdl] list res id=" << ml->resourceID
|
||||
<< " type=" << ml->resourceType
|
||||
<< " size=" << size
|
||||
<< " count=" << *(int *)raw << endl;
|
||||
int limit = (size < 640) ? size : 640;
|
||||
for (int bb = 0; bb < limit; bb += 16)
|
||||
{
|
||||
DEBUG_STREAM << "[mdl] +" << bb << ":";
|
||||
int cc;
|
||||
for (cc = bb; cc < bb + 16 && cc < limit; ++cc)
|
||||
{
|
||||
DEBUG_STREAM << " " << (unsigned)raw[cc];
|
||||
}
|
||||
DEBUG_STREAM << " ";
|
||||
for (cc = bb; cc < bb + 16 && cc < limit; ++cc)
|
||||
{
|
||||
char ch = (raw[cc] >= 32 && raw[cc] < 127)
|
||||
? (char)raw[cc] : '.';
|
||||
DEBUG_STREAM << ch;
|
||||
}
|
||||
DEBUG_STREAM << endl;
|
||||
}
|
||||
//
|
||||
// Identify the members + the streamed
|
||||
// explosionModelFile value by name/type.
|
||||
//
|
||||
int nn = *(int *)raw;
|
||||
if (nn > 0 && nn < 40)
|
||||
{
|
||||
int *ids = (int *)(raw + 4);
|
||||
for (int ee = 0; ee < nn; ++ee)
|
||||
{
|
||||
ResourceDescription *mm =
|
||||
application->GetResourceFile()->
|
||||
FindResourceDescription(ids[ee]);
|
||||
DEBUG_STREAM << "[mdl] member[" << ee
|
||||
<< "] id=" << ids[ee];
|
||||
if (mm != NULL)
|
||||
{
|
||||
DEBUG_STREAM << " type=" << mm->resourceType
|
||||
<< " name=" << mm->resourceName;
|
||||
}
|
||||
DEBUG_STREAM << endl;
|
||||
}
|
||||
}
|
||||
{
|
||||
ResourceDescription *ex =
|
||||
application->GetResourceFile()->
|
||||
FindResourceDescription(
|
||||
explosionResourceID);
|
||||
DEBUG_STREAM << "[mdl] explosionModelFile="
|
||||
<< explosionResourceID;
|
||||
if (ex != NULL)
|
||||
{
|
||||
DEBUG_STREAM << " -> type=" << ex->resourceType
|
||||
<< " name=" << ex->resourceName
|
||||
<< " size=" << ex->resourceSize;
|
||||
}
|
||||
else
|
||||
{
|
||||
DEBUG_STREAM << " -> NOT IN DIRECTORY";
|
||||
}
|
||||
DEBUG_STREAM << endl;
|
||||
|
||||
//
|
||||
// Walk the explosion family's OWN members.
|
||||
//
|
||||
if (ex != NULL && ex->resourceType ==
|
||||
ResourceDescription::ModelListResourceType)
|
||||
{
|
||||
ex->Lock();
|
||||
int *xr = (int *)ex->resourceAddress;
|
||||
int xn = xr[0];
|
||||
DEBUG_STREAM << "[mdl] '" << ex->resourceName
|
||||
<< "' members=" << xn << endl;
|
||||
for (int xx = 0; xx < xn && xx < 16; ++xx)
|
||||
{
|
||||
ResourceDescription *xm =
|
||||
application->GetResourceFile()->
|
||||
FindResourceDescription(xr[1 + xx]);
|
||||
DEBUG_STREAM << "[mdl] [" << xx
|
||||
<< "] id=" << xr[1 + xx];
|
||||
if (xm != NULL)
|
||||
{
|
||||
DEBUG_STREAM
|
||||
<< " type=" << xm->resourceType
|
||||
<< " name=" << xm->resourceName
|
||||
<< " size=" << xm->resourceSize;
|
||||
}
|
||||
DEBUG_STREAM << endl;
|
||||
|
||||
//
|
||||
// The GameModel member: dump as floats
|
||||
// (the missile model record -- lifetime
|
||||
// @+0x44, thrust @+0x48, mode @+0x50).
|
||||
//
|
||||
if (xm != NULL && xm->resourceType ==
|
||||
ResourceDescription::GameModelResourceType)
|
||||
{
|
||||
xm->Lock();
|
||||
float *fv = (float *)xm->resourceAddress;
|
||||
int nf = xm->resourceSize / 4;
|
||||
for (int ff = 0; ff < nf; ++ff)
|
||||
{
|
||||
DEBUG_STREAM << "[mdl] +"
|
||||
<< (ff * 4)
|
||||
<< " f=" << fv[ff]
|
||||
<< " i=" << ((int *)fv)[ff]
|
||||
<< endl;
|
||||
}
|
||||
xm->Unlock();
|
||||
}
|
||||
}
|
||||
ex->Unlock();
|
||||
}
|
||||
}
|
||||
DEBUG_STREAM << flush;
|
||||
ml->Unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Resolve the round's resource FAMILY. The streamed
|
||||
// explosionModelFile IS a family id after all (SRM6 = 17 =
|
||||
// 'mslhit': VideoModel + AudioStreamList + an 8-byte GameModel
|
||||
// {int 498, maxTimeOfFlight 5.0}). Guard: only use it when the
|
||||
// family really carries a GameModel MEMBER -- the Mover base
|
||||
// ctor SearchLists for one and CRASHES on a missing member
|
||||
// (the walk runs off the family list). Fallback = the owner's
|
||||
// family (GameModel provably present).
|
||||
//
|
||||
ResourceDescription::ResourceID round_family =
|
||||
owner->GetResourceID();
|
||||
if (explosionResourceID != ResourceDescription::NullResourceID)
|
||||
{
|
||||
ResourceDescription *family =
|
||||
application->GetResourceFile()->
|
||||
FindResourceDescription(explosionResourceID);
|
||||
if (family != NULL
|
||||
&& family->resourceType
|
||||
== ResourceDescription::ModelListResourceType)
|
||||
{
|
||||
family->Lock();
|
||||
int *members = (int *)family->resourceAddress;
|
||||
int member_count = members[0];
|
||||
Logical family_complete = True;
|
||||
Logical has_game_model = False;
|
||||
for (int mi = 0; mi < member_count; ++mi)
|
||||
{
|
||||
ResourceDescription *member =
|
||||
application->GetResourceFile()->
|
||||
FindResourceDescription(members[1 + mi]);
|
||||
if (member == NULL)
|
||||
{
|
||||
//
|
||||
// A member id absent from the directory makes
|
||||
// the engine SearchList walk CRASH -- this
|
||||
// family is unusable for the Mover make.
|
||||
//
|
||||
family_complete = False;
|
||||
}
|
||||
else if (member->resourceType
|
||||
== ResourceDescription::GameModelResourceType)
|
||||
{
|
||||
has_game_model = True;
|
||||
}
|
||||
}
|
||||
if (family_complete && has_game_model)
|
||||
{
|
||||
round_family = explosionResourceID;
|
||||
//
|
||||
// Keep the family RESIDENT (no Unlock): dropping a
|
||||
// first-touch family back to lockCount 0 left the
|
||||
// engine's re-Lock inside the Mover ctor reading a
|
||||
// dead list (SearchList returned NULL on members we
|
||||
// just resolved). The binary's launcher holds its
|
||||
// projectile model locked for the mission anyway.
|
||||
//
|
||||
}
|
||||
else
|
||||
{
|
||||
family->Unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HostManager *host_manager = application->GetHostManager();
|
||||
Check(host_manager);
|
||||
|
||||
//
|
||||
// The round leaves FROM THE GUN: the mount segment's world
|
||||
// frame (GetMuzzlePoint @004b9948 -- follows the torso twist).
|
||||
// The rotation stays the mech's (the missile aims by velocity).
|
||||
//
|
||||
Origin muzzle = owner->localOrigin;
|
||||
GetMuzzlePoint(muzzle.linearPosition);
|
||||
//
|
||||
// NoCollisionVolumeFlag is LOAD-BEARING: collision volumes are
|
||||
// the Mover DEFAULT (IsCollisionVolume = the flag NOT set), and
|
||||
// the ctor then demands a BoxedSolidStream member -- which a
|
||||
// projectile family doesn't carry (SearchList NULL ->
|
||||
// Lock() page fault). The round's collision is the world
|
||||
// query / proximity fuse, not a boxed solid.
|
||||
//
|
||||
Mover::MakeMessage
|
||||
spawn_round(
|
||||
Entity::MakeMessageID,
|
||||
sizeof(Mover::MakeMessage),
|
||||
EntityID(host_manager->GetLocalHostID()),
|
||||
(Entity::ClassID)MissileClassID,
|
||||
EntityID::Null,
|
||||
round_family,
|
||||
Entity::DefaultFlags | Mover::NoCollisionVolumeFlag,
|
||||
muzzle,
|
||||
Motion::Identity,
|
||||
Motion::Identity
|
||||
);
|
||||
Missile *round = new Missile(&spawn_round);
|
||||
Register_Object(round);
|
||||
round->Launch(
|
||||
owner,
|
||||
owner->GetTargetEntity(),
|
||||
damageData,
|
||||
kMissileLaunchSpeed
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (getenv("BT_MECH_LOG"))
|
||||
{
|
||||
DEBUG_STREAM << "[fire] '" << GetName()
|
||||
<< "' FIRED (salvo of " << missileCount
|
||||
<< ", recharge=" << rechargeRate
|
||||
<< "s heat+=" << heatCostToFire << ")" << endl << flush;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Model-load-time construction. Not yet reconstructed.
|
||||
//
|
||||
Logical
|
||||
MissileLauncher::CreateStreamedSubsystem(
|
||||
ResourceFile *,
|
||||
NotationFile *,
|
||||
const char *,
|
||||
const char *,
|
||||
SubsystemResource *,
|
||||
NotationFile *,
|
||||
const ResourceDirectories *,
|
||||
int
|
||||
)
|
||||
{
|
||||
Fail("MissileLauncher::CreateStreamedSubsystem -- mislanch.cpp not yet reconstructed");
|
||||
return False;
|
||||
}
|
||||
//===========================================================================//
|
||||
// File: mislanch.cpp //
|
||||
// Project: BattleTech Brick: Mech weapons //
|
||||
// Contents: MissileLauncher -- a projectile weapon that fires missile salvos //
|
||||
//---------------------------------------------------------------------------//
|
||||
// Copyright (C) 1995, Virtual World Entertainment, Inc. //
|
||||
// All Rights reserved worldwide //
|
||||
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL //
|
||||
//===========================================================================//
|
||||
|
||||
#include <bt.hpp>
|
||||
#pragma hdrstop
|
||||
|
||||
#if !defined(MISLANCH_HPP)
|
||||
# include <mislanch.hpp>
|
||||
#endif
|
||||
|
||||
#if !defined(MECH_HPP)
|
||||
# include <mech.hpp>
|
||||
#endif
|
||||
|
||||
#if !defined(MISSILE_HPP)
|
||||
# include <missile.hpp>
|
||||
#endif
|
||||
|
||||
#if !defined(APP_HPP)
|
||||
# include <app.hpp>
|
||||
#endif
|
||||
|
||||
#if !defined(HOSTMGR_HPP)
|
||||
# include <hostmgr.hpp>
|
||||
#endif
|
||||
|
||||
//
|
||||
// STAGED launch speed (the binary derives it from the authored
|
||||
// MuzzleVelocity vector -- the muzzle wave).
|
||||
//
|
||||
static const Scalar kMissileLaunchSpeed = 150.0f; // u/s
|
||||
|
||||
Derivation
|
||||
MissileLauncher::ClassDerivations(
|
||||
ProjectileWeapon::ClassDerivations,
|
||||
"MissileLauncher"
|
||||
);
|
||||
|
||||
MissileLauncher::SharedData
|
||||
MissileLauncher::DefaultData(
|
||||
MissileLauncher::ClassDerivations,
|
||||
Subsystem::MessageHandlers,
|
||||
Subsystem::AttributeIndex,
|
||||
Subsystem::StateCount
|
||||
);
|
||||
|
||||
MissileLauncher::MissileLauncher(
|
||||
Mech *owner,
|
||||
int subsystem_ID,
|
||||
SubsystemResource *subsystem_resource,
|
||||
SharedData &default_data
|
||||
):
|
||||
ProjectileWeapon(owner, subsystem_ID, subsystem_resource, default_data)
|
||||
{
|
||||
Check(owner);
|
||||
Check_Pointer(subsystem_resource);
|
||||
|
||||
missileCount = subsystem_resource->missileCount;
|
||||
|
||||
//
|
||||
// A salvo delivers missileCount missiles; split the per-shot damage across
|
||||
// the salvo. (The per-missile launch mechanics live in FireWeapon, staged.)
|
||||
//
|
||||
if (missileCount > 0)
|
||||
{
|
||||
damageData.burstCount = missileCount;
|
||||
damageData.damageAmount = damageData.damageAmount / (Scalar)missileCount;
|
||||
}
|
||||
|
||||
Check_Fpu();
|
||||
}
|
||||
|
||||
MissileLauncher::~MissileLauncher()
|
||||
{
|
||||
}
|
||||
|
||||
Logical
|
||||
MissileLauncher::TestInstance() const
|
||||
{
|
||||
return IsDerivedFrom(ClassDerivations);
|
||||
}
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
// Damage / death.
|
||||
//#############################################################################
|
||||
//
|
||||
void
|
||||
MissileLauncher::TakeDamage(Damage &damage)
|
||||
{
|
||||
Check(this);
|
||||
ProjectileWeapon::TakeDamage(damage);
|
||||
}
|
||||
|
||||
void
|
||||
MissileLauncher::DeathReset(Logical full_reset)
|
||||
{
|
||||
Check(this);
|
||||
//
|
||||
// The weapon respawn restore (the bin restocks in its own DeathReset;
|
||||
// the fresh Loading cycle releases the NoAmmo roach-motel).
|
||||
//
|
||||
MechWeapon::DeathReset(full_reset);
|
||||
}
|
||||
|
||||
//
|
||||
// The missile-salvo discharge. Not yet reconstructed.
|
||||
//
|
||||
void
|
||||
MissileLauncher::FireWeapon()
|
||||
{
|
||||
Check(this);
|
||||
|
||||
//
|
||||
// PARTIAL (binary @004bcc60): the authentic body launches missileCount
|
||||
// Missile entities toward the owner's target (each carrying the salvo-split
|
||||
// damageData) and dumps the firing heat. The Missile entity spawn needs
|
||||
// the entity-spawn + targeting waves; the view/target gate, ammo pull and
|
||||
// recoil all live in the CALLER (ProjectileWeaponSimulation's Loaded case).
|
||||
//
|
||||
// Ballistic heatCostToFire is stored 1e7-unit-NATIVE in the stream (SRM6
|
||||
// reads 5.06e7 = +641K on its own sink -- the same design magnitude as the
|
||||
// PPC's +632K); dump it raw, under the heat-model experience gate (novice /
|
||||
// standard fire generates no heat -- authentic). (The EMITTER's authored
|
||||
// value is small and its 1e7 comes from the energy algebra.)
|
||||
if (HeatModelActive())
|
||||
{
|
||||
AddPendingHeat(heatCostToFire);
|
||||
}
|
||||
|
||||
//
|
||||
// The salvo launch (binary @004bcc60): ONE cluster Missile entity per
|
||||
// trigger, spawned through the Mover make machinery -- the MakeMessage's
|
||||
// resourceID is this weapon's projectile GameModel (explosionResourceID
|
||||
// @0x3e4), so the Mover base streams the authored mass/drag. The round
|
||||
// carries the salvo-split cluster damage record (burstCount =
|
||||
// missileCount rides into the gyro bounce + splash) and homes via its
|
||||
// Seeker; the proximity fuse delivers the damage ONCE (the arcade
|
||||
// economy, BT411 task #62). Muzzle = the mech origin STAGED (the
|
||||
// authored MuzzleVelocity arc through the MOUNT segment frame is the
|
||||
// muzzle wave). If the projectile model resource is absent the launch
|
||||
// falls back to the 5.3.16 instant-hit delivery.
|
||||
//
|
||||
if (targetWithinRange && owner != NULL
|
||||
&& owner->GetTargetEntity() != NULL
|
||||
&& getenv("BT_MISSILE_INSTANT") != NULL)
|
||||
{
|
||||
//
|
||||
// DEV opt-out (BT_MISSILE_INSTANT=1): the 5.3.18a instant-hit
|
||||
// cluster message, kept for A/B damage-economy checks. The flying
|
||||
// Missile below is the DEFAULT since 5.3.19 (teardown closed: the
|
||||
// Projectile ctor NULLs the inherited zone slots).
|
||||
//
|
||||
SendDamage(owner->GetTargetEntity(), damageData);
|
||||
}
|
||||
else if (targetWithinRange && owner != NULL
|
||||
&& owner->GetTargetEntity() != NULL)
|
||||
{
|
||||
//
|
||||
// The MakeMessage's resourceID must be a resource FAMILY id -- the
|
||||
// Mover base ctor runs its own SearchList(resourceID, GameModel),
|
||||
// and SearchList CRASHES (engine @0x414938) when the id isn't in
|
||||
// the top-level directory (member resources resolve only through
|
||||
// their family head). The streamed explosionModelFile is a small
|
||||
// INDEX (SRM6 reads 17) into a model list -- decoding it into the
|
||||
// real projectile family is the projectile-model brick. Until
|
||||
// then the round spawns on the OWNER's family (its GameModel
|
||||
// member provably streams every boot -- the mech model params);
|
||||
// our staged flight integration reads none of the Mover mass/drag.
|
||||
//
|
||||
{
|
||||
//
|
||||
// DEV one-shot (BT_MODEL_DUMP=1): raw-dump the mech's ModelList
|
||||
// stream -- the decode bench for the explosionModelFile INDEX
|
||||
// (SRM6 reads 17). The owner's make resourceID IS the
|
||||
// ModelList record (CreatePlayerVehicle passes
|
||||
// mech_res->resourceID).
|
||||
//
|
||||
{
|
||||
static int dumped = 0;
|
||||
if (!dumped && getenv("BT_MODEL_DUMP"))
|
||||
{
|
||||
dumped = 1;
|
||||
ResourceDescription *ml =
|
||||
application->GetResourceFile()->
|
||||
FindResourceDescription(owner->GetResourceID());
|
||||
if (ml != NULL)
|
||||
{
|
||||
ml->Lock();
|
||||
unsigned char *raw =
|
||||
(unsigned char *)ml->resourceAddress;
|
||||
int size = ml->resourceSize;
|
||||
DEBUG_STREAM << "[mdl] list res id=" << ml->resourceID
|
||||
<< " type=" << ml->resourceType
|
||||
<< " size=" << size
|
||||
<< " count=" << *(int *)raw << endl;
|
||||
int limit = (size < 640) ? size : 640;
|
||||
for (int bb = 0; bb < limit; bb += 16)
|
||||
{
|
||||
DEBUG_STREAM << "[mdl] +" << bb << ":";
|
||||
int cc;
|
||||
for (cc = bb; cc < bb + 16 && cc < limit; ++cc)
|
||||
{
|
||||
DEBUG_STREAM << " " << (unsigned)raw[cc];
|
||||
}
|
||||
DEBUG_STREAM << " ";
|
||||
for (cc = bb; cc < bb + 16 && cc < limit; ++cc)
|
||||
{
|
||||
char ch = (raw[cc] >= 32 && raw[cc] < 127)
|
||||
? (char)raw[cc] : '.';
|
||||
DEBUG_STREAM << ch;
|
||||
}
|
||||
DEBUG_STREAM << endl;
|
||||
}
|
||||
//
|
||||
// Identify the members + the streamed
|
||||
// explosionModelFile value by name/type.
|
||||
//
|
||||
int nn = *(int *)raw;
|
||||
if (nn > 0 && nn < 40)
|
||||
{
|
||||
int *ids = (int *)(raw + 4);
|
||||
for (int ee = 0; ee < nn; ++ee)
|
||||
{
|
||||
ResourceDescription *mm =
|
||||
application->GetResourceFile()->
|
||||
FindResourceDescription(ids[ee]);
|
||||
DEBUG_STREAM << "[mdl] member[" << ee
|
||||
<< "] id=" << ids[ee];
|
||||
if (mm != NULL)
|
||||
{
|
||||
DEBUG_STREAM << " type=" << mm->resourceType
|
||||
<< " name=" << mm->resourceName;
|
||||
}
|
||||
DEBUG_STREAM << endl;
|
||||
}
|
||||
}
|
||||
{
|
||||
ResourceDescription *ex =
|
||||
application->GetResourceFile()->
|
||||
FindResourceDescription(
|
||||
explosionResourceID);
|
||||
DEBUG_STREAM << "[mdl] explosionModelFile="
|
||||
<< explosionResourceID;
|
||||
if (ex != NULL)
|
||||
{
|
||||
DEBUG_STREAM << " -> type=" << ex->resourceType
|
||||
<< " name=" << ex->resourceName
|
||||
<< " size=" << ex->resourceSize;
|
||||
}
|
||||
else
|
||||
{
|
||||
DEBUG_STREAM << " -> NOT IN DIRECTORY";
|
||||
}
|
||||
DEBUG_STREAM << endl;
|
||||
|
||||
//
|
||||
// Walk the explosion family's OWN members.
|
||||
//
|
||||
if (ex != NULL && ex->resourceType ==
|
||||
ResourceDescription::ModelListResourceType)
|
||||
{
|
||||
ex->Lock();
|
||||
int *xr = (int *)ex->resourceAddress;
|
||||
int xn = xr[0];
|
||||
DEBUG_STREAM << "[mdl] '" << ex->resourceName
|
||||
<< "' members=" << xn << endl;
|
||||
for (int xx = 0; xx < xn && xx < 16; ++xx)
|
||||
{
|
||||
ResourceDescription *xm =
|
||||
application->GetResourceFile()->
|
||||
FindResourceDescription(xr[1 + xx]);
|
||||
DEBUG_STREAM << "[mdl] [" << xx
|
||||
<< "] id=" << xr[1 + xx];
|
||||
if (xm != NULL)
|
||||
{
|
||||
DEBUG_STREAM
|
||||
<< " type=" << xm->resourceType
|
||||
<< " name=" << xm->resourceName
|
||||
<< " size=" << xm->resourceSize;
|
||||
}
|
||||
DEBUG_STREAM << endl;
|
||||
|
||||
//
|
||||
// The GameModel member: dump as floats
|
||||
// (the missile model record -- lifetime
|
||||
// @+0x44, thrust @+0x48, mode @+0x50).
|
||||
//
|
||||
if (xm != NULL && xm->resourceType ==
|
||||
ResourceDescription::GameModelResourceType)
|
||||
{
|
||||
xm->Lock();
|
||||
float *fv = (float *)xm->resourceAddress;
|
||||
int nf = xm->resourceSize / 4;
|
||||
for (int ff = 0; ff < nf; ++ff)
|
||||
{
|
||||
DEBUG_STREAM << "[mdl] +"
|
||||
<< (ff * 4)
|
||||
<< " f=" << fv[ff]
|
||||
<< " i=" << ((int *)fv)[ff]
|
||||
<< endl;
|
||||
}
|
||||
xm->Unlock();
|
||||
}
|
||||
}
|
||||
ex->Unlock();
|
||||
}
|
||||
}
|
||||
DEBUG_STREAM << flush;
|
||||
ml->Unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Resolve the round's resource FAMILY. The streamed
|
||||
// explosionModelFile IS a family id after all (SRM6 = 17 =
|
||||
// 'mslhit': VideoModel + AudioStreamList + an 8-byte GameModel
|
||||
// {int 498, maxTimeOfFlight 5.0}). Guard: only use it when the
|
||||
// family really carries a GameModel MEMBER -- the Mover base
|
||||
// ctor SearchLists for one and CRASHES on a missing member
|
||||
// (the walk runs off the family list). Fallback = the owner's
|
||||
// family (GameModel provably present).
|
||||
//
|
||||
ResourceDescription::ResourceID round_family =
|
||||
owner->GetResourceID();
|
||||
if (explosionResourceID != ResourceDescription::NullResourceID)
|
||||
{
|
||||
ResourceDescription *family =
|
||||
application->GetResourceFile()->
|
||||
FindResourceDescription(explosionResourceID);
|
||||
if (family != NULL
|
||||
&& family->resourceType
|
||||
== ResourceDescription::ModelListResourceType)
|
||||
{
|
||||
family->Lock();
|
||||
int *members = (int *)family->resourceAddress;
|
||||
int member_count = members[0];
|
||||
Logical family_complete = True;
|
||||
Logical has_game_model = False;
|
||||
for (int mi = 0; mi < member_count; ++mi)
|
||||
{
|
||||
ResourceDescription *member =
|
||||
application->GetResourceFile()->
|
||||
FindResourceDescription(members[1 + mi]);
|
||||
if (member == NULL)
|
||||
{
|
||||
//
|
||||
// A member id absent from the directory makes
|
||||
// the engine SearchList walk CRASH -- this
|
||||
// family is unusable for the Mover make.
|
||||
//
|
||||
family_complete = False;
|
||||
}
|
||||
else if (member->resourceType
|
||||
== ResourceDescription::GameModelResourceType)
|
||||
{
|
||||
has_game_model = True;
|
||||
}
|
||||
}
|
||||
if (family_complete && has_game_model)
|
||||
{
|
||||
round_family = explosionResourceID;
|
||||
//
|
||||
// Keep the family RESIDENT (no Unlock): dropping a
|
||||
// first-touch family back to lockCount 0 left the
|
||||
// engine's re-Lock inside the Mover ctor reading a
|
||||
// dead list (SearchList returned NULL on members we
|
||||
// just resolved). The binary's launcher holds its
|
||||
// projectile model locked for the mission anyway.
|
||||
//
|
||||
}
|
||||
else
|
||||
{
|
||||
family->Unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HostManager *host_manager = application->GetHostManager();
|
||||
Check(host_manager);
|
||||
|
||||
//
|
||||
// The round leaves FROM THE GUN: the mount segment's world
|
||||
// frame (GetMuzzlePoint @004b9948 -- follows the torso twist).
|
||||
// The rotation stays the mech's (the missile aims by velocity).
|
||||
//
|
||||
Origin muzzle = owner->localOrigin;
|
||||
GetMuzzlePoint(muzzle.linearPosition);
|
||||
//
|
||||
// NoCollisionVolumeFlag is LOAD-BEARING: collision volumes are
|
||||
// the Mover DEFAULT (IsCollisionVolume = the flag NOT set), and
|
||||
// the ctor then demands a BoxedSolidStream member -- which a
|
||||
// projectile family doesn't carry (SearchList NULL ->
|
||||
// Lock() page fault). The round's collision is the world
|
||||
// query / proximity fuse, not a boxed solid.
|
||||
//
|
||||
Mover::MakeMessage
|
||||
spawn_round(
|
||||
Entity::MakeMessageID,
|
||||
sizeof(Mover::MakeMessage),
|
||||
EntityID(host_manager->GetLocalHostID()),
|
||||
(Entity::ClassID)MissileClassID,
|
||||
EntityID::Null,
|
||||
round_family,
|
||||
Entity::DefaultFlags | Mover::NoCollisionVolumeFlag,
|
||||
muzzle,
|
||||
Motion::Identity,
|
||||
Motion::Identity
|
||||
);
|
||||
Missile *round = new Missile(&spawn_round);
|
||||
Register_Object(round);
|
||||
round->Launch(
|
||||
owner,
|
||||
owner->GetTargetEntity(),
|
||||
damageData,
|
||||
kMissileLaunchSpeed
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (getenv("BT_MECH_LOG"))
|
||||
{
|
||||
DEBUG_STREAM << "[fire] '" << GetName()
|
||||
<< "' FIRED (salvo of " << missileCount
|
||||
<< ", recharge=" << rechargeRate
|
||||
<< "s heat+=" << heatCostToFire << ")" << endl << flush;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Model-load-time construction. Not yet reconstructed.
|
||||
//
|
||||
Logical
|
||||
MissileLauncher::CreateStreamedSubsystem(
|
||||
ResourceFile *,
|
||||
NotationFile *,
|
||||
const char *,
|
||||
const char *,
|
||||
SubsystemResource *,
|
||||
NotationFile *,
|
||||
const ResourceDirectories *,
|
||||
int
|
||||
)
|
||||
{
|
||||
Fail("MissileLauncher::CreateStreamedSubsystem -- mislanch.cpp not yet reconstructed");
|
||||
return False;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,407 +1,424 @@
|
||||
//===========================================================================//
|
||||
// File: powersub.hpp //
|
||||
// Project: BattleTech //
|
||||
// Contents: Implementation details for powered subsystems //
|
||||
//---------------------------------------------------------------------------//
|
||||
// Date Who Modification //
|
||||
// -------- --- ---------------------------------------------------------- //
|
||||
// //
|
||||
//---------------------------------------------------------------------------//
|
||||
// Copyright (C) 1995, Virtual World Entertainment, Inc. //
|
||||
// All Rights reserved worldwide //
|
||||
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL //
|
||||
//===========================================================================//
|
||||
|
||||
#if !defined(POWERSUB_HPP)
|
||||
# define POWERSUB_HPP
|
||||
|
||||
# if !defined(HEAT_HPP)
|
||||
# include <heat.hpp>
|
||||
# endif
|
||||
|
||||
//##################### Forward Class Declarations #######################
|
||||
class Mech;
|
||||
|
||||
//###########################################################################
|
||||
//################### PoweredSubsystem Model Resource ###################
|
||||
//###########################################################################
|
||||
|
||||
struct PoweredSubsystem__SubsystemResource:
|
||||
public HeatSink::SubsystemResource
|
||||
{
|
||||
int voltageSourceIndex;
|
||||
Scalar thermalResistivityCoefficient;
|
||||
int auxScreenNumber;
|
||||
int auxScreenPlacement;
|
||||
char auxScreenLabel[64];
|
||||
char engScreenLabel[64];
|
||||
Scalar startTime;
|
||||
};
|
||||
|
||||
//###########################################################################
|
||||
//######################### PoweredSubsystem ############################
|
||||
//###########################################################################
|
||||
|
||||
class PoweredSubsystem:
|
||||
public HeatSink
|
||||
{
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Shared Data Support
|
||||
//
|
||||
public:
|
||||
static Derivation ClassDerivations;
|
||||
static SharedData DefaultData;
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Test Class Support
|
||||
//
|
||||
public:
|
||||
static Logical
|
||||
TestClass(Mech &);
|
||||
Logical
|
||||
TestInstance() const;
|
||||
void
|
||||
ResetToInitialState(Logical powered);
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Electrical state machine (carried in electricalStateAlarm, 5 levels) and
|
||||
// the connection-mode indicator (modeAlarm, 3 levels).
|
||||
//
|
||||
public:
|
||||
enum ElectricalState {
|
||||
Starting = 0, // powering up; startTimer counts toward startTime
|
||||
NoVoltage = 1, // voltage source missing / unresolvable
|
||||
Shorted = 2, // source generator shorted
|
||||
GeneratorOff = 3, // source generator off / not ready
|
||||
Ready = 4 // powered and operating
|
||||
};
|
||||
|
||||
enum ConnectMode {
|
||||
ManualConnect = 0,
|
||||
Connected = 1,
|
||||
AutoConnect = 2
|
||||
};
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Power support
|
||||
//
|
||||
public:
|
||||
//
|
||||
// The cockpit generator-select buttons (binary handler table
|
||||
// @0x50F4EC, ids 4-8, chained onto the HeatSink set so id 3
|
||||
// ToggleCooling stays reachable through every powered subsystem).
|
||||
//
|
||||
enum {
|
||||
SelectGeneratorAMessageID = HeatSink::NextMessageID, // 4
|
||||
SelectGeneratorBMessageID, // 5
|
||||
SelectGeneratorCMessageID, // 6
|
||||
SelectGeneratorDMessageID, // 7
|
||||
ToggleGeneratorModeMessageID, // 8
|
||||
NextMessageID // 9
|
||||
};
|
||||
|
||||
static const HandlerEntry MessageHandlerEntries[];
|
||||
static MessageHandlerSet MessageHandlers;
|
||||
|
||||
void
|
||||
SelectGeneratorAMessageHandler(ReceiverDataMessageOf<int> *message);
|
||||
void
|
||||
SelectGeneratorBMessageHandler(ReceiverDataMessageOf<int> *message);
|
||||
void
|
||||
SelectGeneratorCMessageHandler(ReceiverDataMessageOf<int> *message);
|
||||
void
|
||||
SelectGeneratorDMessageHandler(ReceiverDataMessageOf<int> *message);
|
||||
void
|
||||
ToggleGeneratorModeMessageHandler(ReceiverDataMessageOf<int> *message);
|
||||
|
||||
//
|
||||
// The manual generator re-tap (binary @004b0b18/@004b0dd8 family):
|
||||
// find generator N in the owner's roster, release the current tap,
|
||||
// tap the new source, drop the connect mode to Connected.
|
||||
//
|
||||
void
|
||||
SelectGenerator(int generator_number);
|
||||
Subsystem*
|
||||
FindGeneratorByNumber(int generator_number);
|
||||
void
|
||||
DetachFromVoltageSource();
|
||||
|
||||
Subsystem*
|
||||
ResolveVoltageSource() { return voltageSource.Resolve(); }
|
||||
int
|
||||
AttachToVoltageSource(Subsystem *source);
|
||||
unsigned
|
||||
GetVoltageState() { Check(this); return electricalStateAlarm.GetLevel(); }
|
||||
|
||||
//
|
||||
// The charge time-scale (binary @004b0d50) -- the heat/firepower
|
||||
// feedback: the base voltageScale (the ctor-calibrated exponential
|
||||
// charge constant) stretched by the powering generator's temperature
|
||||
// rise above its start point.
|
||||
//
|
||||
Scalar
|
||||
ChargeTimeScale();
|
||||
|
||||
//
|
||||
// Short event (binary @004b11bc): drive the powering generator into
|
||||
// the Shorted state and zero its output -- the electrical FSM then
|
||||
// runs its short-recovery cycle. Gated on the not-novice experience
|
||||
// predicate (both hops share the mech's player). Called by the
|
||||
// special-damage path (a type-4 Energy hit on a zone with attached
|
||||
// generator criticals).
|
||||
//
|
||||
void
|
||||
ForceShortRecovery();
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Per-frame simulation (heat step + the electrical state machine).
|
||||
//
|
||||
public:
|
||||
typedef void
|
||||
(PoweredSubsystem::*Performance)(Scalar time_slice);
|
||||
void
|
||||
SetPerformance(Performance performance)
|
||||
{
|
||||
Check(this);
|
||||
activePerformance = (Simulation::Performance)performance;
|
||||
}
|
||||
|
||||
void
|
||||
PoweredSubsystemSimulation(Scalar time_slice);
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Construction and Destruction
|
||||
//
|
||||
public:
|
||||
typedef PoweredSubsystem__SubsystemResource SubsystemResource;
|
||||
|
||||
PoweredSubsystem(
|
||||
Mech *owner,
|
||||
int subsystem_ID,
|
||||
SubsystemResource *subsystem_resource,
|
||||
SharedData &shared_data = DefaultData
|
||||
);
|
||||
|
||||
~PoweredSubsystem();
|
||||
|
||||
static int
|
||||
CreateStreamedSubsystem(
|
||||
NotationFile *model_file,
|
||||
const char *model_name,
|
||||
const char *subsystem_name,
|
||||
SubsystemResource *subsystem_resource,
|
||||
NotationFile *subsystem_file,
|
||||
const ResourceDirectories *directories,
|
||||
int passes = 1
|
||||
);
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Local Data
|
||||
//
|
||||
protected:
|
||||
Scalar inputVoltage;
|
||||
Scalar outputVoltage;
|
||||
Scalar ratedVoltage;
|
||||
SubsystemConnection voltageSource;
|
||||
AlarmIndicator electricalStateAlarm;
|
||||
AlarmIndicator modeAlarm;
|
||||
Scalar thermalResistivityCoefficient;
|
||||
Scalar startTime;
|
||||
Scalar startTimer;
|
||||
Scalar voltageScale;
|
||||
};
|
||||
|
||||
//###########################################################################
|
||||
//################### PowerWatcher Model Resource ######################
|
||||
//###########################################################################
|
||||
|
||||
struct PowerWatcher__SubsystemResource:
|
||||
public HeatWatcher::SubsystemResource
|
||||
{
|
||||
Scalar minVoltagePercent;
|
||||
};
|
||||
|
||||
//###########################################################################
|
||||
//############################ PowerWatcher ############################
|
||||
//###########################################################################
|
||||
//
|
||||
// A HeatWatcher that also watches its subject's supply voltage. Base of the
|
||||
// Torso / HUD / Gyroscope cockpit subsystems.
|
||||
//
|
||||
class PowerWatcher:
|
||||
public HeatWatcher
|
||||
{
|
||||
public:
|
||||
static Derivation ClassDerivations;
|
||||
static SharedData DefaultData;
|
||||
|
||||
static Logical
|
||||
TestClass(Mech &);
|
||||
Logical
|
||||
TestInstance() const;
|
||||
void
|
||||
ResetToInitialState(Logical powered);
|
||||
void
|
||||
Simulation(Scalar time_slice);
|
||||
|
||||
public:
|
||||
typedef PowerWatcher__SubsystemResource SubsystemResource;
|
||||
|
||||
PowerWatcher(
|
||||
Mech *owner,
|
||||
int subsystem_ID,
|
||||
SubsystemResource *subsystem_resource,
|
||||
SharedData &shared_data = DefaultData
|
||||
);
|
||||
~PowerWatcher();
|
||||
|
||||
protected:
|
||||
Scalar minVoltage;
|
||||
AlarmIndicator watchdogAlarm;
|
||||
};
|
||||
|
||||
//###########################################################################
|
||||
//#################### Generator Model Resource ########################
|
||||
//###########################################################################
|
||||
|
||||
struct Generator__SubsystemResource:
|
||||
public HeatSink::SubsystemResource
|
||||
{
|
||||
Scalar ratedVoltage;
|
||||
int maxTapCount;
|
||||
Scalar startTime;
|
||||
Scalar shortRecoveryTime;
|
||||
};
|
||||
|
||||
//###########################################################################
|
||||
//############################## Generator #############################
|
||||
//###########################################################################
|
||||
//
|
||||
// The voltage SOURCE a PoweredSubsystem attaches to (currentTapCount tracks
|
||||
// attached loads). A HeatSink that produces power.
|
||||
//
|
||||
class Generator:
|
||||
public HeatSink
|
||||
{
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Shared Data Support
|
||||
//
|
||||
public:
|
||||
static Derivation ClassDerivations;
|
||||
static SharedData DefaultData;
|
||||
|
||||
enum {
|
||||
GeneratorOff = 0,
|
||||
GeneratorStarting,
|
||||
GeneratorReady,
|
||||
GeneratorShorted,
|
||||
GeneratorFailed,
|
||||
GeneratorStateCount
|
||||
};
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Test / reset
|
||||
//
|
||||
public:
|
||||
static Logical
|
||||
TestClass(Mech &);
|
||||
Logical
|
||||
TestInstance() const;
|
||||
void
|
||||
ResetToInitialState();
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Power interface
|
||||
//
|
||||
public:
|
||||
Scalar MeasuredVoltage() { Check(this); return outputVoltage; }
|
||||
Scalar RatedVoltageOf() { Check(this); return ratedVoltage; }
|
||||
int GetGeneratorNumber(){ Check(this); return generatorNumber; }
|
||||
unsigned
|
||||
GeneratorStateOf() { Check(this); return stateAlarm.GetLevel(); }
|
||||
|
||||
//
|
||||
// The short event landing ON this generator (the tail of binary
|
||||
// @004b11bc): Shorted state + output killed; GeneratorSimulation
|
||||
// runs the short-recovery timer back to Ready. Callers gate on the
|
||||
// novice experience predicate.
|
||||
//
|
||||
void
|
||||
ForceShort()
|
||||
{
|
||||
Check(this);
|
||||
stateAlarm.SetLevel(GeneratorShorted);
|
||||
outputVoltage = 0.0f;
|
||||
}
|
||||
|
||||
//
|
||||
// Tap the generator for one load (a PoweredSubsystem attaching): -1 when
|
||||
// every tap is taken, else 0.
|
||||
//
|
||||
int
|
||||
TapVoltageSource()
|
||||
{
|
||||
Check(this);
|
||||
if (currentTapCount >= maxTapCount)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
++currentTapCount;
|
||||
return 0;
|
||||
}
|
||||
void
|
||||
UntapVoltageSource()
|
||||
{
|
||||
Check(this);
|
||||
if (currentTapCount > 0)
|
||||
{
|
||||
--currentTapCount;
|
||||
}
|
||||
}
|
||||
|
||||
typedef void
|
||||
(Generator::*Performance)(Scalar time_slice);
|
||||
void
|
||||
SetPerformance(Performance performance)
|
||||
{
|
||||
Check(this);
|
||||
activePerformance = (Simulation::Performance)performance;
|
||||
}
|
||||
|
||||
void
|
||||
GeneratorSimulation(Scalar time_slice);
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Construction and Destruction
|
||||
//
|
||||
public:
|
||||
typedef Generator__SubsystemResource SubsystemResource;
|
||||
|
||||
Generator(
|
||||
Mech *owner,
|
||||
int subsystem_ID,
|
||||
SubsystemResource *subsystem_resource,
|
||||
SharedData &shared_data = DefaultData
|
||||
);
|
||||
|
||||
~Generator();
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Local Data
|
||||
//
|
||||
public:
|
||||
Scalar percentVoltageAvailable;
|
||||
int generatorOn;
|
||||
Scalar ratedVoltage;
|
||||
Scalar outputVoltage;
|
||||
int generatorNumber;
|
||||
int maxTapCount;
|
||||
int currentTapCount;
|
||||
Scalar startTime;
|
||||
Scalar startTimer;
|
||||
Scalar shortRecoveryTime;
|
||||
Scalar shortTimer;
|
||||
AlarmIndicator stateAlarm;
|
||||
};
|
||||
|
||||
#endif
|
||||
//===========================================================================//
|
||||
// File: powersub.hpp //
|
||||
// Project: BattleTech //
|
||||
// Contents: Implementation details for powered subsystems //
|
||||
//---------------------------------------------------------------------------//
|
||||
// Date Who Modification //
|
||||
// -------- --- ---------------------------------------------------------- //
|
||||
// //
|
||||
//---------------------------------------------------------------------------//
|
||||
// Copyright (C) 1995, Virtual World Entertainment, Inc. //
|
||||
// All Rights reserved worldwide //
|
||||
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL //
|
||||
//===========================================================================//
|
||||
|
||||
#if !defined(POWERSUB_HPP)
|
||||
# define POWERSUB_HPP
|
||||
|
||||
# if !defined(HEAT_HPP)
|
||||
# include <heat.hpp>
|
||||
# endif
|
||||
|
||||
//##################### Forward Class Declarations #######################
|
||||
class Mech;
|
||||
|
||||
//###########################################################################
|
||||
//################### PoweredSubsystem Model Resource ###################
|
||||
//###########################################################################
|
||||
|
||||
struct PoweredSubsystem__SubsystemResource:
|
||||
public HeatSink::SubsystemResource
|
||||
{
|
||||
int voltageSourceIndex;
|
||||
Scalar thermalResistivityCoefficient;
|
||||
int auxScreenNumber;
|
||||
int auxScreenPlacement;
|
||||
char auxScreenLabel[64];
|
||||
char engScreenLabel[64];
|
||||
Scalar startTime;
|
||||
};
|
||||
|
||||
//###########################################################################
|
||||
//######################### PoweredSubsystem ############################
|
||||
//###########################################################################
|
||||
|
||||
class PoweredSubsystem:
|
||||
public HeatSink
|
||||
{
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Shared Data Support
|
||||
//
|
||||
public:
|
||||
static Derivation ClassDerivations;
|
||||
static SharedData DefaultData;
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Test Class Support
|
||||
//
|
||||
public:
|
||||
static Logical
|
||||
TestClass(Mech &);
|
||||
Logical
|
||||
TestInstance() const;
|
||||
void
|
||||
ResetToInitialState(Logical powered);
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Electrical state machine (carried in electricalStateAlarm, 5 levels) and
|
||||
// the connection-mode indicator (modeAlarm, 3 levels).
|
||||
//
|
||||
public:
|
||||
enum ElectricalState {
|
||||
Starting = 0, // powering up; startTimer counts toward startTime
|
||||
NoVoltage = 1, // voltage source missing / unresolvable
|
||||
Shorted = 2, // source generator shorted
|
||||
GeneratorOff = 3, // source generator off / not ready
|
||||
Ready = 4 // powered and operating
|
||||
};
|
||||
|
||||
enum ConnectMode {
|
||||
ManualConnect = 0,
|
||||
Connected = 1,
|
||||
AutoConnect = 2
|
||||
};
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Power support
|
||||
//
|
||||
public:
|
||||
//
|
||||
// The cockpit generator-select buttons (binary handler table
|
||||
// @0x50F4EC, ids 4-8, chained onto the HeatSink set so id 3
|
||||
// ToggleCooling stays reachable through every powered subsystem).
|
||||
//
|
||||
enum {
|
||||
SelectGeneratorAMessageID = HeatSink::NextMessageID, // 4
|
||||
SelectGeneratorBMessageID, // 5
|
||||
SelectGeneratorCMessageID, // 6
|
||||
SelectGeneratorDMessageID, // 7
|
||||
ToggleGeneratorModeMessageID, // 8
|
||||
NextMessageID // 9
|
||||
};
|
||||
|
||||
static const HandlerEntry MessageHandlerEntries[];
|
||||
static MessageHandlerSet MessageHandlers;
|
||||
|
||||
void
|
||||
SelectGeneratorAMessageHandler(ReceiverDataMessageOf<int> *message);
|
||||
void
|
||||
SelectGeneratorBMessageHandler(ReceiverDataMessageOf<int> *message);
|
||||
void
|
||||
SelectGeneratorCMessageHandler(ReceiverDataMessageOf<int> *message);
|
||||
void
|
||||
SelectGeneratorDMessageHandler(ReceiverDataMessageOf<int> *message);
|
||||
void
|
||||
ToggleGeneratorModeMessageHandler(ReceiverDataMessageOf<int> *message);
|
||||
|
||||
//
|
||||
// The manual generator re-tap (binary @004b0b18/@004b0dd8 family):
|
||||
// find generator N in the owner's roster, release the current tap,
|
||||
// tap the new source, drop the connect mode to Connected.
|
||||
//
|
||||
void
|
||||
SelectGenerator(int generator_number);
|
||||
Subsystem*
|
||||
FindGeneratorByNumber(int generator_number);
|
||||
void
|
||||
DetachFromVoltageSource();
|
||||
|
||||
//
|
||||
// Respawn restore: thermal + electrical re-init (the FSM restarts
|
||||
// from GeneratorOff/Starting exactly like spawn).
|
||||
//
|
||||
virtual void
|
||||
DeathReset(Logical full_reset);
|
||||
|
||||
Subsystem*
|
||||
ResolveVoltageSource() { return voltageSource.Resolve(); }
|
||||
int
|
||||
AttachToVoltageSource(Subsystem *source);
|
||||
unsigned
|
||||
GetVoltageState() { Check(this); return electricalStateAlarm.GetLevel(); }
|
||||
|
||||
//
|
||||
// The charge time-scale (binary @004b0d50) -- the heat/firepower
|
||||
// feedback: the base voltageScale (the ctor-calibrated exponential
|
||||
// charge constant) stretched by the powering generator's temperature
|
||||
// rise above its start point.
|
||||
//
|
||||
Scalar
|
||||
ChargeTimeScale();
|
||||
|
||||
//
|
||||
// Short event (binary @004b11bc): drive the powering generator into
|
||||
// the Shorted state and zero its output -- the electrical FSM then
|
||||
// runs its short-recovery cycle. Gated on the not-novice experience
|
||||
// predicate (both hops share the mech's player). Called by the
|
||||
// special-damage path (a type-4 Energy hit on a zone with attached
|
||||
// generator criticals).
|
||||
//
|
||||
void
|
||||
ForceShortRecovery();
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Per-frame simulation (heat step + the electrical state machine).
|
||||
//
|
||||
public:
|
||||
typedef void
|
||||
(PoweredSubsystem::*Performance)(Scalar time_slice);
|
||||
void
|
||||
SetPerformance(Performance performance)
|
||||
{
|
||||
Check(this);
|
||||
activePerformance = (Simulation::Performance)performance;
|
||||
}
|
||||
|
||||
void
|
||||
PoweredSubsystemSimulation(Scalar time_slice);
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Construction and Destruction
|
||||
//
|
||||
public:
|
||||
typedef PoweredSubsystem__SubsystemResource SubsystemResource;
|
||||
|
||||
PoweredSubsystem(
|
||||
Mech *owner,
|
||||
int subsystem_ID,
|
||||
SubsystemResource *subsystem_resource,
|
||||
SharedData &shared_data = DefaultData
|
||||
);
|
||||
|
||||
~PoweredSubsystem();
|
||||
|
||||
static int
|
||||
CreateStreamedSubsystem(
|
||||
NotationFile *model_file,
|
||||
const char *model_name,
|
||||
const char *subsystem_name,
|
||||
SubsystemResource *subsystem_resource,
|
||||
NotationFile *subsystem_file,
|
||||
const ResourceDirectories *directories,
|
||||
int passes = 1
|
||||
);
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Local Data
|
||||
//
|
||||
protected:
|
||||
Scalar inputVoltage;
|
||||
Scalar outputVoltage;
|
||||
Scalar ratedVoltage;
|
||||
SubsystemConnection voltageSource;
|
||||
AlarmIndicator electricalStateAlarm;
|
||||
AlarmIndicator modeAlarm;
|
||||
Scalar thermalResistivityCoefficient;
|
||||
Scalar startTime;
|
||||
Scalar startTimer;
|
||||
Scalar voltageScale;
|
||||
};
|
||||
|
||||
//###########################################################################
|
||||
//################### PowerWatcher Model Resource ######################
|
||||
//###########################################################################
|
||||
|
||||
struct PowerWatcher__SubsystemResource:
|
||||
public HeatWatcher::SubsystemResource
|
||||
{
|
||||
Scalar minVoltagePercent;
|
||||
};
|
||||
|
||||
//###########################################################################
|
||||
//############################ PowerWatcher ############################
|
||||
//###########################################################################
|
||||
//
|
||||
// A HeatWatcher that also watches its subject's supply voltage. Base of the
|
||||
// Torso / HUD / Gyroscope cockpit subsystems.
|
||||
//
|
||||
class PowerWatcher:
|
||||
public HeatWatcher
|
||||
{
|
||||
public:
|
||||
static Derivation ClassDerivations;
|
||||
static SharedData DefaultData;
|
||||
|
||||
static Logical
|
||||
TestClass(Mech &);
|
||||
Logical
|
||||
TestInstance() const;
|
||||
void
|
||||
ResetToInitialState(Logical powered);
|
||||
//
|
||||
// Respawn restore: base heal + the watchdog re-baseline (the
|
||||
// HeatWatcher::DeathReset it would otherwise inherit binds the
|
||||
// NON-virtual ResetToInitialState statically and skips this one).
|
||||
//
|
||||
virtual void
|
||||
DeathReset(Logical full_reset);
|
||||
void
|
||||
Simulation(Scalar time_slice);
|
||||
|
||||
public:
|
||||
typedef PowerWatcher__SubsystemResource SubsystemResource;
|
||||
|
||||
PowerWatcher(
|
||||
Mech *owner,
|
||||
int subsystem_ID,
|
||||
SubsystemResource *subsystem_resource,
|
||||
SharedData &shared_data = DefaultData
|
||||
);
|
||||
~PowerWatcher();
|
||||
|
||||
protected:
|
||||
Scalar minVoltage;
|
||||
AlarmIndicator watchdogAlarm;
|
||||
};
|
||||
|
||||
//###########################################################################
|
||||
//#################### Generator Model Resource ########################
|
||||
//###########################################################################
|
||||
|
||||
struct Generator__SubsystemResource:
|
||||
public HeatSink::SubsystemResource
|
||||
{
|
||||
Scalar ratedVoltage;
|
||||
int maxTapCount;
|
||||
Scalar startTime;
|
||||
Scalar shortRecoveryTime;
|
||||
};
|
||||
|
||||
//###########################################################################
|
||||
//############################## Generator #############################
|
||||
//###########################################################################
|
||||
//
|
||||
// The voltage SOURCE a PoweredSubsystem attaches to (currentTapCount tracks
|
||||
// attached loads). A HeatSink that produces power.
|
||||
//
|
||||
class Generator:
|
||||
public HeatSink
|
||||
{
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Shared Data Support
|
||||
//
|
||||
public:
|
||||
static Derivation ClassDerivations;
|
||||
static SharedData DefaultData;
|
||||
|
||||
enum {
|
||||
GeneratorOff = 0,
|
||||
GeneratorStarting,
|
||||
GeneratorReady,
|
||||
GeneratorShorted,
|
||||
GeneratorFailed,
|
||||
GeneratorStateCount
|
||||
};
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Test / reset
|
||||
//
|
||||
public:
|
||||
static Logical
|
||||
TestClass(Mech &);
|
||||
Logical
|
||||
TestInstance() const;
|
||||
void
|
||||
ResetToInitialState();
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Power interface
|
||||
//
|
||||
public:
|
||||
Scalar MeasuredVoltage() { Check(this); return outputVoltage; }
|
||||
Scalar RatedVoltageOf() { Check(this); return ratedVoltage; }
|
||||
int GetGeneratorNumber(){ Check(this); return generatorNumber; }
|
||||
unsigned
|
||||
GeneratorStateOf() { Check(this); return stateAlarm.GetLevel(); }
|
||||
|
||||
//
|
||||
// The short event landing ON this generator (the tail of binary
|
||||
// @004b11bc): Shorted state + output killed; GeneratorSimulation
|
||||
// runs the short-recovery timer back to Ready. Callers gate on the
|
||||
// novice experience predicate.
|
||||
//
|
||||
void
|
||||
ForceShort()
|
||||
{
|
||||
Check(this);
|
||||
stateAlarm.SetLevel(GeneratorShorted);
|
||||
outputVoltage = 0.0f;
|
||||
}
|
||||
|
||||
//
|
||||
// Tap the generator for one load (a PoweredSubsystem attaching): -1 when
|
||||
// every tap is taken, else 0.
|
||||
//
|
||||
int
|
||||
TapVoltageSource()
|
||||
{
|
||||
Check(this);
|
||||
if (currentTapCount >= maxTapCount)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
++currentTapCount;
|
||||
return 0;
|
||||
}
|
||||
virtual void
|
||||
DeathReset(Logical full_reset);
|
||||
|
||||
void
|
||||
UntapVoltageSource()
|
||||
{
|
||||
Check(this);
|
||||
if (currentTapCount > 0)
|
||||
{
|
||||
--currentTapCount;
|
||||
}
|
||||
}
|
||||
|
||||
typedef void
|
||||
(Generator::*Performance)(Scalar time_slice);
|
||||
void
|
||||
SetPerformance(Performance performance)
|
||||
{
|
||||
Check(this);
|
||||
activePerformance = (Simulation::Performance)performance;
|
||||
}
|
||||
|
||||
void
|
||||
GeneratorSimulation(Scalar time_slice);
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Construction and Destruction
|
||||
//
|
||||
public:
|
||||
typedef Generator__SubsystemResource SubsystemResource;
|
||||
|
||||
Generator(
|
||||
Mech *owner,
|
||||
int subsystem_ID,
|
||||
SubsystemResource *subsystem_resource,
|
||||
SharedData &shared_data = DefaultData
|
||||
);
|
||||
|
||||
~Generator();
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Local Data
|
||||
//
|
||||
public:
|
||||
Scalar percentVoltageAvailable;
|
||||
int generatorOn;
|
||||
Scalar ratedVoltage;
|
||||
Scalar outputVoltage;
|
||||
int generatorNumber;
|
||||
int maxTapCount;
|
||||
int currentTapCount;
|
||||
Scalar startTime;
|
||||
Scalar startTimer;
|
||||
Scalar shortRecoveryTime;
|
||||
Scalar shortTimer;
|
||||
AlarmIndicator stateAlarm;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,438 +1,439 @@
|
||||
//===========================================================================//
|
||||
// File: projweap.cpp //
|
||||
// Project: BattleTech Brick: Mech weapons //
|
||||
// Contents: ProjectileWeapon -- ballistic (ammo-fed) weapon base //
|
||||
//---------------------------------------------------------------------------//
|
||||
// Copyright (C) 1995, Virtual World Entertainment, Inc. //
|
||||
// All Rights reserved worldwide //
|
||||
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL //
|
||||
//===========================================================================//
|
||||
|
||||
#include <bt.hpp>
|
||||
#pragma hdrstop
|
||||
|
||||
#if !defined(PROJWEAP_HPP)
|
||||
# include <projweap.hpp>
|
||||
#endif
|
||||
|
||||
#if !defined(MECH_HPP)
|
||||
# include <mech.hpp>
|
||||
#endif
|
||||
|
||||
#if !defined(AMMOBIN_HPP)
|
||||
# include <ammobin.hpp>
|
||||
#endif
|
||||
|
||||
#if !defined(RANDOM_HPP)
|
||||
# include <random.hpp>
|
||||
#endif
|
||||
|
||||
#if !defined(BTPLAYER_HPP)
|
||||
# include <btplayer.hpp>
|
||||
#endif
|
||||
|
||||
Derivation
|
||||
ProjectileWeapon::ClassDerivations(
|
||||
MechWeapon::ClassDerivations,
|
||||
"ProjectileWeapon"
|
||||
);
|
||||
|
||||
ProjectileWeapon::SharedData
|
||||
ProjectileWeapon::DefaultData(
|
||||
ProjectileWeapon::ClassDerivations,
|
||||
MechWeapon::MessageHandlers,
|
||||
MechWeapon::AttributeIndex,
|
||||
Subsystem::StateCount
|
||||
);
|
||||
|
||||
ProjectileWeapon::ProjectileWeapon(
|
||||
Mech *owner,
|
||||
int subsystem_ID,
|
||||
SubsystemResource *subsystem_resource,
|
||||
SharedData &shared_data
|
||||
):
|
||||
MechWeapon(owner, subsystem_ID, subsystem_resource, shared_data),
|
||||
ammoBinLink()
|
||||
{
|
||||
Check(owner);
|
||||
Check_Pointer(subsystem_resource);
|
||||
|
||||
tracerInterval = subsystem_resource->tracerInterval;
|
||||
minTimeOfFlight = subsystem_resource->minTimeOfFlight;
|
||||
minJamChance = subsystem_resource->minJamChance;
|
||||
minVoltagePercentToFire = subsystem_resource->minVoltagePercentToFire;
|
||||
|
||||
tracerCounter = 0;
|
||||
ejectState = 0;
|
||||
totalTimeToEject = 0.0f;
|
||||
timeToEject = 0.0f;
|
||||
percentOfEject = 0.0f;
|
||||
tracerModelHandle = 0;
|
||||
leadPosition = Point3D(0.0f, 0.0f, 0.0f);
|
||||
launchVelocity = Vector3D(0.0f, 0.0f, 0.0f);
|
||||
tracerOrigin = Vector3D(0.0f, 0.0f, 0.0f);
|
||||
|
||||
//
|
||||
// Link the AmmoBin subsystem the resource references by roster index (the
|
||||
// binary ctor resolves it from the owner's subsystem table and connects the
|
||||
// embedded link @0x43C). Shipped content always links a bin; log when the
|
||||
// data doesn't resolve one.
|
||||
//
|
||||
{
|
||||
Subsystem *roster_bin = NULL;
|
||||
if (subsystem_resource->ammoBinIndex >= 0
|
||||
&& subsystem_resource->ammoBinIndex < owner->GetSubsystemCount())
|
||||
{
|
||||
roster_bin = owner->GetSubsystem(subsystem_resource->ammoBinIndex);
|
||||
}
|
||||
if (roster_bin != NULL)
|
||||
{
|
||||
ammoBinLink.Add(roster_bin);
|
||||
}
|
||||
if (getenv("BT_MECH_LOG"))
|
||||
{
|
||||
DEBUG_STREAM << "[proj] '" << GetName()
|
||||
<< "' ammoBinIndex=" << subsystem_resource->ammoBinIndex
|
||||
<< " -> ";
|
||||
if (roster_bin != NULL)
|
||||
{
|
||||
DEBUG_STREAM << roster_bin->GetName()
|
||||
<< " (rounds=" << ((AmmoBin *)roster_bin)->GetAmmoCount() << ")";
|
||||
}
|
||||
else
|
||||
{
|
||||
DEBUG_STREAM << "<no bin>";
|
||||
}
|
||||
DEBUG_STREAM << endl << flush;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Install the ballistic fire state machine (a replicant copy is driven by
|
||||
// console updates instead).
|
||||
//
|
||||
if (owner->GetInstance() != Entity::ReplicantInstance)
|
||||
{
|
||||
SetPerformance(&ProjectileWeapon::ProjectileWeaponSimulation);
|
||||
}
|
||||
|
||||
Check_Fpu();
|
||||
}
|
||||
|
||||
ProjectileWeapon::~ProjectileWeapon()
|
||||
{
|
||||
}
|
||||
|
||||
Logical
|
||||
ProjectileWeapon::TestClass(Mech &)
|
||||
{
|
||||
return True;
|
||||
}
|
||||
|
||||
Logical
|
||||
ProjectileWeapon::TestInstance() const
|
||||
{
|
||||
return IsDerivedFrom(ClassDerivations);
|
||||
}
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
// FireWeapon The ballistic discharge (PARTIAL -- binary @004bc104). The
|
||||
// authentic body is heat + projectile/tracer spawn ONLY: the view/target gate,
|
||||
// the ammo pull and the recoil set all live in the CALLER (the Loaded case of
|
||||
// ProjectileWeaponSimulation) -- early-returning any gate from here while the
|
||||
// caller cycles the alarm anyway is exactly the 1995 "denied shot fakes a full
|
||||
// firing cycle" defect class. The projectile spawn (Missile entities /
|
||||
// tracer) needs the entity-spawn + targeting waves; MissileLauncher overrides
|
||||
// this for the salvo launch.
|
||||
//#############################################################################
|
||||
//
|
||||
void
|
||||
ProjectileWeapon::FireWeapon()
|
||||
{
|
||||
Check(this);
|
||||
|
||||
// Ballistic heatCostToFire is stored 1e7-unit-NATIVE in the stream (SRM6
|
||||
// reads 5.06e7 = +641K on its own sink -- the same design magnitude as the
|
||||
// PPC's +632K); dump it raw, under the heat-model experience gate (novice /
|
||||
// standard fire generates no heat -- authentic). (The EMITTER's authored
|
||||
// value is small and its 1e7 comes from the energy algebra -- see
|
||||
// EMITTER.CPP.)
|
||||
if (HeatModelActive())
|
||||
{
|
||||
AddPendingHeat(heatCostToFire);
|
||||
}
|
||||
|
||||
if (getenv("BT_MECH_LOG"))
|
||||
{
|
||||
DEBUG_STREAM << "[fire] '" << GetName()
|
||||
<< "' FIRED (ballistic, recharge=" << rechargeRate
|
||||
<< "s heat+=" << heatCostToFire << ")"
|
||||
<< endl << flush;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
// LiveFireEnabled -- the "sim live" novice lockout: owner mech ->
|
||||
// Entity::playerLink -> BTPlayer::simLive, 0 only for NOVICE experience. A
|
||||
// NULL player link reads LIVE (the unlinked dev mech / target dummy).
|
||||
//#############################################################################
|
||||
//
|
||||
Logical
|
||||
ProjectileWeapon::LiveFireEnabled()
|
||||
{
|
||||
Check(this);
|
||||
|
||||
if (owner == NULL)
|
||||
{
|
||||
return True;
|
||||
}
|
||||
BTPlayer *player = Cast_Object(BTPlayer *, owner->GetPlayerLink());
|
||||
if (player == NULL)
|
||||
{
|
||||
return True;
|
||||
}
|
||||
return player->IsSimLive();
|
||||
}
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
// CheckForJam -- the heat-scaled jam roll (binary @4bbfcc), AUTHENTIC form:
|
||||
// novice never jams (LiveFireEnabled); a cold heat model never jams (heatLoad
|
||||
// stays 0 with the model off -- UpdateHeatLoad only runs under the experience
|
||||
// gate); otherwise
|
||||
// p = 0.41 * currentTemperature / failureTemperature,
|
||||
// clamped to [minJamChance, 1.0], rolled against the uniform random. Note the
|
||||
// floor: even a cool launcher at veteran+ carries the authored minimum jam
|
||||
// chance per shot (SRM6: 5%) -- authentic pod behavior. A jammed launcher
|
||||
// clears only on ResetToInitialState (mission reset).
|
||||
//#############################################################################
|
||||
//
|
||||
Logical
|
||||
ProjectileWeapon::CheckForJam()
|
||||
{
|
||||
Check(this);
|
||||
|
||||
if (!LiveFireEnabled())
|
||||
{
|
||||
return False;
|
||||
}
|
||||
if (heatLoad <= 0.0f)
|
||||
{
|
||||
return False;
|
||||
}
|
||||
|
||||
Scalar p = (0.41f * CurrentTemperatureOf()) / failureTemperature;
|
||||
if (minJamChance <= p)
|
||||
{
|
||||
if (p > 1.0f)
|
||||
{
|
||||
p = 1.0f;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
p = minJamChance;
|
||||
}
|
||||
|
||||
return (p > (Scalar)Random) ? True : False;
|
||||
}
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
// ProjectileWeaponSimulation The ballistic per-frame fire state machine
|
||||
// (binary @004bbd04, FULLY RECOVERED in the BT411 RE). The weapon state is
|
||||
// carried in the weapon alarm: 0/1/4/6 transient audio blips, 2 Loaded,
|
||||
// 3 Loading, 5 Jammed, 7 unavailable/NoAmmo (the roach-motel: nothing in the
|
||||
// machine ever leaves it -- only a mission reset does).
|
||||
//
|
||||
// PARTIAL: the leading PoweredSubsystemSimulation electrical step, the
|
||||
// destroyed / FailureHeat / mech-disabled hard gate (gate 1), the magazine
|
||||
// eject, the electrical-Ready recoil gate, the heat-scaled jam roll and the
|
||||
// HasActiveTarget half of the fire gate are deferred with the power / heat /
|
||||
// damage / targeting waves. What runs is authentic in shape: the single
|
||||
// trigger-edge sample, the dry-bin gate (gate 2), the Loaded ammo-pull ->
|
||||
// Firing -> Loading cycle with the denial blip, the Loading recoil bleed +
|
||||
// recharge dial, and the NoAmmo dry-fire blip.
|
||||
//
|
||||
// DEV hook BT_FORCE_FIRE=1: pulses the trigger whenever the weapon is Loaded
|
||||
// (same as the emitter hook) -- every armed weapon auto-fires at its authored
|
||||
// cadence until its bin runs dry.
|
||||
//#############################################################################
|
||||
//
|
||||
void
|
||||
ProjectileWeapon::ProjectileWeaponSimulation(Scalar time_slice)
|
||||
{
|
||||
Check(this);
|
||||
|
||||
//
|
||||
// The PoweredSubsystem step first (binary @4bbd12): the HeatSink thermal
|
||||
// absorb/conduct + the electrical state machine.
|
||||
//
|
||||
PoweredSubsystem::PoweredSubsystemSimulation(time_slice);
|
||||
|
||||
{
|
||||
static int forceFire = -1;
|
||||
if (forceFire < 0)
|
||||
{
|
||||
forceFire = (getenv("BT_FORCE_FIRE") != NULL) ? 1 : 0;
|
||||
}
|
||||
if (forceFire)
|
||||
{
|
||||
fireImpulse = (GetWeaponState() == LoadedState) ? 1.0f : 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// The trigger edge is sampled ONCE, before the gates.
|
||||
//
|
||||
Logical trigger = CheckFireEdge();
|
||||
|
||||
targetWithinRange = UpdateTargeting();
|
||||
|
||||
//
|
||||
// Gate 1 (@4bbd36): weapon DESTROYED or its own sink at FailureHeat -> pin
|
||||
// full recoil + the unavailable alarm. No return -- the frame continues
|
||||
// into the state machine (whose NoAmmo case re-asserts). (The owning-mech-
|
||||
// disabled half joins with the damage wave.)
|
||||
//
|
||||
if (GetSimulationState() == 1 || GetHeatState() == FailureHeat)
|
||||
{
|
||||
if (getenv("BT_MECH_LOG") && GetWeaponState() != NoAmmoState)
|
||||
{
|
||||
DEBUG_STREAM << "[fire] '" << GetName()
|
||||
<< "' -> UNAVAILABLE (gate1: heatState=" << GetHeatState()
|
||||
<< " T=" << CurrentTemperatureOf() << ")" << endl << flush;
|
||||
}
|
||||
recoil = rechargeRate;
|
||||
weaponAlarm.SetLevel(NoAmmoState);
|
||||
}
|
||||
|
||||
//
|
||||
// Gate 2: the linked AmmoBin. Empty (or missing) -> pin unavailable EVERY
|
||||
// frame while dry.
|
||||
//
|
||||
AmmoBin *bin = (AmmoBin *)ammoBinLink.Resolve();
|
||||
if (bin != NULL && bin->GetAmmoState() == AmmoBin::AmmoEmptyState)
|
||||
{
|
||||
if (getenv("BT_MECH_LOG") && GetWeaponState() != NoAmmoState)
|
||||
{
|
||||
DEBUG_STREAM << "[fire] '" << GetName()
|
||||
<< "' -> NoAmmo (bin dry)" << endl << flush;
|
||||
}
|
||||
weaponAlarm.SetLevel(NoAmmoState);
|
||||
}
|
||||
|
||||
switch (GetWeaponState())
|
||||
{
|
||||
case LoadedState:
|
||||
if (trigger)
|
||||
{
|
||||
//
|
||||
// The authentic fire gate (@4bbee6): armed in the current look
|
||||
// view AND a target under the reticle.
|
||||
//
|
||||
if (viewFireEnable && HasActiveTarget())
|
||||
{
|
||||
//
|
||||
// The ammo pull happens HERE, in the caller -- a failed pull
|
||||
// just stays Loaded, silently.
|
||||
//
|
||||
if (bin != NULL && bin->FeedAmmo() != 0)
|
||||
{
|
||||
weaponAlarm.SetLevel(FiringState);
|
||||
ForceUpdate();
|
||||
FireWeapon();
|
||||
if (bin->GetAmmoState() == AmmoBin::AmmoEmptyState)
|
||||
{
|
||||
weaponAlarm.SetLevel(NoAmmoState);
|
||||
}
|
||||
else if (CheckForJam())
|
||||
{
|
||||
weaponAlarm.SetLevel(JammedState);
|
||||
if (getenv("BT_MECH_LOG"))
|
||||
{
|
||||
DEBUG_STREAM << "[fire] '" << GetName()
|
||||
<< "' JAMMED (T=" << CurrentTemperatureOf()
|
||||
<< ")" << endl << flush;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
weaponAlarm.SetLevel(LoadingState);
|
||||
}
|
||||
ForceUpdate();
|
||||
recoil = rechargeRate;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//
|
||||
// THE DENIAL BLIP: a shot denied by the look-view gate does
|
||||
// SetLevel(4); SetLevel(2) -- a one-frame audio blip, the pip
|
||||
// stays Loaded and NO ammo is pulled.
|
||||
//
|
||||
weaponAlarm.SetLevel(TriggerDuringLoadState);
|
||||
weaponAlarm.SetLevel(LoadedState);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case LoadingState:
|
||||
if (trigger)
|
||||
{
|
||||
weaponAlarm.SetLevel(TriggerDuringLoadState);
|
||||
weaponAlarm.SetLevel(LoadingState);
|
||||
}
|
||||
//
|
||||
// Recoil bleeds ONLY while the electrical supply is Ready (binary
|
||||
// @4bbdf5/@4bbe04); at zero (and a round chambered) -> Loaded.
|
||||
//
|
||||
if (GetVoltageState() == Ready)
|
||||
{
|
||||
recoil -= time_slice;
|
||||
if (recoil < 0.0f)
|
||||
{
|
||||
recoil = 0.0f;
|
||||
if (bin != NULL && bin->GetAmmoState() == AmmoBin::AmmoLoadedState)
|
||||
{
|
||||
weaponAlarm.SetLevel(LoadedState);
|
||||
if (getenv("BT_MECH_LOG"))
|
||||
{
|
||||
DEBUG_STREAM << "[fire] '" << GetName()
|
||||
<< "' LOADED (rounds=" << bin->GetAmmoCount()
|
||||
<< " T=" << CurrentTemperatureOf() << ")"
|
||||
<< endl << flush;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ComputeOutputVoltage();
|
||||
break;
|
||||
|
||||
case JammedState:
|
||||
if (trigger)
|
||||
{
|
||||
weaponAlarm.SetLevel(TriggerDuringJamState);
|
||||
weaponAlarm.SetLevel(JammedState);
|
||||
}
|
||||
recoil = rechargeRate;
|
||||
break;
|
||||
|
||||
case NoAmmoState:
|
||||
if (trigger)
|
||||
{
|
||||
weaponAlarm.SetLevel(DryTriggerState); // the dry-fire blip
|
||||
}
|
||||
weaponAlarm.SetLevel(NoAmmoState); // re-assert (the roach-motel)
|
||||
recoil = rechargeRate;
|
||||
ComputeOutputVoltage(); // dial pinned at 0
|
||||
break;
|
||||
|
||||
default: // 0/1/4/6: transient audio-blip states, no case body
|
||||
break;
|
||||
}
|
||||
|
||||
Check_Fpu();
|
||||
}
|
||||
//===========================================================================//
|
||||
// File: projweap.cpp //
|
||||
// Project: BattleTech Brick: Mech weapons //
|
||||
// Contents: ProjectileWeapon -- ballistic (ammo-fed) weapon base //
|
||||
//---------------------------------------------------------------------------//
|
||||
// Copyright (C) 1995, Virtual World Entertainment, Inc. //
|
||||
// All Rights reserved worldwide //
|
||||
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL //
|
||||
//===========================================================================//
|
||||
|
||||
#include <bt.hpp>
|
||||
#pragma hdrstop
|
||||
|
||||
#if !defined(PROJWEAP_HPP)
|
||||
# include <projweap.hpp>
|
||||
#endif
|
||||
|
||||
#if !defined(MECH_HPP)
|
||||
# include <mech.hpp>
|
||||
#endif
|
||||
|
||||
#if !defined(AMMOBIN_HPP)
|
||||
# include <ammobin.hpp>
|
||||
#endif
|
||||
|
||||
#if !defined(RANDOM_HPP)
|
||||
# include <random.hpp>
|
||||
#endif
|
||||
|
||||
#if !defined(BTPLAYER_HPP)
|
||||
# include <btplayer.hpp>
|
||||
#endif
|
||||
|
||||
Derivation
|
||||
ProjectileWeapon::ClassDerivations(
|
||||
MechWeapon::ClassDerivations,
|
||||
"ProjectileWeapon"
|
||||
);
|
||||
|
||||
ProjectileWeapon::SharedData
|
||||
ProjectileWeapon::DefaultData(
|
||||
ProjectileWeapon::ClassDerivations,
|
||||
MechWeapon::MessageHandlers,
|
||||
MechWeapon::AttributeIndex,
|
||||
Subsystem::StateCount
|
||||
);
|
||||
|
||||
ProjectileWeapon::ProjectileWeapon(
|
||||
Mech *owner,
|
||||
int subsystem_ID,
|
||||
SubsystemResource *subsystem_resource,
|
||||
SharedData &shared_data
|
||||
):
|
||||
MechWeapon(owner, subsystem_ID, subsystem_resource, shared_data),
|
||||
ammoBinLink()
|
||||
{
|
||||
Check(owner);
|
||||
Check_Pointer(subsystem_resource);
|
||||
|
||||
tracerInterval = subsystem_resource->tracerInterval;
|
||||
minTimeOfFlight = subsystem_resource->minTimeOfFlight;
|
||||
minJamChance = subsystem_resource->minJamChance;
|
||||
minVoltagePercentToFire = subsystem_resource->minVoltagePercentToFire;
|
||||
|
||||
tracerCounter = 0;
|
||||
ejectState = 0;
|
||||
totalTimeToEject = 0.0f;
|
||||
timeToEject = 0.0f;
|
||||
percentOfEject = 0.0f;
|
||||
tracerModelHandle = 0;
|
||||
leadPosition = Point3D(0.0f, 0.0f, 0.0f);
|
||||
launchVelocity = Vector3D(0.0f, 0.0f, 0.0f);
|
||||
tracerOrigin = Vector3D(0.0f, 0.0f, 0.0f);
|
||||
|
||||
//
|
||||
// Link the AmmoBin subsystem the resource references by roster index (the
|
||||
// binary ctor resolves it from the owner's subsystem table and connects the
|
||||
// embedded link @0x43C). Shipped content always links a bin; log when the
|
||||
// data doesn't resolve one.
|
||||
//
|
||||
{
|
||||
Subsystem *roster_bin = NULL;
|
||||
if (subsystem_resource->ammoBinIndex >= 0
|
||||
&& subsystem_resource->ammoBinIndex < owner->GetSubsystemCount())
|
||||
{
|
||||
roster_bin = owner->GetSubsystem(subsystem_resource->ammoBinIndex);
|
||||
}
|
||||
if (roster_bin != NULL)
|
||||
{
|
||||
ammoBinLink.Add(roster_bin);
|
||||
}
|
||||
if (getenv("BT_MECH_LOG"))
|
||||
{
|
||||
DEBUG_STREAM << "[proj] '" << GetName()
|
||||
<< "' ammoBinIndex=" << subsystem_resource->ammoBinIndex
|
||||
<< " -> ";
|
||||
if (roster_bin != NULL)
|
||||
{
|
||||
DEBUG_STREAM << roster_bin->GetName()
|
||||
<< " (rounds=" << ((AmmoBin *)roster_bin)->GetAmmoCount() << ")";
|
||||
}
|
||||
else
|
||||
{
|
||||
DEBUG_STREAM << "<no bin>";
|
||||
}
|
||||
DEBUG_STREAM << endl << flush;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Install the ballistic fire state machine (a replicant copy is driven by
|
||||
// console updates instead).
|
||||
//
|
||||
if (owner->GetInstance() != Entity::ReplicantInstance)
|
||||
{
|
||||
SetPerformance(&ProjectileWeapon::ProjectileWeaponSimulation);
|
||||
}
|
||||
|
||||
Check_Fpu();
|
||||
}
|
||||
|
||||
ProjectileWeapon::~ProjectileWeapon()
|
||||
{
|
||||
}
|
||||
|
||||
Logical
|
||||
ProjectileWeapon::TestClass(Mech &)
|
||||
{
|
||||
return True;
|
||||
}
|
||||
|
||||
Logical
|
||||
ProjectileWeapon::TestInstance() const
|
||||
{
|
||||
return IsDerivedFrom(ClassDerivations);
|
||||
}
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
// FireWeapon The ballistic discharge (PARTIAL -- binary @004bc104). The
|
||||
// authentic body is heat + projectile/tracer spawn ONLY: the view/target gate,
|
||||
// the ammo pull and the recoil set all live in the CALLER (the Loaded case of
|
||||
// ProjectileWeaponSimulation) -- early-returning any gate from here while the
|
||||
// caller cycles the alarm anyway is exactly the 1995 "denied shot fakes a full
|
||||
// firing cycle" defect class. The projectile spawn (Missile entities /
|
||||
// tracer) needs the entity-spawn + targeting waves; MissileLauncher overrides
|
||||
// this for the salvo launch.
|
||||
//#############################################################################
|
||||
//
|
||||
void
|
||||
ProjectileWeapon::FireWeapon()
|
||||
{
|
||||
Check(this);
|
||||
|
||||
// Ballistic heatCostToFire is stored 1e7-unit-NATIVE in the stream (SRM6
|
||||
// reads 5.06e7 = +641K on its own sink -- the same design magnitude as the
|
||||
// PPC's +632K); dump it raw, under the heat-model experience gate (novice /
|
||||
// standard fire generates no heat -- authentic). (The EMITTER's authored
|
||||
// value is small and its 1e7 comes from the energy algebra -- see
|
||||
// EMITTER.CPP.)
|
||||
if (HeatModelActive())
|
||||
{
|
||||
AddPendingHeat(heatCostToFire);
|
||||
}
|
||||
|
||||
if (getenv("BT_MECH_LOG"))
|
||||
{
|
||||
DEBUG_STREAM << "[fire] '" << GetName()
|
||||
<< "' FIRED (ballistic, recharge=" << rechargeRate
|
||||
<< "s heat+=" << heatCostToFire << ")"
|
||||
<< endl << flush;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
// LiveFireEnabled -- the "sim live" novice lockout: owner mech ->
|
||||
// Entity::playerLink -> BTPlayer::simLive, 0 only for NOVICE experience. A
|
||||
// NULL player link reads LIVE (the unlinked dev mech / target dummy).
|
||||
//#############################################################################
|
||||
//
|
||||
Logical
|
||||
ProjectileWeapon::LiveFireEnabled()
|
||||
{
|
||||
Check(this);
|
||||
|
||||
if (owner == NULL)
|
||||
{
|
||||
return True;
|
||||
}
|
||||
BTPlayer *player = Cast_Object(BTPlayer *, owner->GetPlayerLink());
|
||||
if (player == NULL)
|
||||
{
|
||||
return True;
|
||||
}
|
||||
return player->IsSimLive();
|
||||
}
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
// CheckForJam -- the heat-scaled jam roll (binary @4bbfcc), AUTHENTIC form:
|
||||
// novice never jams (LiveFireEnabled); a cold heat model never jams (heatLoad
|
||||
// stays 0 with the model off -- UpdateHeatLoad only runs under the experience
|
||||
// gate); otherwise
|
||||
// p = 0.41 * currentTemperature / failureTemperature,
|
||||
// clamped to [minJamChance, 1.0], rolled against the uniform random. Note the
|
||||
// floor: even a cool launcher at veteran+ carries the authored minimum jam
|
||||
// chance per shot (SRM6: 5%) -- authentic pod behavior. A jammed launcher
|
||||
// clears only on ResetToInitialState (mission reset).
|
||||
//#############################################################################
|
||||
//
|
||||
Logical
|
||||
ProjectileWeapon::CheckForJam()
|
||||
{
|
||||
Check(this);
|
||||
|
||||
if (!LiveFireEnabled())
|
||||
{
|
||||
return False;
|
||||
}
|
||||
if (heatLoad <= 0.0f)
|
||||
{
|
||||
return False;
|
||||
}
|
||||
|
||||
Scalar p = (0.41f * CurrentTemperatureOf()) / failureTemperature;
|
||||
if (minJamChance <= p)
|
||||
{
|
||||
if (p > 1.0f)
|
||||
{
|
||||
p = 1.0f;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
p = minJamChance;
|
||||
}
|
||||
|
||||
return (p > (Scalar)Random) ? True : False;
|
||||
}
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
// ProjectileWeaponSimulation The ballistic per-frame fire state machine
|
||||
// (binary @004bbd04, FULLY RECOVERED in the BT411 RE). The weapon state is
|
||||
// carried in the weapon alarm: 0/1/4/6 transient audio blips, 2 Loaded,
|
||||
// 3 Loading, 5 Jammed, 7 unavailable/NoAmmo (the roach-motel: nothing in the
|
||||
// machine ever leaves it -- only a mission reset does).
|
||||
//
|
||||
// PARTIAL: the leading PoweredSubsystemSimulation electrical step, the
|
||||
// destroyed / FailureHeat / mech-disabled hard gate (gate 1), the magazine
|
||||
// eject, the electrical-Ready recoil gate, the heat-scaled jam roll and the
|
||||
// HasActiveTarget half of the fire gate are deferred with the power / heat /
|
||||
// damage / targeting waves. What runs is authentic in shape: the single
|
||||
// trigger-edge sample, the dry-bin gate (gate 2), the Loaded ammo-pull ->
|
||||
// Firing -> Loading cycle with the denial blip, the Loading recoil bleed +
|
||||
// recharge dial, and the NoAmmo dry-fire blip.
|
||||
//
|
||||
// DEV hook BT_FORCE_FIRE=1: pulses the trigger whenever the weapon is Loaded
|
||||
// (same as the emitter hook) -- every armed weapon auto-fires at its authored
|
||||
// cadence until its bin runs dry.
|
||||
//#############################################################################
|
||||
//
|
||||
void
|
||||
ProjectileWeapon::ProjectileWeaponSimulation(Scalar time_slice)
|
||||
{
|
||||
Check(this);
|
||||
|
||||
//
|
||||
// The PoweredSubsystem step first (binary @4bbd12): the HeatSink thermal
|
||||
// absorb/conduct + the electrical state machine.
|
||||
//
|
||||
PoweredSubsystem::PoweredSubsystemSimulation(time_slice);
|
||||
|
||||
{
|
||||
static int forceFire = -1;
|
||||
if (forceFire < 0)
|
||||
{
|
||||
forceFire = (getenv("BT_FORCE_FIRE") != NULL) ? 1 : 0;
|
||||
}
|
||||
if (forceFire)
|
||||
{
|
||||
fireImpulse = (GetWeaponState() == LoadedState) ? 1.0f : 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// The trigger edge is sampled ONCE, before the gates.
|
||||
//
|
||||
Logical trigger = CheckFireEdge();
|
||||
|
||||
targetWithinRange = UpdateTargeting();
|
||||
|
||||
//
|
||||
// Gate 1 (@4bbd36): weapon DESTROYED or its own sink at FailureHeat -> pin
|
||||
// full recoil + the unavailable alarm. No return -- the frame continues
|
||||
// into the state machine (whose NoAmmo case re-asserts). (The owning-mech-
|
||||
// disabled half joins with the damage wave.)
|
||||
//
|
||||
if (GetSimulationState() == 1 || GetHeatState() == FailureHeat
|
||||
|| (owner != NULL && owner->IsMechDestroyed()))
|
||||
{
|
||||
if (getenv("BT_MECH_LOG") && GetWeaponState() != NoAmmoState)
|
||||
{
|
||||
DEBUG_STREAM << "[fire] '" << GetName()
|
||||
<< "' -> UNAVAILABLE (gate1: heatState=" << GetHeatState()
|
||||
<< " T=" << CurrentTemperatureOf() << ")" << endl << flush;
|
||||
}
|
||||
recoil = rechargeRate;
|
||||
weaponAlarm.SetLevel(NoAmmoState);
|
||||
}
|
||||
|
||||
//
|
||||
// Gate 2: the linked AmmoBin. Empty (or missing) -> pin unavailable EVERY
|
||||
// frame while dry.
|
||||
//
|
||||
AmmoBin *bin = (AmmoBin *)ammoBinLink.Resolve();
|
||||
if (bin != NULL && bin->GetAmmoState() == AmmoBin::AmmoEmptyState)
|
||||
{
|
||||
if (getenv("BT_MECH_LOG") && GetWeaponState() != NoAmmoState)
|
||||
{
|
||||
DEBUG_STREAM << "[fire] '" << GetName()
|
||||
<< "' -> NoAmmo (bin dry)" << endl << flush;
|
||||
}
|
||||
weaponAlarm.SetLevel(NoAmmoState);
|
||||
}
|
||||
|
||||
switch (GetWeaponState())
|
||||
{
|
||||
case LoadedState:
|
||||
if (trigger)
|
||||
{
|
||||
//
|
||||
// The authentic fire gate (@4bbee6): armed in the current look
|
||||
// view AND a target under the reticle.
|
||||
//
|
||||
if (viewFireEnable && HasActiveTarget())
|
||||
{
|
||||
//
|
||||
// The ammo pull happens HERE, in the caller -- a failed pull
|
||||
// just stays Loaded, silently.
|
||||
//
|
||||
if (bin != NULL && bin->FeedAmmo() != 0)
|
||||
{
|
||||
weaponAlarm.SetLevel(FiringState);
|
||||
ForceUpdate();
|
||||
FireWeapon();
|
||||
if (bin->GetAmmoState() == AmmoBin::AmmoEmptyState)
|
||||
{
|
||||
weaponAlarm.SetLevel(NoAmmoState);
|
||||
}
|
||||
else if (CheckForJam())
|
||||
{
|
||||
weaponAlarm.SetLevel(JammedState);
|
||||
if (getenv("BT_MECH_LOG"))
|
||||
{
|
||||
DEBUG_STREAM << "[fire] '" << GetName()
|
||||
<< "' JAMMED (T=" << CurrentTemperatureOf()
|
||||
<< ")" << endl << flush;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
weaponAlarm.SetLevel(LoadingState);
|
||||
}
|
||||
ForceUpdate();
|
||||
recoil = rechargeRate;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//
|
||||
// THE DENIAL BLIP: a shot denied by the look-view gate does
|
||||
// SetLevel(4); SetLevel(2) -- a one-frame audio blip, the pip
|
||||
// stays Loaded and NO ammo is pulled.
|
||||
//
|
||||
weaponAlarm.SetLevel(TriggerDuringLoadState);
|
||||
weaponAlarm.SetLevel(LoadedState);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case LoadingState:
|
||||
if (trigger)
|
||||
{
|
||||
weaponAlarm.SetLevel(TriggerDuringLoadState);
|
||||
weaponAlarm.SetLevel(LoadingState);
|
||||
}
|
||||
//
|
||||
// Recoil bleeds ONLY while the electrical supply is Ready (binary
|
||||
// @4bbdf5/@4bbe04); at zero (and a round chambered) -> Loaded.
|
||||
//
|
||||
if (GetVoltageState() == Ready)
|
||||
{
|
||||
recoil -= time_slice;
|
||||
if (recoil < 0.0f)
|
||||
{
|
||||
recoil = 0.0f;
|
||||
if (bin != NULL && bin->GetAmmoState() == AmmoBin::AmmoLoadedState)
|
||||
{
|
||||
weaponAlarm.SetLevel(LoadedState);
|
||||
if (getenv("BT_MECH_LOG"))
|
||||
{
|
||||
DEBUG_STREAM << "[fire] '" << GetName()
|
||||
<< "' LOADED (rounds=" << bin->GetAmmoCount()
|
||||
<< " T=" << CurrentTemperatureOf() << ")"
|
||||
<< endl << flush;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ComputeOutputVoltage();
|
||||
break;
|
||||
|
||||
case JammedState:
|
||||
if (trigger)
|
||||
{
|
||||
weaponAlarm.SetLevel(TriggerDuringJamState);
|
||||
weaponAlarm.SetLevel(JammedState);
|
||||
}
|
||||
recoil = rechargeRate;
|
||||
break;
|
||||
|
||||
case NoAmmoState:
|
||||
if (trigger)
|
||||
{
|
||||
weaponAlarm.SetLevel(DryTriggerState); // the dry-fire blip
|
||||
}
|
||||
weaponAlarm.SetLevel(NoAmmoState); // re-assert (the roach-motel)
|
||||
recoil = rechargeRate;
|
||||
ComputeOutputVoltage(); // dial pinned at 0
|
||||
break;
|
||||
|
||||
default: // 0/1/4/6: transient audio-blip states, no case body
|
||||
break;
|
||||
}
|
||||
|
||||
Check_Fpu();
|
||||
}
|
||||
|
||||
+232
-227
@@ -1,227 +1,232 @@
|
||||
//===========================================================================//
|
||||
// File: sensor.cpp //
|
||||
// Project: BattleTech Brick: Mech subsystems //
|
||||
// Contents: Sensor -- the radar / targeting subsystem //
|
||||
//---------------------------------------------------------------------------//
|
||||
// Copyright (C) 1995, Virtual World Entertainment, Inc. //
|
||||
// All Rights reserved worldwide //
|
||||
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL //
|
||||
//===========================================================================//
|
||||
|
||||
#include <bt.hpp>
|
||||
#pragma hdrstop
|
||||
|
||||
#if !defined(SENSOR_HPP)
|
||||
# include <sensor.hpp>
|
||||
#endif
|
||||
|
||||
#if !defined(MECH_HPP)
|
||||
# include <mech.hpp>
|
||||
#endif
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
// Shared data support
|
||||
//#############################################################################
|
||||
//
|
||||
Derivation
|
||||
Sensor::ClassDerivations(
|
||||
PoweredSubsystem::ClassDerivations,
|
||||
"Sensor"
|
||||
);
|
||||
|
||||
Sensor::SharedData
|
||||
Sensor::DefaultData(
|
||||
Sensor::ClassDerivations,
|
||||
Subsystem::MessageHandlers,
|
||||
Subsystem::AttributeIndex,
|
||||
Subsystem::StateCount
|
||||
);
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
// The radar / targeting subsystem. A non-replicant (master) instance runs the
|
||||
// per-frame SensorSimulation.
|
||||
//#############################################################################
|
||||
//
|
||||
Sensor::Sensor(
|
||||
Mech *owner,
|
||||
int subsystem_ID,
|
||||
SubsystemResource *subsystem_resource
|
||||
):
|
||||
PoweredSubsystem(owner, subsystem_ID, subsystem_resource, DefaultData)
|
||||
{
|
||||
Check(owner);
|
||||
Check_Pointer(subsystem_resource);
|
||||
|
||||
radarPercent = 1.0f; // RadarBaseline
|
||||
selfTest = False;
|
||||
badVoltage = False;
|
||||
|
||||
//
|
||||
// Install the per-frame performance unless the owner is a replicant copy
|
||||
// (which is driven by console updates instead).
|
||||
//
|
||||
if (owner->GetInstance() != Entity::ReplicantInstance)
|
||||
{
|
||||
SetPerformance(&Sensor::SensorSimulation);
|
||||
}
|
||||
|
||||
Check_Fpu();
|
||||
}
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
//#############################################################################
|
||||
//
|
||||
Sensor::~Sensor()
|
||||
{
|
||||
}
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
//#############################################################################
|
||||
//
|
||||
Logical
|
||||
Sensor::TestClass(Mech &)
|
||||
{
|
||||
return True;
|
||||
}
|
||||
|
||||
Logical
|
||||
Sensor::TestInstance() const
|
||||
{
|
||||
return IsDerivedFrom(ClassDerivations);
|
||||
}
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
//#############################################################################
|
||||
//
|
||||
void
|
||||
Sensor::ResetToInitialState()
|
||||
{
|
||||
Check(this);
|
||||
|
||||
radarPercent = 1.0f; // RadarBaseline
|
||||
selfTest = False;
|
||||
badVoltage = False;
|
||||
|
||||
PoweredSubsystem::ResetToInitialState(True);
|
||||
}
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
// Per-frame radar / targeting update. Not yet reconstructed -- fires only once
|
||||
// the mech is ticking.
|
||||
//#############################################################################
|
||||
//
|
||||
void
|
||||
Sensor::SensorSimulation(Scalar time_slice)
|
||||
{
|
||||
Check(this);
|
||||
|
||||
//
|
||||
// The authentic per-frame sensor update (binary @004b1c4c): the
|
||||
// PoweredSubsystem step first, then radarPercent = baseline - structural
|
||||
// damage, gated by the electrical Ready state and the heat state.
|
||||
// (Still deferred: the novice-mode HeatModelOff gate -- the player
|
||||
// experience switch -- joins with the player-link accessor wave.)
|
||||
//
|
||||
PoweredSubsystemSimulation(time_slice);
|
||||
|
||||
radarPercent = 1.0f - GetSubsystemDamageLevel(); // RadarBaseline - zoneDamage
|
||||
if (radarPercent < 0.0f)
|
||||
{
|
||||
radarPercent = 0.0f;
|
||||
}
|
||||
selfTest = True;
|
||||
|
||||
if (GetVoltageState() == Ready)
|
||||
{
|
||||
badVoltage = False;
|
||||
}
|
||||
else
|
||||
{
|
||||
badVoltage = True;
|
||||
radarPercent = 0.0f;
|
||||
}
|
||||
|
||||
switch (GetHeatState())
|
||||
{
|
||||
case NormalHeat:
|
||||
selfTest = True;
|
||||
break;
|
||||
|
||||
case DegradationHeat:
|
||||
radarPercent *= 0.5f; // HeatDegradationScale
|
||||
selfTest = True;
|
||||
break;
|
||||
|
||||
case FailureHeat:
|
||||
radarPercent = 0.0f;
|
||||
selfTest = False;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
{
|
||||
//
|
||||
// One-shot confirmation that the engine's per-frame entity/roster tick
|
||||
// (Entity::PerformAndWatch) has engaged -- i.e. the mission reached
|
||||
// RunningMission and the mech is being simulated each frame.
|
||||
//
|
||||
static int firstTick = 0;
|
||||
if (!firstTick && getenv("BT_MECH_LOG"))
|
||||
{
|
||||
firstTick = 1;
|
||||
DEBUG_STREAM << "[tick] roster live (first Sensor frame), radarPercent="
|
||||
<< radarPercent << " voltState=" << GetVoltageState()
|
||||
<< endl << flush;
|
||||
}
|
||||
}
|
||||
|
||||
Check_Fpu();
|
||||
}
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
// Damage / death.
|
||||
//#############################################################################
|
||||
//
|
||||
void
|
||||
Sensor::TakeDamage(Damage &damage)
|
||||
{
|
||||
Check(this);
|
||||
PoweredSubsystem::TakeDamage(damage);
|
||||
}
|
||||
|
||||
void
|
||||
Sensor::DeathReset(Logical /*full_recovery*/)
|
||||
{
|
||||
Check(this);
|
||||
ResetToInitialState();
|
||||
}
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
// CreateStreamedSubsystem Model-load-time construction. Not yet reconstructed
|
||||
// (see MECHSUB.NOTES.md -- the 4.10 subsystem stream format).
|
||||
//#############################################################################
|
||||
//
|
||||
int
|
||||
Sensor::CreateStreamedSubsystem(
|
||||
NotationFile *,
|
||||
const char *,
|
||||
const char *,
|
||||
SubsystemResource *,
|
||||
NotationFile *,
|
||||
const ResourceDirectories *,
|
||||
int
|
||||
)
|
||||
{
|
||||
Fail("Sensor::CreateStreamedSubsystem -- sensor.cpp not yet reconstructed");
|
||||
return 0;
|
||||
}
|
||||
//===========================================================================//
|
||||
// File: sensor.cpp //
|
||||
// Project: BattleTech Brick: Mech subsystems //
|
||||
// Contents: Sensor -- the radar / targeting subsystem //
|
||||
//---------------------------------------------------------------------------//
|
||||
// Copyright (C) 1995, Virtual World Entertainment, Inc. //
|
||||
// All Rights reserved worldwide //
|
||||
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL //
|
||||
//===========================================================================//
|
||||
|
||||
#include <bt.hpp>
|
||||
#pragma hdrstop
|
||||
|
||||
#if !defined(SENSOR_HPP)
|
||||
# include <sensor.hpp>
|
||||
#endif
|
||||
|
||||
#if !defined(MECH_HPP)
|
||||
# include <mech.hpp>
|
||||
#endif
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
// Shared data support
|
||||
//#############################################################################
|
||||
//
|
||||
Derivation
|
||||
Sensor::ClassDerivations(
|
||||
PoweredSubsystem::ClassDerivations,
|
||||
"Sensor"
|
||||
);
|
||||
|
||||
Sensor::SharedData
|
||||
Sensor::DefaultData(
|
||||
Sensor::ClassDerivations,
|
||||
Subsystem::MessageHandlers,
|
||||
Subsystem::AttributeIndex,
|
||||
Subsystem::StateCount
|
||||
);
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
// The radar / targeting subsystem. A non-replicant (master) instance runs the
|
||||
// per-frame SensorSimulation.
|
||||
//#############################################################################
|
||||
//
|
||||
Sensor::Sensor(
|
||||
Mech *owner,
|
||||
int subsystem_ID,
|
||||
SubsystemResource *subsystem_resource
|
||||
):
|
||||
PoweredSubsystem(owner, subsystem_ID, subsystem_resource, DefaultData)
|
||||
{
|
||||
Check(owner);
|
||||
Check_Pointer(subsystem_resource);
|
||||
|
||||
radarPercent = 1.0f; // RadarBaseline
|
||||
selfTest = False;
|
||||
badVoltage = False;
|
||||
|
||||
//
|
||||
// Install the per-frame performance unless the owner is a replicant copy
|
||||
// (which is driven by console updates instead).
|
||||
//
|
||||
if (owner->GetInstance() != Entity::ReplicantInstance)
|
||||
{
|
||||
SetPerformance(&Sensor::SensorSimulation);
|
||||
}
|
||||
|
||||
Check_Fpu();
|
||||
}
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
//#############################################################################
|
||||
//
|
||||
Sensor::~Sensor()
|
||||
{
|
||||
}
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
//#############################################################################
|
||||
//
|
||||
Logical
|
||||
Sensor::TestClass(Mech &)
|
||||
{
|
||||
return True;
|
||||
}
|
||||
|
||||
Logical
|
||||
Sensor::TestInstance() const
|
||||
{
|
||||
return IsDerivedFrom(ClassDerivations);
|
||||
}
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
//#############################################################################
|
||||
//
|
||||
void
|
||||
Sensor::ResetToInitialState()
|
||||
{
|
||||
Check(this);
|
||||
|
||||
radarPercent = 1.0f; // RadarBaseline
|
||||
selfTest = False;
|
||||
badVoltage = False;
|
||||
|
||||
PoweredSubsystem::ResetToInitialState(True);
|
||||
}
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
// Per-frame radar / targeting update. Not yet reconstructed -- fires only once
|
||||
// the mech is ticking.
|
||||
//#############################################################################
|
||||
//
|
||||
void
|
||||
Sensor::SensorSimulation(Scalar time_slice)
|
||||
{
|
||||
Check(this);
|
||||
|
||||
//
|
||||
// The authentic per-frame sensor update (binary @004b1c4c): the
|
||||
// PoweredSubsystem step first, then radarPercent = baseline - structural
|
||||
// damage, gated by the electrical Ready state and the heat state.
|
||||
// (Still deferred: the novice-mode HeatModelOff gate -- the player
|
||||
// experience switch -- joins with the player-link accessor wave.)
|
||||
//
|
||||
PoweredSubsystemSimulation(time_slice);
|
||||
|
||||
radarPercent = 1.0f - GetSubsystemDamageLevel(); // RadarBaseline - zoneDamage
|
||||
if (radarPercent < 0.0f)
|
||||
{
|
||||
radarPercent = 0.0f;
|
||||
}
|
||||
selfTest = True;
|
||||
|
||||
if (GetVoltageState() == Ready)
|
||||
{
|
||||
badVoltage = False;
|
||||
}
|
||||
else
|
||||
{
|
||||
badVoltage = True;
|
||||
radarPercent = 0.0f;
|
||||
}
|
||||
|
||||
switch (GetHeatState())
|
||||
{
|
||||
case NormalHeat:
|
||||
selfTest = True;
|
||||
break;
|
||||
|
||||
case DegradationHeat:
|
||||
radarPercent *= 0.5f; // HeatDegradationScale
|
||||
selfTest = True;
|
||||
break;
|
||||
|
||||
case FailureHeat:
|
||||
radarPercent = 0.0f;
|
||||
selfTest = False;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
{
|
||||
//
|
||||
// One-shot confirmation that the engine's per-frame entity/roster tick
|
||||
// (Entity::PerformAndWatch) has engaged -- i.e. the mission reached
|
||||
// RunningMission and the mech is being simulated each frame.
|
||||
//
|
||||
static int firstTick = 0;
|
||||
if (!firstTick && getenv("BT_MECH_LOG"))
|
||||
{
|
||||
firstTick = 1;
|
||||
DEBUG_STREAM << "[tick] roster live (first Sensor frame), radarPercent="
|
||||
<< radarPercent << " voltState=" << GetVoltageState()
|
||||
<< endl << flush;
|
||||
}
|
||||
}
|
||||
|
||||
Check_Fpu();
|
||||
}
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
// Damage / death.
|
||||
//#############################################################################
|
||||
//
|
||||
void
|
||||
Sensor::TakeDamage(Damage &damage)
|
||||
{
|
||||
Check(this);
|
||||
PoweredSubsystem::TakeDamage(damage);
|
||||
}
|
||||
|
||||
void
|
||||
Sensor::DeathReset(Logical full_recovery)
|
||||
{
|
||||
Check(this);
|
||||
//
|
||||
// The base heal FIRST (private zone + DestroyedState + status level --
|
||||
// a crit-killed radar must come back), then the sensor re-baseline.
|
||||
//
|
||||
MechSubsystem::DeathReset(full_recovery);
|
||||
ResetToInitialState();
|
||||
}
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
// CreateStreamedSubsystem Model-load-time construction. Not yet reconstructed
|
||||
// (see MECHSUB.NOTES.md -- the 4.10 subsystem stream format).
|
||||
//#############################################################################
|
||||
//
|
||||
int
|
||||
Sensor::CreateStreamedSubsystem(
|
||||
NotationFile *,
|
||||
const char *,
|
||||
const char *,
|
||||
SubsystemResource *,
|
||||
NotationFile *,
|
||||
const ResourceDirectories *,
|
||||
int
|
||||
)
|
||||
{
|
||||
Fail("Sensor::CreateStreamedSubsystem -- sensor.cpp not yet reconstructed");
|
||||
return 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user