BT410 5.3.118: the census closes -- the authoring-side CreateStreamedSubsystem family, and the Fail count reaches ZERO

The last eight stubs plus the two chain layers they actually need
(PoweredSubsystem @004b13ac and ProjectileWeapon @004bc7cc were DECLARED in
our headers all along -- only their definitions were missing).  These are
the notation->stream converters the 1995 authoring pipeline ran; missions
read prebuilt streams and never call them, so the verification standard is
compile + link + the raw decomp's key fingerprints -- and those fingerprints
convicted the donors three more times:

- The heat chain: Condenser, Reservoir AND PoweredSubsystem all chain ONE
  shared parser (@004ae150, reconstructed in full from the decomp -- the
  BT411 donor's body was its own TODO stub with a miscited address).  It
  reads the five thermal scalars and the conduction-graph link (the
  linkedSinkIndex our wire-format work dump-verified from the runtime side,
  with the +2 segment-table bias), and HeatSink adds no keys of its own.
- The Emitter's SeekVoltage LADDER is a notation ENTRY LIST walked in file
  order (curve rungs in sequence, the RecommendedIndex entry by name); the
  donor flattened it to one keyed read, which would author one-rung ladders
  against the runtime's real five-rung ones.  MakeEntryList/GetFirstEntry
  is the engine's own idiom.
- PipColor and MuzzleVelocity parse through the engine's authentic
  Convert_From_Ascii overloads (RGBColor / Vector3D); the donors' sscanf
  substitutes were port drift.  Explosion and alarm models resolve as model
  FAMILIES (ModelListResourceType == the literal 1 in the binary; the donor
  comment calling it GameModelResourceType was wrong-name-right-number).

Entry reads target the SUBSYSTEM notation file -- the decomp's fifth
argument at every site; several donors wrote model_file.  Class IDs stamp
through OUR reader's enum symbols (they parse the real BTL4.RES streams
today, and the 3000-base enum lands exactly on the binary's 0xBBB.. ids;
0x0BCD is the weapon-base stamp every concrete leaf overwrites).

Stubs: NONE.  Every function in restoration/source410 now has a body.
Mission soak clean.  Remaining reconstruction is depth, not holes: the
mech4/mech3 archaeology (IntegrateMotion, the master-perf disasm, the
stream builders that seed the 0xC/0x100 flags), HUD blocks 4-6, and the
live networked/rig verifications queued for when a pod is available.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-08-04 11:22:12 -05:00
co-authored by Claude Fable 5
parent b01db52d43
commit 80ac2943a0
11 changed files with 1223 additions and 259 deletions
+110 -10
View File
@@ -611,18 +611,118 @@ void
// CreateStreamedSubsystem Model-load-time construction. Not yet reconstructed.
//#############################################################################
//
//
//#############################################################################
// CreateStreamedSubsystem -- binary @004bb478 (1024 bytes). Chains the
// weapon record, stamps classID 0x0BC8 / size 0x1DC, then DischargeTime,
// GraphicLength and the SEEKVOLTAGE LADDER: the binary walks a notation
// ENTRY LIST (prefix "SeekVoltage") in file order -- curve samples fill
// seekVoltage[] in sequence, and the entry named
// "SeekVoltageRecommendedIndex" sets the recommended gear. (The BT411
// donor flattened this to a single keyed read -- that would author a
// one-rung ladder; the runtime's authored 5-rung ladders are real.)
//#############################################################################
//
int
Emitter::CreateStreamedSubsystem(
ResourceFile *,
NotationFile *,
const char *,
const char *,
SubsystemResource *,
NotationFile *,
const ResourceDirectories *,
int
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
)
{
Fail("Emitter::CreateStreamedSubsystem -- emitter.cpp not yet reconstructed");
return 0;
if (
!MechWeapon::CreateStreamedSubsystem(
resource_file, model_file, model_name, subsystem_name,
subsystem_resource, subsystem_file, directories, passes
)
)
{
return False;
}
subsystem_resource->subsystemModelSize = 0x1DC;
subsystem_resource->classID = RegisteredClass::EmitterClassID;
if (passes == 1)
{
int
rung;
subsystem_resource->graphicLength = -1.0f;
subsystem_resource->dischargeTime = -1.0f;
for (rung = 0; rung < 5; rung++)
{
subsystem_resource->seekVoltage[rung] = -1.0f;
}
subsystem_resource->seekVoltageRecommendedIndex = -1;
}
if (
!subsystem_file->GetEntry(subsystem_name, "DischargeTime",
&subsystem_resource->dischargeTime)
&& subsystem_resource->dischargeTime == -1.0f
)
{
DEBUG_STREAM << subsystem_name << " missing DischargeTime!" << endl;
return False;
}
if (
!subsystem_file->GetEntry(subsystem_name, "GraphicLength",
&subsystem_resource->graphicLength)
&& subsystem_resource->graphicLength == -1.0f
)
{
DEBUG_STREAM << subsystem_name << " missing GraphicLength!" << endl;
return False;
}
//
// The voltage ladder.
//
NameList
*seek_list = subsystem_file->MakeEntryList(subsystem_name, "SeekVoltage");
if (
(seek_list == NULL || seek_list->GetFirstEntry() == NULL) &&
subsystem_resource->seekVoltageRecommendedIndex == -1
)
{
if (seek_list != NULL)
{
delete seek_list;
}
DEBUG_STREAM << subsystem_name << " missing SeekVoltage" << endl;
return False;
}
if (seek_list != NULL)
{
Scalar
*rung_cursor = &subsystem_resource->seekVoltage[0];
ObjectNameList::Entry
*entry;
for (
entry = seek_list->GetFirstEntry();
entry != NULL;
entry = entry->GetNextEntry()
)
{
if (entry->IsName("SeekVoltageRecommendedIndex"))
{
subsystem_resource->seekVoltageRecommendedIndex =
entry->GetAtoi();
}
else
{
*rung_cursor++ = (Scalar)entry->GetAtof();
}
}
delete seek_list;
}
Check_Fpu();
return True;
}
+119 -9
View File
@@ -147,19 +147,129 @@ Logical
// damage-zone stream). Not yet reconstructed (see MECHSUB.NOTES.md).
//#############################################################################
//
//
//#############################################################################
// CreateStreamedSubsystem -- binary @004ae150 (904 bytes), read in full from
// the raw decomp (the BT411 donor's body was its own TODO stub). The shared
// heat-record parser: Condenser, Reservoir AND PoweredSubsystem all chain
// THIS function directly (HeatSink adds no keys of its own). Stamps classID
// 0x0BBC / size 0xFC; five required thermal scalars; the conduction-graph
// link ("HeatSink", a subsystem name -> index with the +2 segment-table
// bias) lands in HeatSink::SubsystemResource::linkedSinkIndex -- the callers
// always carry at least a HeatSink-sized record, hence the cast.
//#############################################################################
//
int
HeatableSubsystem::CreateStreamedSubsystem(
NotationFile *,
const char *,
const char *,
SubsystemResource *,
NotationFile *,
const ResourceDirectories *,
int
NotationFile *model_file,
const char *model_name,
const char *subsystem_name,
SubsystemResource *subsystem_resource,
NotationFile *subsystem_file,
const ResourceDirectories *directories,
int passes
)
{
Fail("HeatableSubsystem::CreateStreamedSubsystem -- heat.cpp not yet reconstructed");
return 0;
if (
!MechSubsystem::CreateStreamedSubsystem(
model_file, model_name, subsystem_name,
subsystem_resource, subsystem_file, directories, passes
)
)
{
return False;
}
subsystem_resource->subsystemModelSize = 0xFC;
subsystem_resource->classID = RegisteredClass::HeatableSubsystemClassID;
HeatSink::SubsystemResource
*sink_resource = (HeatSink::SubsystemResource *)subsystem_resource;
if (passes == 1)
{
subsystem_resource->startingTemperature = -1.0f;
subsystem_resource->degradationTemperature = -1.0f;
subsystem_resource->failureTemperature = -1.0f;
subsystem_resource->thermalConductance = -1.0f;
subsystem_resource->thermalMass = -1.0f;
sink_resource->linkedSinkIndex = -1;
}
if (
!subsystem_file->GetEntry(subsystem_name, "StartingTemperature",
&subsystem_resource->startingTemperature)
&& subsystem_resource->startingTemperature == -1.0f
)
{
DEBUG_STREAM << subsystem_name << " missing StartingTemperature!" << endl;
return False;
}
if (
!subsystem_file->GetEntry(subsystem_name, "DegradationTemperature",
&subsystem_resource->degradationTemperature)
&& subsystem_resource->degradationTemperature == -1.0f
)
{
DEBUG_STREAM << subsystem_name << " missing DegradationTemperature!" << endl;
return False;
}
if (
!subsystem_file->GetEntry(subsystem_name, "FailureTemperature",
&subsystem_resource->failureTemperature)
&& subsystem_resource->failureTemperature == -1.0f
)
{
DEBUG_STREAM << subsystem_name << " missing FailureTemperature!" << endl;
return False;
}
if (
!subsystem_file->GetEntry(subsystem_name, "ThermalConductance",
&subsystem_resource->thermalConductance)
&& subsystem_resource->thermalConductance == -1.0f
)
{
DEBUG_STREAM << subsystem_name << " missing ThermalConductance!" << endl;
return False;
}
if (
!subsystem_file->GetEntry(subsystem_name, "ThermalMass",
&subsystem_resource->thermalMass)
&& subsystem_resource->thermalMass == -1.0f
)
{
DEBUG_STREAM << subsystem_name << " missing ThermalMass!" << endl;
return False;
}
//
// The conduction-graph link.
//
const char
*sink_name = "Unspecified";
if (
!subsystem_file->GetEntry(subsystem_name, "HeatSink", &sink_name)
&& sink_resource->linkedSinkIndex == -1
)
{
DEBUG_STREAM << subsystem_name << " missing HeatSink!" << endl;
return False;
}
if (strcmp(sink_name, "Unspecified") != 0)
{
sink_resource->linkedSinkIndex =
Find_Subsystem_Index(subsystem_file, sink_name);
}
if (sink_resource->linkedSinkIndex < 0)
{
DEBUG_STREAM << subsystem_name << " has an invalid heat sink!" << endl;
return False;
}
sink_resource->linkedSinkIndex += 2;
Check_Fpu();
return True;
}
//###########################################################################
+114 -9
View File
@@ -309,19 +309,124 @@ void
// MECHSUB.NOTES.md -- the 4.10 per-type damage-zone stream format).
//#############################################################################
//
//
//#############################################################################
// CreateStreamedSubsystem -- binary @004ac9ec (1469 bytes). The authoring
// converter: parse the damageable-subsystem record from the notation files
// onto the stream resource. First pass primes every field to its unset
// sentinel; on later passes a missing key is only an error if the field is
// still unset. All entry reads come from the SUBSYSTEM notation file (the
// binary's fifth argument; the BT411 donor's model_file reads were drift).
//#############################################################################
//
int
MechSubsystem::CreateStreamedSubsystem(
NotationFile *,
const char *,
const char *,
SubsystemResource *,
NotationFile *,
const ResourceDirectories *,
int
NotationFile *model_file,
const char *model_name,
const char *subsystem_name,
SubsystemResource *subsystem_resource,
NotationFile *subsystem_file,
const ResourceDirectories *directories,
int passes
)
{
Fail("MechSubsystem::CreateStreamedSubsystem -- mechsub.cpp not yet reconstructed");
return 0;
if (
!Subsystem::CreateStreamedSubsystem(
model_file, model_name, subsystem_name,
subsystem_resource, subsystem_file, directories
)
)
{
return False;
}
subsystem_resource->subsystemModelSize = 0xE4;
subsystem_resource->classID = RegisteredClass::MechSubsystemClassID;
if (passes == 1)
{
int
facing;
subsystem_resource->criticalReference = -1.0f;
for (facing = 0; facing < 5; facing++)
{
subsystem_resource->armorByFacing[facing] = -1.0f;
}
subsystem_resource->structureReference = -1.0f;
subsystem_resource->vitalSubsystemIndex = -1;
memset(subsystem_resource->videoObjectName, 0,
sizeof(subsystem_resource->videoObjectName));
strcpy(subsystem_resource->videoObjectName, "None");
subsystem_resource->alarmModel = 0;
subsystem_resource->printSimulationState = 0;
subsystem_resource->collisionCriticalHitWeight = 0.0f;
}
subsystem_file->GetEntry(subsystem_name, "PrintSimulationState",
&subsystem_resource->printSimulationState);
subsystem_file->GetEntry(subsystem_name, "CollisionCriticalHitWeight",
&subsystem_resource->collisionCriticalHitWeight);
const char
*video_name = "Unspecified";
subsystem_file->GetEntry(subsystem_name, "VideoObjectName", &video_name);
if (strcmp(video_name, "Unspecified") != 0)
{
strcpy(subsystem_resource->videoObjectName, video_name);
}
//
// "VitalSubsystem" -- optional; a skeleton page name resolved to a
// segment index.
//
const char
*vital_name = "Unspecified";
subsystem_file->GetEntry(subsystem_name, "VitalSubsystem", &vital_name);
if (strcmp(vital_name, "Unspecified") != 0)
{
subsystem_resource->vitalSubsystemIndex =
Get_Segment_Index(model_file, model_name, directories, vital_name);
}
//
// The armour/crit economy keys (binary key strings @0x50e09d..0x50e15d):
// WeaponDamagePoints and CriticalHitScoreBonus are REQUIRED; the five
// per-damage-type points are optional. (structureReference /
// armorByFacing are this tree's names for the same stream fields the
// runtime damage economy reads.)
//
if (
!subsystem_file->GetEntry(subsystem_name, "WeaponDamagePoints",
&subsystem_resource->structureReference)
&& subsystem_resource->structureReference == -1.0f
)
{
DEBUG_STREAM << subsystem_name << " Must have a WeaponDamagePoints" << endl;
return False;
}
if (
!subsystem_file->GetEntry(subsystem_name, "CriticalHitScoreBonus",
&subsystem_resource->criticalReference)
&& subsystem_resource->criticalReference == -1.0f
)
{
DEBUG_STREAM << subsystem_name << " Must have a CriticalHitScoreBonus" << endl;
return False;
}
subsystem_file->GetEntry(subsystem_name, "CollisionDamagePoints",
&subsystem_resource->armorByFacing[0]);
subsystem_file->GetEntry(subsystem_name, "BallisticDamagePoints",
&subsystem_resource->armorByFacing[1]);
subsystem_file->GetEntry(subsystem_name, "ExplosiveDamagePoints",
&subsystem_resource->armorByFacing[2]);
subsystem_file->GetEntry(subsystem_name, "LaserDamagePoints",
&subsystem_resource->armorByFacing[3]);
subsystem_file->GetEntry(subsystem_name, "EnergyDamagePoints",
&subsystem_resource->armorByFacing[4]);
Check_Fpu();
return True;
}
//
+115 -67
View File
@@ -1,67 +1,115 @@
//===========================================================================//
// File: mechtech.cpp //
// Project: BattleTech Brick: Mech subsystems //
// Contents: MechTech -- the on-board technician / repair subsystem //
//---------------------------------------------------------------------------//
// Copyright (C) 1995, Virtual World Entertainment, Inc. //
// All Rights reserved worldwide //
//===========================================================================//
#include <bt.hpp>
#pragma hdrstop
#if !defined(MECHTECH_HPP)
# include <mechtech.hpp>
#endif
#if !defined(MECH_HPP)
# include <mech.hpp>
#endif
Derivation
MechTech::ClassDerivations(Subsystem::ClassDerivations, "MechTech");
MechTech::SharedData
MechTech::DefaultData(
MechTech::ClassDerivations,
Subsystem::MessageHandlers,
Subsystem::AttributeIndex,
Subsystem::StateCount
);
MechTech::MechTech(
Mech *entity,
int subsystem_id,
SubsystemResource *model,
SharedData &shared_data
):
Subsystem(entity, subsystem_id, model, shared_data),
subsystemMonitors(NULL)
{
Check(entity);
Check_Pointer(model);
Check_Fpu();
}
MechTech::~MechTech()
{
}
Logical
MechTech::TestInstance() const
{
return IsDerivedFrom(ClassDerivations);
}
Logical
MechTech::CreateStreamedSubsystem(
NotationFile *,
const char *,
const char *,
SubsystemResource *,
NotationFile *,
const ResourceDirectories *,
ResourceFile *
)
{
Fail("MechTech::CreateStreamedSubsystem -- mechtech.cpp not yet reconstructed");
return False;
}
//===========================================================================//
// File: mechtech.cpp //
// Project: BattleTech Brick: Mech subsystems //
// Contents: MechTech -- the on-board technician / repair subsystem //
//---------------------------------------------------------------------------//
// Copyright (C) 1995, Virtual World Entertainment, Inc. //
// All Rights reserved worldwide //
//===========================================================================//
#include <bt.hpp>
#pragma hdrstop
#if !defined(MECHTECH_HPP)
# include <mechtech.hpp>
#endif
#if !defined(MECH_HPP)
# include <mech.hpp>
#endif
Derivation
MechTech::ClassDerivations(Subsystem::ClassDerivations, "MechTech");
MechTech::SharedData
MechTech::DefaultData(
MechTech::ClassDerivations,
Subsystem::MessageHandlers,
Subsystem::AttributeIndex,
Subsystem::StateCount
);
MechTech::MechTech(
Mech *entity,
int subsystem_id,
SubsystemResource *model,
SharedData &shared_data
):
Subsystem(entity, subsystem_id, model, shared_data),
subsystemMonitors(NULL)
{
Check(entity);
Check_Pointer(model);
Check_Fpu();
}
MechTech::~MechTech()
{
}
Logical
MechTech::TestInstance() const
{
return IsDerivedFrom(ClassDerivations);
}
//
//#############################################################################
// CreateStreamedSubsystem -- binary @004ad48c. Stamps classID 0x0BDC / size
// 0x40 and zeroes the wooHoo trio; "AlarmModel" is OPTIONAL -- absent means
// no alarm; present, the named model family must resolve in the resource
// file ("<name> couldn't locate <name>.mod" otherwise). The surviving
// MECHTECH.HPP signature puts the ResourceFile LAST.
//#############################################################################
//
Logical
MechTech::CreateStreamedSubsystem(
NotationFile *model_file,
const char *model_name,
const char *subsystem_name,
SubsystemResource *subsystem_resource,
NotationFile *subsystem_file,
const ResourceDirectories *directories,
ResourceFile *resource_file
)
{
if (
!Subsystem::CreateStreamedSubsystem(
model_file, model_name, subsystem_name,
subsystem_resource, subsystem_file, directories
)
)
{
return False;
}
subsystem_resource->subsystemModelSize = 0x40;
subsystem_resource->classID = RegisteredClass::MechTechClassID;
subsystem_resource->alarmModel = (ResourceDescription::ResourceID)-1;
subsystem_resource->wooHooMinimumDuration = 0.0f;
subsystem_resource->wooHooDurationRange = 0.0f;
subsystem_resource->wooHooChance = 0.0f;
const char
*alarm_model_name = NULL;
if (!subsystem_file->GetEntry(subsystem_name, "AlarmModel", &alarm_model_name))
{
return True;
}
ResourceDescription
*description =
resource_file->FindResourceDescription(
alarm_model_name,
ResourceDescription::ModelListResourceType);
if (description == NULL)
{
DEBUG_STREAM << subsystem_name << " couldn't locate "
<< alarm_model_name << ".mod" << endl;
return False;
}
subsystem_resource->alarmModel = description->resourceID;
Check_Fpu();
return True;
}
+219 -10
View File
@@ -11,6 +11,10 @@
#include <bt.hpp>
#pragma hdrstop
#if !defined(COLOR_HPP)
# include <color.hpp> // RGBColor + Convert_From_Ascii (PipColor)
#endif
#if !defined(MECHWEAP_HPP)
# include <mechweap.hpp>
#endif
@@ -488,18 +492,223 @@ void
// CreateStreamedSubsystem Model-load-time construction. Not yet reconstructed.
//#############################################################################
//
//
//#############################################################################
// CreateStreamedSubsystem -- binary @004b9d10 (1868 bytes). The weapon
// record parser: chains the powered record, stamps size 0x1BC and the
// weapon-base classID (0x0BCD), primes the weapon fields on the first pass,
// then reads the ballistics/pip block. Required unless already set:
// DamageAmount, HeatCostToFire, PipPosition, PipColor, PipExtendedRange,
// RechargeRate, WeaponRange, DamageType, ExplosionModelFile. PipColor uses
// the engine's own RGB parser (Convert_From_Ascii -- the BT411 donor's
// sscanf was drift); the explosion resolves as a model FAMILY
// (ModelListResourceType -- the streamed id is a family id, the same truth
// the missile work established from the runtime side).
//#############################################################################
//
int
MechWeapon::CreateStreamedSubsystem(
ResourceFile *,
NotationFile *,
const char *,
const char *,
SubsystemResource *,
NotationFile *,
const ResourceDirectories *,
int
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
)
{
Fail("MechWeapon::CreateStreamedSubsystem -- mechweap.cpp not yet reconstructed");
return 0;
if (
!PoweredSubsystem::CreateStreamedSubsystem(
model_file, model_name, subsystem_name,
subsystem_resource, subsystem_file, directories, passes
)
)
{
return False;
}
subsystem_resource->subsystemModelSize = 0x1BC;
subsystem_resource->classID =
(RegisteredClass::ClassID)RegisteredClass::ProjectileWeaponClassID;
// ^ 0x0BCD -- the binary's weapon-base stamp; our enum spells the value
// ProjectileWeaponClassID. Every concrete leaf (Emitter, launcher...)
// overwrites it with its own id after this call.
if (passes == 1)
{
subsystem_resource->rechargeRate = -1.0f;
subsystem_resource->weaponRange = -1.0f;
subsystem_resource->explosionModelFile = (ResourceDescription::ResourceID)-1;
subsystem_resource->damageAmount = -1.0f;
subsystem_resource->heatCostToFire = -1.0f;
subsystem_resource->pipPosition = -1;
subsystem_resource->pipColor[0] = -1.0f;
subsystem_resource->pipColor[1] = -1.0f;
subsystem_resource->pipColor[2] = -1.0f;
subsystem_resource->pipExtendedRange = -1;
}
if (subsystem_resource->segmentIndex == -1 && passes > 1)
{
DEBUG_STREAM << subsystem_name << " missing SegmentPageName!" << endl;
return False;
}
if (
!subsystem_file->GetEntry(subsystem_name, "DamageAmount",
&subsystem_resource->damageAmount)
&& subsystem_resource->damageAmount == -1.0f
)
{
DEBUG_STREAM << model_name << " Missing DamageAmount" << endl;
return False;
}
if (
!subsystem_file->GetEntry(subsystem_name, "HeatCostToFire",
&subsystem_resource->heatCostToFire)
&& subsystem_resource->heatCostToFire == -1.0f
)
{
DEBUG_STREAM << subsystem_name << " missing HeatCostToFire !" << endl;
return False;
}
if (
!subsystem_file->GetEntry(subsystem_name, "PipPosition",
&subsystem_resource->pipPosition)
&& subsystem_resource->pipPosition == -1
)
{
DEBUG_STREAM << subsystem_name << " missing PipPosition!" << endl;
return False;
}
const char
*pip_color_name = "Unspecified";
if (
!subsystem_file->GetEntry(subsystem_name, "PipColor", &pip_color_name)
&& subsystem_resource->pipColor[0] == -1.0f
)
{
DEBUG_STREAM << subsystem_name << " missing PipColor!" << endl;
return False;
}
if (strcmp(pip_color_name, "Unspecified") != 0)
{
RGBColor
pip_color;
Convert_From_Ascii(pip_color_name, &pip_color);
subsystem_resource->pipColor[0] = pip_color.Red;
subsystem_resource->pipColor[1] = pip_color.Green;
subsystem_resource->pipColor[2] = pip_color.Blue;
}
if (
!subsystem_file->GetEntry(subsystem_name, "PipExtendedRange",
&subsystem_resource->pipExtendedRange)
&& subsystem_resource->pipExtendedRange == -1
)
{
DEBUG_STREAM << subsystem_name << " missing PipExtendedRange!" << endl;
return False;
}
if (
!subsystem_file->GetEntry(subsystem_name, "RechargeRate",
&subsystem_resource->rechargeRate)
&& subsystem_resource->rechargeRate == -1.0f
)
{
DEBUG_STREAM << model_name << " Missing RechargeRate" << endl;
return False;
}
if (
!subsystem_file->GetEntry(subsystem_name, "WeaponRange",
&subsystem_resource->weaponRange)
&& subsystem_resource->weaponRange == -1.0f
)
{
DEBUG_STREAM << model_name << " Missing WeaponRange" << endl;
return False;
}
const char
*damage_type_name = "Unspecified";
if (
!subsystem_file->GetEntry(subsystem_name, "DamageType", &damage_type_name)
&& subsystem_resource->damageAmount == -1.0f
)
{
DEBUG_STREAM << model_name << " missing DamageType" << endl;
return False;
}
if (strcmp(damage_type_name, "Unspecified") != 0)
{
if (strcmp(damage_type_name, "CollisionDamage") == 0)
{
subsystem_resource->damageType = Damage::CollisionDamageType;
}
else if (strcmp(damage_type_name, "BallisticDamage") == 0)
{
subsystem_resource->damageType = Damage::BallisticDamageType;
}
else if (strcmp(damage_type_name, "ExplosiveDamage") == 0)
{
subsystem_resource->damageType = Damage::ExplosiveDamageType;
}
else if (strcmp(damage_type_name, "LaserDamage") == 0)
{
subsystem_resource->damageType = Damage::LaserDamageType;
}
else if (strcmp(damage_type_name, "EnergyDamage") == 0)
{
subsystem_resource->damageType = Damage::EnergyDamageType;
}
else
{
DEBUG_STREAM << model_name << " has an unknown DamageType of "
<< damage_type_name << endl;
return False;
}
}
const char
*explosion_model_name = "Unspecified";
if (
!subsystem_file->GetEntry(subsystem_name, "ExplosionModelFile",
&explosion_model_name)
&& subsystem_resource->explosionModelFile
== (ResourceDescription::ResourceID)-1
)
{
DEBUG_STREAM << model_name << " missing ExplosionModelFile" << endl;
return False;
}
ResourceDescription
*explosion = NULL;
if (strcmp(explosion_model_name, "Unspecified") != 0)
{
explosion =
resource_file->FindResourceDescription(
explosion_model_name,
ResourceDescription::ModelListResourceType);
}
if (
explosion == NULL &&
subsystem_resource->explosionModelFile
== (ResourceDescription::ResourceID)-1
)
{
DEBUG_STREAM << model_name << " cannot find " << explosion_model_name
<< " in resource file!" << endl;
return False;
}
if (explosion != NULL)
{
subsystem_resource->explosionModelFile = explosion->resourceID;
}
Check_Fpu();
return True;
}
+66 -9
View File
@@ -388,17 +388,74 @@ void
Check_Fpu();
}
//
//#############################################################################
// CreateStreamedSubsystem -- binary @0049be10 (324 bytes). Chains the base
// Subsystem record, stamps classID 0x0BD3 / size 0x38, resolves the
// REQUIRED "TerrainHitExplosion" model family, reads the REQUIRED
// "RendererCompensateTime". (The runtime never consumes either field --
// see the inert terrain-hit note above -- but the authored stream carries
// both, so the converter writes both.)
//#############################################################################
//
Logical
SubsystemMessageManager::CreateStreamedSubsystem(
ResourceFile *,
NotationFile *,
const char *,
const char *,
SubsystemResource *,
NotationFile *,
const ResourceDirectories *
ResourceFile *resource_file,
NotationFile *model_file,
const char *model_name,
const char *subsystem_name,
SubsystemResource *subsystem_resource,
NotationFile *subsystem_file,
const ResourceDirectories *directories
)
{
Fail("SubsystemMessageManager::CreateStreamedSubsystem -- messmgr.cpp not yet reconstructed");
return False;
if (
!Subsystem::CreateStreamedSubsystem(
model_file, model_name, subsystem_name,
subsystem_resource, subsystem_file, directories
)
)
{
return False;
}
subsystem_resource->classID =
RegisteredClass::SubsystemMessageManagerClassID;
subsystem_resource->subsystemModelSize = 0x38;
const char
*terrain_hit_name;
if (
!subsystem_file->GetEntry(subsystem_name, "TerrainHitExplosion",
&terrain_hit_name)
)
{
DEBUG_STREAM << subsystem_name << " missing TerrainHitExplosion!" << endl;
return False;
}
ResourceDescription
*description =
resource_file->FindResourceDescription(
terrain_hit_name,
ResourceDescription::ModelListResourceType);
if (description == NULL)
{
DEBUG_STREAM << subsystem_name << " cannot find " << terrain_hit_name
<< " in resource file!" << endl;
return False;
}
subsystem_resource->terrainHitExplosionID = description->resourceID;
if (
!subsystem_file->GetEntry(subsystem_name, "RendererCompensateTime",
&subsystem_resource->rendererCompensateTime)
)
{
DEBUG_STREAM << subsystem_name << " missing RendererCompensateTime" << endl;
return False;
}
Check_Fpu();
return True;
}
+65 -10
View File
@@ -450,18 +450,73 @@ void
//
// Model-load-time construction. Not yet reconstructed.
//
//
//#############################################################################
// CreateStreamedSubsystem -- binary @004bd08c. Chains the projectile
// record, stamps the launcher's own classID and record size, then
// MissileCount and the "x y z" MuzzleVelocity vector (engine
// Convert_From_Ascii -- the donor's sscanf was drift).
//#############################################################################
//
Logical
MissileLauncher::CreateStreamedSubsystem(
ResourceFile *,
NotationFile *,
const char *,
const char *,
SubsystemResource *,
NotationFile *,
const ResourceDirectories *,
int
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
)
{
Fail("MissileLauncher::CreateStreamedSubsystem -- mislanch.cpp not yet reconstructed");
return False;
if (
!ProjectileWeapon::CreateStreamedSubsystem(
resource_file, model_file, model_name, subsystem_name,
subsystem_resource, subsystem_file, directories, passes
)
)
{
return False;
}
subsystem_resource->subsystemModelSize = sizeof(*subsystem_resource);
subsystem_resource->classID = RegisteredClass::MissileLauncherClassID;
if (passes == 1)
{
subsystem_resource->missileCount = -1;
subsystem_resource->muzzleVelocity = Vector3D::Identity;
}
if (
!subsystem_file->GetEntry(subsystem_name, "MissileCount",
&subsystem_resource->missileCount)
&& subsystem_resource->missileCount == -1
)
{
DEBUG_STREAM << subsystem_name << " missing MissileCount!" << endl;
return False;
}
const char
*muzzle_velocity_text = "Unspecified";
if (
!subsystem_file->GetEntry(subsystem_name, "MuzzleVelocity",
&muzzle_velocity_text)
&& subsystem_resource->muzzleVelocity == Vector3D::Identity
)
{
DEBUG_STREAM << subsystem_name << " missing MuzzleVelocity!" << endl;
return False;
}
if (strcmp(muzzle_velocity_text, "Unspecified") != 0)
{
Convert_From_Ascii(
muzzle_velocity_text, &subsystem_resource->muzzleVelocity);
}
Check_Fpu();
return True;
}
+133
View File
@@ -883,3 +883,136 @@ void
{
UpdateWatch();
}
//
//#############################################################################
// CreateStreamedSubsystem -- binary @004b13ac (declared in POWERSUB.HPP all
// along; the definition was the gap). Chains the shared heat-record parser
// (@004ae150 -- HeatSink adds no keys, so the chain call is Heatable's),
// stamps classID 0x0BC2 / size 0x190, then the electrical block: the
// REQUIRED "VoltageSource" subsystem link (+2 segment-table bias), the
// thermal resistivity, the aux/eng screen plumbing the cockpit reads
// (AuxScreenPlacement optional; AuxScreenNumber/labels required), and
// "StartTime".
//#############################################################################
//
int
PoweredSubsystem::CreateStreamedSubsystem(
NotationFile *model_file,
const char *model_name,
const char *subsystem_name,
SubsystemResource *subsystem_resource,
NotationFile *subsystem_file,
const ResourceDirectories *directories,
int passes
)
{
if (
!HeatableSubsystem::CreateStreamedSubsystem(
model_file, model_name, subsystem_name,
subsystem_resource, subsystem_file, directories, passes
)
)
{
return False;
}
subsystem_resource->subsystemModelSize = 0x190;
subsystem_resource->classID = RegisteredClass::PoweredSubsystemClassID;
if (passes == 1)
{
subsystem_resource->voltageSourceIndex = -1;
subsystem_resource->thermalResistivityCoefficient = -1.0f;
subsystem_resource->auxScreenNumber = -1;
subsystem_resource->auxScreenPlacement = -1;
memset(subsystem_resource->auxScreenLabel, 0,
sizeof(subsystem_resource->auxScreenLabel));
memset(subsystem_resource->engScreenLabel, 0,
sizeof(subsystem_resource->engScreenLabel));
subsystem_resource->startTime = -1.0f;
}
const char
*voltage_source_name = "Unspecified";
if (
!subsystem_file->GetEntry(subsystem_name, "VoltageSource",
&voltage_source_name)
&& subsystem_resource->voltageSourceIndex == -1
)
{
DEBUG_STREAM << subsystem_name << " missing VoltageSource!" << endl;
return False;
}
if (strcmp(voltage_source_name, "Unspecified") != 0)
{
subsystem_resource->voltageSourceIndex =
Find_Subsystem_Index(subsystem_file, voltage_source_name);
}
if (subsystem_resource->voltageSourceIndex < 0)
{
DEBUG_STREAM << subsystem_name << " has an invalid voltage source!" << endl;
return False;
}
if (strcmp(voltage_source_name, "Unspecified") != 0)
{
subsystem_resource->voltageSourceIndex += 2;
}
if (
!subsystem_file->GetEntry(subsystem_name, "ThermalResistivityCoefficient",
&subsystem_resource->thermalResistivityCoefficient)
&& subsystem_resource->thermalResistivityCoefficient == -1.0f
)
{
DEBUG_STREAM << subsystem_name << " missing ThermalResistivityCoefficient!" << endl;
return False;
}
subsystem_file->GetEntry(subsystem_name, "AuxScreenPlacement",
&subsystem_resource->auxScreenPlacement);
if (
!subsystem_file->GetEntry(subsystem_name, "AuxScreenNumber",
&subsystem_resource->auxScreenNumber)
&& subsystem_resource->auxScreenNumber == -1
)
{
DEBUG_STREAM << subsystem_name << " missing AuxScreenNumber!" << endl;
return False;
}
const char
*label;
if (subsystem_file->GetEntry(subsystem_name, "AuxScreenLabel", &label))
{
strcpy(subsystem_resource->auxScreenLabel, label);
}
else if (subsystem_resource->auxScreenLabel[0] == '\0')
{
DEBUG_STREAM << subsystem_name << " missing AuxScreenLabel!" << endl;
return False;
}
if (subsystem_file->GetEntry(subsystem_name, "EngScreenLabel", &label))
{
strcpy(subsystem_resource->engScreenLabel, label);
}
else if (subsystem_resource->engScreenLabel[0] == '\0')
{
DEBUG_STREAM << subsystem_name << " missing EngScreenLabel!" << endl;
return False;
}
if (
!subsystem_file->GetEntry(subsystem_name, "StartTime",
&subsystem_resource->startTime)
&& subsystem_resource->startTime == -1.0f
)
{
DEBUG_STREAM << subsystem_name << " missing StartTime!" << endl;
return False;
}
Check_Fpu();
return True;
}
+110
View File
@@ -483,3 +483,113 @@ void
Check_Fpu();
}
//
//#############################################################################
// CreateStreamedSubsystem -- binary @004bc7cc (declared in PROJWEAP.HPP all
// along; the definition was the gap). Chains the weapon record, keeps the
// weapon-base classID (the concrete launcher re-stamps after), size 0x1D0,
// then the projectile block: TracerInterval, the REQUIRED "AmmoBin"
// subsystem link (+2 segment-table bias), MinTimeOfFlight, MinJamChance,
// MinVoltagePercentToFire.
//#############################################################################
//
int
ProjectileWeapon::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
)
{
if (
!MechWeapon::CreateStreamedSubsystem(
resource_file, model_file, model_name, subsystem_name,
subsystem_resource, subsystem_file, directories, passes
)
)
{
return False;
}
subsystem_resource->subsystemModelSize = 0x1D0;
if (passes == 1)
{
subsystem_resource->tracerInterval = -1;
subsystem_resource->ammoBinIndex = -1;
subsystem_resource->minTimeOfFlight = -1.0f;
subsystem_resource->minVoltagePercentToFire = -1.0f;
subsystem_resource->minJamChance = -1.0f;
}
if (
!subsystem_file->GetEntry(subsystem_name, "TracerInterval",
&subsystem_resource->tracerInterval)
&& subsystem_resource->tracerInterval == -1
)
{
DEBUG_STREAM << subsystem_name << " missing TracerInterval!" << endl;
return False;
}
const char
*ammo_bin_name = "Unspecified";
if (
!subsystem_file->GetEntry(subsystem_name, "AmmoBin", &ammo_bin_name)
&& subsystem_resource->ammoBinIndex == -1
)
{
DEBUG_STREAM << subsystem_name << " missing AmmoBin!" << endl;
return False;
}
if (strcmp(ammo_bin_name, "Unspecified") != 0)
{
subsystem_resource->ammoBinIndex =
Find_Subsystem_Index(subsystem_file, ammo_bin_name);
}
if (subsystem_resource->ammoBinIndex < 0)
{
DEBUG_STREAM << subsystem_name << " has an invalid Ammo Bin!" << endl;
return False;
}
if (strcmp(ammo_bin_name, "Unspecified") != 0)
{
subsystem_resource->ammoBinIndex += 2;
}
if (
!subsystem_file->GetEntry(subsystem_name, "MinTimeOfFlight",
&subsystem_resource->minTimeOfFlight)
&& subsystem_resource->minTimeOfFlight == -1.0f
)
{
DEBUG_STREAM << subsystem_name << " missing MinTimeOfFlight !" << endl;
return False;
}
if (
!subsystem_file->GetEntry(subsystem_name, "MinJamChance",
&subsystem_resource->minJamChance)
&& subsystem_resource->minJamChance == -1.0f
)
{
DEBUG_STREAM << subsystem_name << " missing MinJamChance !" << endl;
return False;
}
if (
!subsystem_file->GetEntry(subsystem_name, "MinVoltagePercentToFire",
&subsystem_resource->minVoltagePercentToFire)
&& subsystem_resource->minVoltagePercentToFire == -1.0f
)
{
DEBUG_STREAM << subsystem_name << " missing MinVoltagePercentToFire !" << endl;
return False;
}
Check_Fpu();
return True;
}
+142 -126
View File
@@ -1,126 +1,142 @@
//===========================================================================//
// File: projweap.hpp //
// Project: BattleTech Brick: Mech weapons //
// Contents: ProjectileWeapon -- the ballistic (ammo-fed) weapon base //
//---------------------------------------------------------------------------//
// Copyright (C) 1995, Virtual World Entertainment, Inc. //
// All Rights reserved worldwide //
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL //
//===========================================================================//
#if !defined(PROJWEAP_HPP)
# define PROJWEAP_HPP
# if !defined(MECHWEAP_HPP)
# include <mechweap.hpp>
# endif
# if !defined(POINT3D_HPP)
# include <point3d.hpp>
# endif
//##################### Forward Class Declarations #######################
class Mech;
//###########################################################################
//################ ProjectileWeapon Model Resource #####################
//###########################################################################
//
// WIRE-VERIFIED layout (raw-stream dump, TEST.EGG SRM6s): tracerInterval
// reads 1, ammoBinIndex the true bin roster slot (27/29), minTimeOfFlight
// 0.5, minVoltagePercentToFire 0.3, minJamChance 0.05, and
// MissileLauncher's missileCount lands on 6 (an SRM6!). (The interim
// alignment pad is gone -- the ancestry shortfall was the HeatSink
// linkedSinkIndex + the MechWeapon pip tail, both now real fields.)
//
struct ProjectileWeapon__SubsystemResource:
public MechWeapon::SubsystemResource
{
int tracerInterval;
int ammoBinIndex;
Scalar minTimeOfFlight;
Scalar minVoltagePercentToFire;
Scalar minJamChance;
};
//###########################################################################
//######################### ProjectileWeapon ###########################
//###########################################################################
class ProjectileWeapon:
public MechWeapon
{
public:
static Derivation ClassDerivations;
static SharedData DefaultData;
static Logical
TestClass(Mech &);
Logical
TestInstance() const;
virtual void
FireWeapon();
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Per-frame simulation (the ballistic fire state machine).
//
public:
typedef void
(ProjectileWeapon::*Performance)(Scalar time_slice);
void
SetPerformance(Performance performance)
{
Check(this);
activePerformance = (Simulation::Performance)performance;
}
void
ProjectileWeaponSimulation(Scalar time_slice);
Logical
CheckForJam();
//
// The "sim live" novice lockout (the owning BTPlayer's simLive
// experience flag): novice ballistics never jam.
//
Logical
LiveFireEnabled();
public:
typedef ProjectileWeapon__SubsystemResource SubsystemResource;
ProjectileWeapon(
Mech *owner,
int subsystem_ID,
SubsystemResource *subsystem_resource,
SharedData &shared_data = DefaultData
);
~ProjectileWeapon();
//
// The linked bin's round count, or -1 when no bin is linked. The
// cockpit's ammo readout needs this from BT_L4 and the link itself
// is protected (the binary read the resolved bin's count direct).
//
int
GetAmmoCount();
protected:
SubsystemConnection ammoBinLink;
int tracerInterval;
int tracerCounter;
int ejectState;
Scalar totalTimeToEject;
Scalar timeToEject;
Scalar percentOfEject;
Scalar minJamChance;
Scalar minTimeOfFlight;
Scalar minVoltagePercentToFire;
Vector3D launchVelocity;
Vector3D tracerOrigin;
Point3D leadPosition;
int tracerModelHandle;
};
#endif
//===========================================================================//
// File: projweap.hpp //
// Project: BattleTech Brick: Mech weapons //
// Contents: ProjectileWeapon -- the ballistic (ammo-fed) weapon base //
//---------------------------------------------------------------------------//
// Copyright (C) 1995, Virtual World Entertainment, Inc. //
// All Rights reserved worldwide //
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL //
//===========================================================================//
#if !defined(PROJWEAP_HPP)
# define PROJWEAP_HPP
# if !defined(MECHWEAP_HPP)
# include <mechweap.hpp>
# endif
# if !defined(POINT3D_HPP)
# include <point3d.hpp>
# endif
//##################### Forward Class Declarations #######################
class Mech;
//###########################################################################
//################ ProjectileWeapon Model Resource #####################
//###########################################################################
//
// WIRE-VERIFIED layout (raw-stream dump, TEST.EGG SRM6s): tracerInterval
// reads 1, ammoBinIndex the true bin roster slot (27/29), minTimeOfFlight
// 0.5, minVoltagePercentToFire 0.3, minJamChance 0.05, and
// MissileLauncher's missileCount lands on 6 (an SRM6!). (The interim
// alignment pad is gone -- the ancestry shortfall was the HeatSink
// linkedSinkIndex + the MechWeapon pip tail, both now real fields.)
//
struct ProjectileWeapon__SubsystemResource:
public MechWeapon::SubsystemResource
{
int tracerInterval;
int ammoBinIndex;
Scalar minTimeOfFlight;
Scalar minVoltagePercentToFire;
Scalar minJamChance;
};
//###########################################################################
//######################### ProjectileWeapon ###########################
//###########################################################################
class ProjectileWeapon:
public MechWeapon
{
public:
static Derivation ClassDerivations;
static SharedData DefaultData;
static Logical
TestClass(Mech &);
Logical
TestInstance() const;
virtual void
FireWeapon();
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Per-frame simulation (the ballistic fire state machine).
//
public:
typedef void
(ProjectileWeapon::*Performance)(Scalar time_slice);
void
SetPerformance(Performance performance)
{
Check(this);
activePerformance = (Simulation::Performance)performance;
}
void
ProjectileWeaponSimulation(Scalar time_slice);
Logical
CheckForJam();
//
// The "sim live" novice lockout (the owning BTPlayer's simLive
// experience flag): novice ballistics never jam.
//
Logical
LiveFireEnabled();
public:
typedef ProjectileWeapon__SubsystemResource SubsystemResource;
//
// The authoring-side stream converter (binary @004bc7cc); defined in
// projweap.cpp.
//
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
);
ProjectileWeapon(
Mech *owner,
int subsystem_ID,
SubsystemResource *subsystem_resource,
SharedData &shared_data = DefaultData
);
~ProjectileWeapon();
//
// The linked bin's round count, or -1 when no bin is linked. The
// cockpit's ammo readout needs this from BT_L4 and the link itself
// is protected (the binary read the resolved bin's count direct).
//
int
GetAmmoCount();
protected:
SubsystemConnection ammoBinLink;
int tracerInterval;
int tracerCounter;
int ejectState;
Scalar totalTimeToEject;
Scalar timeToEject;
Scalar percentOfEject;
Scalar minJamChance;
Scalar minTimeOfFlight;
Scalar minVoltagePercentToFire;
Vector3D launchVelocity;
Vector3D tracerOrigin;
Point3D leadPosition;
int tracerModelHandle;
};
#endif
+30 -9
View File
@@ -238,17 +238,38 @@ void
// (see MECHSUB.NOTES.md -- the 4.10 subsystem stream format).
//#############################################################################
//
//
//#############################################################################
// CreateStreamedSubsystem -- binary @004b1dcc: chain the powered-record
// parser, stamp classID 0x0BC3 / size 0x190. The Sensor record is an empty
// extension of the PoweredSubsystem record.
//#############################################################################
//
int
Sensor::CreateStreamedSubsystem(
NotationFile *,
const char *,
const char *,
SubsystemResource *,
NotationFile *,
const ResourceDirectories *,
int
NotationFile *model_file,
const char *model_name,
const char *subsystem_name,
SubsystemResource *subsystem_resource,
NotationFile *subsystem_file,
const ResourceDirectories *directories,
int passes
)
{
Fail("Sensor::CreateStreamedSubsystem -- sensor.cpp not yet reconstructed");
return 0;
if (
!PoweredSubsystem::CreateStreamedSubsystem(
model_file, model_name, subsystem_name,
subsystem_resource, subsystem_file, directories, passes
)
)
{
return False;
}
subsystem_resource->subsystemModelSize = 0x190;
subsystem_resource->classID = RegisteredClass::SensorClassID;
Check_Fpu();
return True;
}