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>
715 lines
22 KiB
C++
715 lines
22 KiB
C++
//===========================================================================//
|
|
// File: mechweap.cpp //
|
|
// Project: BattleTech Brick: Mech weapons //
|
|
// Contents: MechWeapon -- the base weapon 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(COLOR_HPP)
|
|
# include <color.hpp> // RGBColor + Convert_From_Ascii (PipColor)
|
|
#endif
|
|
|
|
#if !defined(MECHWEAP_HPP)
|
|
# include <mechweap.hpp>
|
|
#endif
|
|
|
|
#if !defined(MECH_HPP)
|
|
# include <mech.hpp>
|
|
#endif
|
|
|
|
#if !defined(SEGMENT_HPP)
|
|
# include <segment.hpp>
|
|
#endif
|
|
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
|
|
#if !defined(RANDOM_HPP)
|
|
# include <random.hpp>
|
|
#endif
|
|
|
|
#if !defined(BTPLAYER_HPP)
|
|
# include <btplayer.hpp>
|
|
#endif
|
|
|
|
Derivation
|
|
MechWeapon::ClassDerivations(
|
|
PoweredSubsystem::ClassDerivations,
|
|
"MechWeapon"
|
|
);
|
|
|
|
//
|
|
//#############################################################################
|
|
// Message handlers (binary ids 9/10 @004b9550/@004b95b8) -- the weapon-group
|
|
// config buttons, chained onto the PoweredSubsystem generator panel (4-8),
|
|
// which chains HeatSink (3): a weapon's dispatch reaches the whole cockpit
|
|
// column. (The set MUST be a real defined object: the surviving CODE
|
|
// PPC.CPP binds the inherited name into PPC::DefaultData, and a
|
|
// declared-but-undefined static links as a zero-filled common block.)
|
|
//#############################################################################
|
|
//
|
|
const MechWeapon::HandlerEntry
|
|
MechWeapon::MessageHandlerEntries[] =
|
|
{
|
|
MESSAGE_ENTRY(MechWeapon, ConfigureMappables), // id 9 @004b9550
|
|
MESSAGE_ENTRY(MechWeapon, ChooseButton) // id 10 @004b95b8
|
|
};
|
|
|
|
MechWeapon::MessageHandlerSet
|
|
MechWeapon::MessageHandlers(
|
|
ELEMENTS(MechWeapon::MessageHandlerEntries),
|
|
MechWeapon::MessageHandlerEntries,
|
|
PoweredSubsystem::MessageHandlers
|
|
);
|
|
|
|
//
|
|
//#############################################################################
|
|
// Attribute Support -- the 1995 binary table (@0x511890), IDs pinned so the
|
|
// streamed per-mech control mappings resolve: TriggerState (0x13) is what the
|
|
// authored fire buttons bind (the controls push then writes the trigger state
|
|
// straight into fireImpulse each frame). The pads bridge our chain gap
|
|
// 2..0x11 (parents publish nothing yet) -- REQUIRED, because Build leaves
|
|
// uncovered gap slots uninitialized; they target rechargeLevel, harmless if a
|
|
// stray mapping ever binds one (BT411 shipped the same). Pip*/WeaponRange
|
|
// pads-to-rechargeLevel where the member isn't reconstructed yet are noted.
|
|
//#############################################################################
|
|
//
|
|
#define MECHWEAP_PAD(n, name) \
|
|
{ (int)MechWeapon::MechWeaponPadFirstAttributeID + n, name, \
|
|
(Simulation::AttributePointer)&MechWeapon::rechargeLevel }
|
|
|
|
const MechWeapon::IndexEntry
|
|
MechWeapon::AttributePointers[]=
|
|
{
|
|
//
|
|
// TWO pads now, not three: HeatSink grew ReportLeak, so
|
|
// PoweredSubsystem::NextAttributeID moved 0x0F -> 0x10 and the bridge
|
|
// to the binary-pinned PercentDone (0x12) is one slot shorter.
|
|
//
|
|
MECHWEAP_PAD( 0, "MechWeaponPad11"),
|
|
ATTRIBUTE_ENTRY(MechWeapon, PercentDone, rechargeLevel), // 0x12
|
|
ATTRIBUTE_ENTRY(MechWeapon, TriggerState, fireImpulse), // 0x13
|
|
ATTRIBUTE_ENTRY(MechWeapon, DistanceToTarget, rangeToTarget), // 0x14
|
|
ATTRIBUTE_ENTRY(MechWeapon, TargetWithinRange, targetWithinRange), // 0x15
|
|
ATTRIBUTE_ENTRY(MechWeapon, WeaponRange, effectiveRange), // 0x16
|
|
{ (int)MechWeapon::PipPositionAttributeID, "PipPosition", // 0x17 (pip
|
|
(Simulation::AttributePointer)&MechWeapon::rechargeLevel }, // members
|
|
{ (int)MechWeapon::PipColorAttributeID, "PipColor", // 0x18 not yet
|
|
(Simulation::AttributePointer)&MechWeapon::rechargeLevel }, // recon-
|
|
{ (int)MechWeapon::PipExtendedRangeAttributeID, "PipExtendedRange", // 0x19 structed)
|
|
(Simulation::AttributePointer)&MechWeapon::rechargeLevel },
|
|
ATTRIBUTE_ENTRY(MechWeapon, EstimatedReadyTime, estimatedReadyTime),// 0x1A
|
|
ATTRIBUTE_ENTRY(MechWeapon, RearFiring, rearFiring), // 0x1B
|
|
ATTRIBUTE_ENTRY(MechWeapon, WeaponState, weaponAlarm) // 0x1C
|
|
};
|
|
|
|
MechWeapon::AttributeIndexSet
|
|
MechWeapon::AttributeIndex(
|
|
ELEMENTS(MechWeapon::AttributePointers),
|
|
MechWeapon::AttributePointers,
|
|
PoweredSubsystem::AttributeIndex
|
|
);
|
|
|
|
MechWeapon::SharedData
|
|
MechWeapon::DefaultData(
|
|
MechWeapon::ClassDerivations,
|
|
MechWeapon::MessageHandlers,
|
|
MechWeapon::AttributeIndex,
|
|
Subsystem::StateCount
|
|
);
|
|
|
|
MechWeapon::MechWeapon(
|
|
Mech *owner,
|
|
int subsystem_ID,
|
|
SubsystemResource *subsystem_resource,
|
|
SharedData &shared_data
|
|
):
|
|
PoweredSubsystem(owner, subsystem_ID, subsystem_resource, shared_data)
|
|
{
|
|
Check(owner);
|
|
Check_Pointer(subsystem_resource);
|
|
|
|
rechargeRate = subsystem_resource->rechargeRate;
|
|
weaponRange = subsystem_resource->weaponRange;
|
|
damageAmount = subsystem_resource->damageAmount;
|
|
damageType = subsystem_resource->damageType;
|
|
heatCostToFire = subsystem_resource->heatCostToFire;
|
|
explosionResourceID = subsystem_resource->explosionModelFile;
|
|
targetWithinRange = False;
|
|
|
|
damageData.damageType = damageType;
|
|
damageData.damageAmount = damageAmount;
|
|
|
|
//
|
|
// Fire-machine state: spawn fully charged and Loaded (ready to fire).
|
|
//
|
|
fireImpulse = 0.0f;
|
|
previousFireImpulse = 0.0f;
|
|
rechargeLevel = 1.0f;
|
|
rangeToTarget = 0.0f;
|
|
effectiveRange = weaponRange;
|
|
estimatedReadyTime = 0;
|
|
recoil = 0.0f;
|
|
weaponAlarm.Initialize(WeaponStateCount);
|
|
weaponAlarm.SetLevel(LoadedState);
|
|
|
|
//
|
|
// REAR-FIRING resolve (the binary ctor tail @004b99a8): a weapon is
|
|
// rear-mounted when its MOUNT SEGMENT's site name carries the 'b' (back)
|
|
// marker -- the back gun ports (sitelbgunport / siterbgunport) are the only
|
|
// port names containing it. Rear-mounted weapons fire only into the
|
|
// LOOK-BACK view; everything else fires forward. The look-state commit
|
|
// (Mech::CommitLookState) re-arms viewFireEnable on every view change; a
|
|
// weapon spawns armed for the forward view.
|
|
//
|
|
rearFiring = False;
|
|
{
|
|
EntitySegment *mount = owner->GetSegment(GetSegmentIndex());
|
|
if (mount != NULL)
|
|
{
|
|
const char *mount_name = mount->GetName();
|
|
if (mount_name != NULL && strstr(mount_name, "b") != NULL)
|
|
{
|
|
rearFiring = True;
|
|
}
|
|
}
|
|
}
|
|
viewFireEnable = (rearFiring == False);
|
|
|
|
if (getenv("BT_MECH_LOG"))
|
|
{
|
|
EntitySegment *mount = owner->GetSegment(GetSegmentIndex());
|
|
DEBUG_STREAM << "[weap] '" << GetName()
|
|
<< "' seg=" << GetSegmentIndex() << " mount=";
|
|
if (mount != NULL)
|
|
{
|
|
DEBUG_STREAM << mount->GetName();
|
|
}
|
|
else
|
|
{
|
|
DEBUG_STREAM << "<none>";
|
|
}
|
|
DEBUG_STREAM << " rear=" << (int)rearFiring << endl << flush;
|
|
}
|
|
|
|
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
|
|
// (@004b9550/@004b95b8) drive the group-assign flow with the HUD config
|
|
// page; until that page exists the press is latched (configureActivePress)
|
|
// and logged -- the dispatch path itself is the live, authentic part.
|
|
//#############################################################################
|
|
//
|
|
void
|
|
MechWeapon::ConfigureMappablesMessageHandler(
|
|
ReceiverDataMessageOf<int> *message)
|
|
{
|
|
Check(this);
|
|
configureActivePress = message->dataContents;
|
|
if (getenv("BT_MECH_LOG"))
|
|
{
|
|
DEBUG_STREAM << "[btn] '" << GetName()
|
|
<< "' ConfigureMappables press=" << message->dataContents
|
|
<< " (staged latch)" << endl << flush;
|
|
}
|
|
}
|
|
|
|
void
|
|
MechWeapon::ChooseButtonMessageHandler(
|
|
ReceiverDataMessageOf<int> *message)
|
|
{
|
|
Check(this);
|
|
if (getenv("BT_MECH_LOG"))
|
|
{
|
|
DEBUG_STREAM << "[btn] '" << GetName()
|
|
<< "' ChooseButton press=" << message->dataContents
|
|
<< " (staged)" << endl << flush;
|
|
}
|
|
}
|
|
|
|
MechWeapon::~MechWeapon()
|
|
{
|
|
}
|
|
|
|
Logical
|
|
MechWeapon::TestClass(Mech &)
|
|
{
|
|
return True;
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// CheckFireEdge -- the rising-edge detector on fireImpulse (binary @004b9608).
|
|
// TriggerState (attr 0x13) carries the raw ControlsButton INT bit-copied by the
|
|
// streamed direct mapping (+(button+1) press / -(button+1) release), so the
|
|
// release value reads as a negative NaN in float terms; the binary's x87
|
|
// compare effectively treated it as "released". Comparing the BIT PATTERNS as
|
|
// signed ints is sign-correct for the button ints AND for genuine float values
|
|
// (0.0f = 0x0, positives > 0, negatives/NaNs < 0), reproducing the binary's
|
|
// behavior exactly.
|
|
//#############################################################################
|
|
//
|
|
Logical
|
|
MechWeapon::CheckFireEdge()
|
|
{
|
|
Check(this);
|
|
|
|
int current = *(int *)&fireImpulse;
|
|
int previous = *(int *)&previousFireImpulse;
|
|
Logical edge = (current > 0 && previous <= 0) ? True : False;
|
|
previousFireImpulse = fireImpulse;
|
|
return edge;
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// ComputeOutputVoltage -- the recharge-dial writer (binary @004b9c9c, vtable
|
|
// slot the Emitter overrides with its own voltage form):
|
|
// rechargeLevel = (rechargeRate - recoil) / rechargeRate
|
|
// The Loading tick decays recoil from rechargeRate toward 0, so the dial rises
|
|
// 0 -> 1 over the authored RechargeRate seconds.
|
|
//#############################################################################
|
|
//
|
|
void
|
|
MechWeapon::ComputeOutputVoltage()
|
|
{
|
|
Check(this);
|
|
|
|
rechargeLevel = (rechargeRate - recoil) / rechargeRate;
|
|
}
|
|
|
|
Logical
|
|
MechWeapon::TestInstance() const
|
|
{
|
|
return IsDerivedFrom(ClassDerivations);
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// FireWeapon Abstract -- each concrete weapon (Emitter/PPC, ProjectileWeapon/
|
|
// Gauss/MissileLauncher) overrides this. Reaching the base is a bug.
|
|
//#############################################################################
|
|
//
|
|
void
|
|
MechWeapon::FireWeapon()
|
|
{
|
|
Fail("MechWeapon::FireWeapon -- should not be here (abstract)");
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// GetMuzzlePoint (binary @004b9948) -- the mount segment's world-frame
|
|
// position: segmentToEntity x localToWorld (GetSegmentToWorld), translation
|
|
// extracted by transforming the segment-space origin. This is what makes
|
|
// fire leave FROM THE GUN and follow the torso twist.
|
|
//#############################################################################
|
|
//
|
|
void
|
|
MechWeapon::GetMuzzlePoint(Point3D &point)
|
|
{
|
|
Check(this);
|
|
|
|
point = owner->localOrigin.linearPosition;
|
|
|
|
if (GetSegmentIndex() >= 0)
|
|
{
|
|
EntitySegment *segment = owner->GetSegment(GetSegmentIndex());
|
|
if (segment != NULL)
|
|
{
|
|
LinearMatrix to_world;
|
|
owner->GetSegmentToWorld(*segment, &to_world);
|
|
point.Multiply(Point3D(0.0f, 0.0f, 0.0f), to_world);
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// HasActiveTarget -- the owner mech's target slot (binary owner+0x388).
|
|
//#############################################################################
|
|
//
|
|
Logical
|
|
MechWeapon::HasActiveTarget()
|
|
{
|
|
Check(this);
|
|
return (owner != NULL && owner->GetTargetEntity() != NULL)
|
|
? True : False;
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// UpdateTargeting -- refresh the range to the current target and the
|
|
// within-range flag (binary @004b9bdc). effectiveRange is the (heat-degraded,
|
|
// later) weapon range.
|
|
//#############################################################################
|
|
//
|
|
Logical
|
|
MechWeapon::UpdateTargeting()
|
|
{
|
|
Check(this);
|
|
|
|
Entity *target = (owner != NULL) ? owner->GetTargetEntity() : NULL;
|
|
if (target == NULL)
|
|
{
|
|
targetWithinRange = False;
|
|
return False;
|
|
}
|
|
|
|
Vector3D span;
|
|
span.Subtract(
|
|
target->localOrigin.linearPosition,
|
|
owner->localOrigin.linearPosition
|
|
);
|
|
rangeToTarget = span.Length();
|
|
targetWithinRange = (rangeToTarget <= effectiveRange) ? True : False;
|
|
return targetWithinRange;
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// SendDamage Deliver this weapon's damage to the struck entity: build the
|
|
// engine TakeDamageMessage (inflictor = our mech) and dispatch it AT the
|
|
// target, whose handler routes damageZones[zone]->TakeDamage with the
|
|
// authored per-type scales. INTERIM zone pick: a uniform roll over the
|
|
// target's hull zones -- the authentic resolver is the reticle hit (aimed) or
|
|
// the cylinder hit-location table (unaimed), both in the render/mech3 wave.
|
|
//#############################################################################
|
|
//
|
|
void
|
|
MechWeapon::SendDamage(Entity *target, Damage &damage)
|
|
{
|
|
Check(this);
|
|
Check(target);
|
|
|
|
int zone = -1;
|
|
if (target->damageZoneCount > 0)
|
|
{
|
|
zone = Random(target->damageZoneCount);
|
|
|
|
//
|
|
// DEV hook BT_FORCE_ZONE=n: pin every hit to zone n (concentrated
|
|
// fire -- exercises the zone-death cascade headlessly).
|
|
//
|
|
{
|
|
const char *force_zone = getenv("BT_FORCE_ZONE");
|
|
if (force_zone != NULL)
|
|
{
|
|
int forced = atoi(force_zone);
|
|
if (forced >= 0 && forced < target->damageZoneCount)
|
|
{
|
|
zone = forced;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Entity::TakeDamageMessage
|
|
hit(
|
|
Entity::TakeDamageMessageID,
|
|
sizeof(Entity::TakeDamageMessage),
|
|
(owner != NULL) ? owner->GetEntityID() : EntityID::Null,
|
|
zone,
|
|
damage
|
|
);
|
|
target->Dispatch(&hit);
|
|
|
|
//
|
|
// The DAMAGE SCORE: post the type-0 ScoreInflicted to OUR pilot (the
|
|
// authentic producer sits in the unexported master-perf writer; the
|
|
// port shape posts per delivered hit). senderMechID = the VICTIM.
|
|
//
|
|
if (owner != NULL && owner->GetInstance() != Entity::ReplicantInstance
|
|
&& owner->GetPlayerLink() != NULL)
|
|
{
|
|
BTPlayer::ScoreMessage
|
|
damage_score(
|
|
BTPlayer::ScoreInflictedMessageID,
|
|
sizeof(BTPlayer::ScoreMessage),
|
|
0.0f,
|
|
BTPlayer::DamageInflictedScore,
|
|
damage.damageAmount,
|
|
target->GetEntityID()
|
|
);
|
|
owner->GetPlayerLink()->Dispatch(&damage_score);
|
|
}
|
|
|
|
if (getenv("BT_MECH_LOG"))
|
|
{
|
|
DEBUG_STREAM << "[dmg] '" << GetName()
|
|
<< "' hit zone " << zone
|
|
<< " amount=" << damage.damageAmount
|
|
<< " type=" << damage.damageType;
|
|
if (zone >= 0 && zone < target->damageZoneCount
|
|
&& target->damageZones != NULL
|
|
&& target->damageZones[zone] != NULL)
|
|
{
|
|
DEBUG_STREAM << " -> zoneLevel="
|
|
<< target->damageZones[zone]->damageLevel;
|
|
}
|
|
DEBUG_STREAM << endl << flush;
|
|
}
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// 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 *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 (
|
|
!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;
|
|
}
|