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:
Cyd
2026-07-24 16:35:05 -05:00
co-authored by Claude Fable 5
parent 2f0d3b6239
commit 623c872c92
19 changed files with 5857 additions and 5363 deletions
+104 -82
View File
@@ -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;
}
+118 -112
View File
@@ -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).
+2 -1
View File
@@ -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)
{
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+141
View File
@@ -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
+12 -1
View File
@@ -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
+34
View File
@@ -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.
+338 -310
View File
@@ -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;
}
+202 -192
View File
@@ -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
+28
View File
@@ -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
+271 -263
View File
@@ -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
+455 -450
View File
@@ -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
+424 -407
View File
@@ -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
+439 -438
View File
@@ -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
View File
@@ -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;
}