Files
BT412/game/reconstructed/mechweap.cpp
T
arcattackandClaude Opus 4.8 9d82be46a1 P7: subsystem-tree alarm unification -- the whole PoweredSubsystem weapon/power subtree is byte-exact
The reconstruction modeled the binary's 0x54 AlarmIndicator (FUN_0041b9ec) with undersized
stand-ins (AlarmIndicator==ReconAlarm==4B; HeatAlarm==8B) across the entire subsystem tree, so
every field above an alarm sat at the wrong compiled offset.  Retype every such stand-in to
GaugeAlarm54(0x54) and de-phantom each class against its ctor, so the whole PoweredSubsystem
subtree becomes byte-exact.  An 8-agent read-only decomp-mapping workflow decoded every ctor
first; then hands-on implementation.  static_assert-locked chain (verified vs the raw ctors):

  HeatSink 0x1D0
   -> PoweredSubsystem 0x31C   retype electricalStateAlarm@0x264 + modeAlarm@0x2B8   @004b0f74
   -> MechWeapon      0x3F0   retype weaponAlarm@0x350; delete 5 phantom tail fields  @004b99a8
   -> Emitter         0x478   delete outputVoltage/beamLengthRatio/firingArmed aliases
                              + beamHit*/beamColor/beamHitData/energyRampTime phantoms;
                              retype beamOrientation EulerAngles->Quaternion(16B)       @004bb120
   -> PPC             0x478   (no own fields)                                           @004bb888
  PoweredSubsystem -> Sensor  0x328   (no alarm; 3 own fields)                          @004b1d18
  PoweredSubsystem -> Myomers 0x358   (no alarm)                                        @004b8fec

New systemic bug-class instances fixed (added to the CLAUDE.md checklist):
  * alias field    - Emitter outputVoltage==inherited rechargeLevel@0x320; beamLengthRatio==
                     beamScale.z@0x434; firingArmed==inherited useConfiguredPip@0x3E0
  * phantom field  - MechWeapon segmentReference/pipSegment/hasTarget/targetPoint/muzzlePoint
                     (past 0x3F0); Emitter beamHitPoint/beamImpact/beamImpactScalar/beamColor/
                     beamHitData/energyRampTime (binary writes inherited damageData/voltageScale
                     or the value is a method local)

Non-layout fixes required in the same wave:
  * outputVoltage->rechargeLevel also in the compiled GAUSS.CPP:74/93 (external readers of the
    removed Emitter field -- must grep EVERY TU, not just the class's own .cpp)
  * MechWeapon::GetMuzzlePoint reimplemented faithfully (removed muzzlePoint collided with
    Emitter's own fields at 0x3F0) via a BTResolveWeaponMuzzle void* bridge in mech4.cpp,
    resolving the weapon's mount segment (inherited this+0xdc) through the owner segment table
  * DetachFromVoltageSource fixed to set electricalStateAlarm not modeAlarm (raw @004b0e30
    writes the 0x264 alarm)

The vehicleSubSystems aux-screen gauge raw reads (btl4gau2.cpp:868/952 at subsystem+0x2b8/+0x278)
and the Sensor RadarPercent path now read the CORRECT byte offsets (garbage under the short layout).

Verified: combat DESTROYED-in-8, 28 shots, 0 crashes, heat heatEnergy=1.34e7, every static_assert
lock passes, heapcheck-clean through construction (the phase the isolated PoweredSubsystem retype
had overflowed).  LESSON: a factory-bridge runtime Check(sizeof<=alloc) does NOT fail the build --
only a static_assert sizeof lock catches alloc overflow at compile time.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 11:49:58 -05:00

650 lines
23 KiB
C++

//===========================================================================//
// File: mechweap.cpp //
// Project: BattleTech Brick: Entity Manager //
// Contents: MechWeapon -- abstract base for every mounted Mech weapon //
//---------------------------------------------------------------------------//
// Date Who Modification //
// -------- --- ---------------------------------------------------------- //
// --/--/95 JM Initial coding. //
//---------------------------------------------------------------------------//
// Copyright (C) 1995, Virtual World Entertainment, Inc. All Rights reserved //
// PROPRIETARY AND CONFIDENTIAL //
//===========================================================================//
//
// RECONSTRUCTED from the shipped binary. Behaviour follows the Ghidra
// pseudo-C for the module cluster @004b95ec-@004b9d10 (file=bt/mechweap.cpp)
// plus the pure-virtual stub @004ba45c (file=bt/mechweap.hpp). Method and
// member names follow the surviving derived sources (PPC.CPP, GAUSS.CPP,
// MISLANCH.HPP), the resource-parser tag strings, and the RP WEAPSYS.h analog.
// Each non-trivial method cites the originating @ADDR.
//
// Resolved read-only constants (4 LE bytes from section_dump CODE):
// _DAT_004b9648 = 00 00 00 00 = 0.0f (fire-edge threshold)
// _DAT_004b9b98 = 00 00 80 bf = -1.0f (default pip-colour component)
// _DAT_004b9c98 = 00 00 80 3f = 1.0f (full-heat reference)
// _DAT_004ba37c = 00 00 80 bf = -1.0f (resource "unset" sentinel)
//
// Helper-function name mapping (engine internals referenced by the decomp):
// FUN_004b0f74 PoweredSubsystem base constructor
// FUN_004b115c PoweredSubsystem destructor
// FUN_004b0e6c PoweredSubsystem::ResetToInitialState (vtable slot 10)
// FUN_004b0efc PoweredSubsystem (vtable slot 9 -- ReadUpdateRecord)
// FUN_004b0f48 PoweredSubsystem::GetStatusFlags (vtable slot 12)
// FUN_004b1224 PoweredSubsystem::PrintState (vtable slot 13)
// FUN_004b13ac PoweredSubsystem::CreateStreamedSubsystem
// FUN_0041bd34 Subsystem::TakeDamage (vtable slot 6, base)
// FUN_0041c500 Subsystem::WriteUpdateRecord (vtable slot 7, base)
// FUN_0041b9ec AlarmIndicator(levels) FUN_0041baa4 ~AlarmIndicator
// FUN_0041bbd8 AlarmIndicator::SetLevel(n)
// FUN_0041db7c Damage::Damage() / init
// FUN_0041a1a4 IsDerivedFrom(classDerivations)
// FUN_00408440 Vector copy FUN_0040a7f4 Point copy
// FUN_00408644 Vector subtract (out = a - b)
// FUN_004087f4 Vector negate/copy
// FUN_0040aadc Matrix identity FUN_0040ad2c Matrix from angles
// FUN_00409a70 apply transform FUN_00409968 Vector set
// FUN_004dd138 sqrtf FUN_004dc8ec atan2f(double,double)
// FUN_00408110 angle normalise
// FUN_0041f216 / FUN_0041f235 segment-iterator construct / destruct
// FUN_00424da8 resolve segment world point
// FUN_0049b6d8 cockpit HUD: register pip / fire marker
// FUN_004b13ac etc. (resource parser primitives):
// FUN_00404118 NotationFile::Read(scalar) FUN_00404088 Read(string)
// FUN_004040d8 Read(int/bool) FUN_00404190 Read(int)
// FUN_00406824 parse colour FUN_00406ff8 ResourceFile::FindResource
// FUN_004d4b58 strcmp FUN_004d4b14 memcmp FUN_004d4c78 strstr
// FUN_004dbb24 DebugStream << FUN_004d9c38 DebugStream flush
// FUN_0040385c Verify()/assert(msg,file,line)
//
#include <bt.hpp>
#pragma hdrstop
#if !defined(MECHWEAP_HPP)
# include <mechweap.hpp>
#endif
#if !defined(APP_HPP)
# include <app.hpp>
#endif
#if !defined(TESTBT_HPP)
# include <testbt.hpp>
#endif
//#############################################################################
// Shared Data Support
//
Derivation
MechWeapon::ClassDerivations(
PoweredSubsystem::GetClassDerivations(),
"MechWeapon"
);
Receiver::MessageHandlerSet MechWeapon::MessageHandlers;
Simulation::AttributeIndexSet MechWeapon::AttributeIndex;
MechWeapon::SharedData
MechWeapon::DefaultData(
&MechWeapon::ClassDerivations,
MechWeapon::MessageHandlers,
MechWeapon::AttributeIndex,
MechWeapon::StateCount
);
//#############################################################################
// Firing -- abstract base body
//
// @004b95ec -- MechWeapon is abstract; the base FireWeapon() must never run.
// Exact analog of RP GenericGun::Fire()'s "Should not be here!" stub. Real
// firing lives in Emitter::FireWeapon (PPC/Gauss) and ProjectileWeapon
// (MissileLauncher). Assert tag: MECHWEAP.CPP : 0xC9.
//
void
MechWeapon::FireWeapon()
{
Fail("MechWeapon abstract class should not be fired");
}
//
// @004ba45c -- a second pure-virtual placeholder emitted from MECHWEAP.HPP:343
// ("ERROR: Accessing Pure Virtual Method"). Compiled into the next translation
// unit (Emitter); reproduced here for completeness. A human should reconcile
// which header-declared pure virtual this guards.
//
// void MechWeapon::<pure-virtual>() {
// Verify(False, "ERROR: Accessing Pure Virtual Method",
// "d:\\tesla\\bt\\bt\\mechweap.hpp", 0x157);
// }
//#############################################################################
// Construction / Destruction
//
// @004b99a8 -- chains to the PoweredSubsystem ctor (@004b0f74), installs the
// MechWeapon vtable (PTR_FUN_00511d2c), constructs the alarm indicator and the
// Damage member, then copies the weapon parameters out of the resource record.
//
MechWeapon::MechWeapon(
Mech *owner,
int subsystem_ID,
SubsystemResource *subsystem_resource,
SharedData &shared_data
):
PoweredSubsystem(owner, subsystem_ID, subsystem_resource, shared_data),
weaponAlarm(/*levels=*/ 3), // FUN_0041b9ec(this+0xd4, levels)
damageData() // FUN_0041db7c(this+0xea)
{
Check(owner);
Check_Pointer(subsystem_resource);
// pip colour defaults to (-1,-1,-1) -- "use the configured/team colour".
pipColor.Red = -1.0f; // _DAT_004b9b98
pipColor.Green = -1.0f;
pipColor.Blue = -1.0f;
// --- copy weapon parameters out of the resource ---
rechargeRate = subsystem_resource->rechargeRate; // +0x190
explosionResourceID = subsystem_resource->explosionResourceID; // +0x198
damageData.damageType = subsystem_resource->damageType; // +0x1A0
damageData.damageAmount = subsystem_resource->damageAmount; // +0x19C
segmentPageIndex = subsystem_resource->segmentIndex; // +0x28
weaponRange = subsystem_resource->weaponRange; // +0x194
heatCostToFire = subsystem_resource->heatCostToFire; // +0x1A4
pipPosition = subsystem_resource->pipPosition; // +0x1A8
pipColor = subsystem_resource->pipColor; // +0x1AC..+0x1B4
pipExtendedRange = subsystem_resource->pipExtendedRange; // +0x1B8
effectiveRange = weaponRange; // 0x328 primed to weaponRange (recomputed in UpdateTargeting)
simulationFlags |= 0x8; // this[10] |= 8 (mark "weapon" subsystem)
rangeToTarget = 0.0f; // 0x324
targetWithinRange = False; // 0x34C
// segmentReference/pipSegment were phantom tail members (past the binary 0x3F0 end);
// removed. The muzzle segment index is the inherited base slot this+0xdc (used by
// GetMuzzlePoint); the pip mount is gated by useConfiguredPip in DrawWeaponPip.
// usesExternalModel: True if the model name contains the marker substring
// (DAT_00511aa2); selects whether the configured pip is drawn.
usesExternalModel = (strstr(GetName(), "EXT") != 0) ? True : False;
fireImpulse = 0.0f; // 0x31C
rechargeLevel = 1.0f; // 0x320 (fully charged)
previousFireImpulse = 0.0f; // 0x3A4
recoil = 0.0f; // 0x3E8
useConfiguredPip = (usesExternalModel == 0); // 0x3E0
pipState = 0; // 0x330
Check_Fpu();
}
//
// @004b9b9c -- destroys the alarm indicator, chains to ~PoweredSubsystem, and
// (if the deleting-dtor bit is set) frees the object.
//
MechWeapon::~MechWeapon()
{
Check(this);
// weaponAlarm (@0xd4) destroyed by its own dtor (FUN_0041baa4(this+0xd4, 2));
// base chain (FUN_004b115c) handles the rest.
Check_Fpu();
}
//#############################################################################
// Test Class Support
//
Logical
MechWeapon::TestClass(Mech &)
{
return True;
}
Logical
MechWeapon::TestInstance() const
{
return IsDerivedFrom(ClassDerivations);
}
//#############################################################################
// Subsystem virtual overrides
//
//
// @004b964c -- slot 6. Apply damage through the base, then set the weapon's
// display alarm: a destroyed weapon (damage->destroyed flag, +0x14) clears the
// alarm to 0, otherwise the alarm tracks the incoming damage level (+0x10).
//
void
MechWeapon::TakeDamage(Damage &damage)
{
Subsystem::TakeDamage(damage); // FUN_0041bd34
// A destroyed weapon clears its display alarm; otherwise the alarm tracks
// the damage-zone state set by the base TakeDamage above. (The decomp read
// the post-damage destroyed/level flags out of the damage record; here we
// read them back off the resolved damage zone.)
// Read the ENGINE Subsystem::damageZone (the real built zone), qualified -- the
// nearer MechSubsystem::damageZone is a ReconDamageZone-shim SHADOW of it (exposed
// once HeatableSubsystem re-based onto MechSubsystem). TODO: remove that shadow.
::DamageZone *dz = this->Subsystem::damageZone;
if (dz != 0
&& dz->GetGraphicState() == DamageZone::DestroyedGraphicState)
{
weaponAlarm.SetLevel(0); // FUN_0041bbd8(this+0xd4, 0)
}
else
{
weaponAlarm.SetLevel(
(dz != 0 && dz->damageLevel > 0.0f) ? 1 : 0
); // FUN_0041bbd8(this+0xd4, level)
}
}
//
// @004b9690 -- slot 7. Append the weapon's replication fields to the update
// record: record type 0x18, the alarm state (this+0x364), and a "ready/idle"
// flag derived from this+0x360.
//
void
MechWeapon::WriteUpdateRecord(UpdateRecord *message, int update_model)
{
Subsystem::WriteUpdateRecord(message, update_model); // FUN_0041c500
message->recordID = 0x18; // *message = 0x18
message->alarmState = weaponAlarm.GetLevel(); // message[4] = *(this+0x364)
message->weaponIdle = (weaponAlarm.GetLevel() == 0) ? 1 : 0; // message[5]
}
//
// @004b96d4 -- slot 9. MechWeapon adds nothing of its own; defers to the
// PoweredSubsystem implementation (paired with WriteUpdateRecord above).
//
void
MechWeapon::ReadUpdateRecord(UpdateRecord *message)
{
PoweredSubsystem::ReadUpdateRecord(message); // FUN_004b0efc
}
//
// @004b96ec -- slot 10. Reset the firing/recharge state, then chain to base.
//
void
MechWeapon::ResetToInitialState()
{
PoweredSubsystem::ResetToInitialState(); // FUN_004b0e6c
recoil = 0.0f; // this[0xfa] = 0
rechargeLevel = 1.0f; // this[200] = 0x3f800000
fireImpulse = 0.0f; // this[199] = 0
previousFireImpulse = 0.0f; // this[0xe9] = 0
}
//
// @004b9d00 -- slot 13. Defers to the PoweredSubsystem print routine.
//
void
MechWeapon::PrintState()
{
PoweredSubsystem::PrintState(); // FUN_004b1224
}
//#############################################################################
// Targeting / aiming helpers
//
//
// @004b9bdc -- recompute the range to the owning Mech's current target and the
// heat-degraded effective range, then decide whether the target is in range.
//
// no target (entity+0x388 == 0):
// rangeToTarget = -1; targetWithinRange = False
// else:
// v = entity->targetPoint(+0x37c) - entity->muzzlePoint(+0x100)
// rangeToTarget = |v|
// effectiveRange = (1.0 - heatSource->heatLoad(+0x158)) * weaponRange
// targetWithinRange = (effectiveRange > rangeToTarget)
//
Logical
MechWeapon::UpdateTargeting()
{
if (!HasActiveTarget()) // *(entity+0x388) == 0
{
rangeToTarget = -1.0f; // 0xbf800000
targetWithinRange = False;
}
else
{
Point3D target, muzzle;
GetTargetPosition(target);
GetMuzzlePoint(muzzle);
Vector3D delta;
delta.Subtract(target, muzzle); // FUN_00408644
rangeToTarget = (Scalar)Sqrt( // FUN_004dd138
delta.x * delta.x + delta.y * delta.y + delta.z * delta.z);
// 1.0f == _DAT_004b9c98; heatLoad is the inherited HeatableSubsystem load.
effectiveRange = (1.0f - heatLoad) * weaponRange; // *0x32c
targetWithinRange = (effectiveRange > rangeToTarget) ? True : False;
}
return targetWithinRange;
}
//
// @004b9608 -- rising-edge detector on fireImpulse (threshold _DAT_004b9648 == 0.0f).
// Returns True the frame fireImpulse becomes positive while the previous sample
// was non-positive, then stores the current sample for next time.
// TODO: confirm exact role (discharge vs. recharge-ready edge).
//
Logical
MechWeapon::CheckFireEdge()
{
Logical edge =
(fireImpulse > 0.0f && previousFireImpulse <= 0.0f) ? True : False;
previousFireImpulse = fireImpulse;
return edge;
}
//
// @004b9cbc -- copy the owning Mech's current target position (entity+0x1c4).
//
void
MechWeapon::GetTargetPosition(Point3D &position)
{
// E3: read the owning Mech's current target position (mech+0x37c, set by mech4's
// targeting step) -- the local `targetPoint` member isn't refreshed in the port.
char *o = (char *)owner; // inherited MechSubsystem::owner (the Mech)
if (o != 0)
position = *(Point3D *)(o + 0x37c); // MECH_TARGET_POS
// (no owner -> leave position unchanged; the phantom `targetPoint` member is gone --
// binary GetTargetPosition @004b9cbc unconditionally reads the owner target slot.)
}
// Faithful FUN_004b9948 muzzle resolve, defined in mech4.cpp (a complete-Mech TU);
// mechweap.cpp reaches the owner Mech only as a raw pointer, so it bridges via void*.
extern void BTResolveWeaponMuzzle(void *ownerMech, int segIndex, Point3D &out);
//
// @004b9cdc -- direction from a supplied point to the weapon mount (entity+0x37c).
//
void
MechWeapon::GetVectorToWeapon(const Point3D &from, Vector3D &direction)
{
Point3D muzzle;
GetMuzzlePoint(muzzle); // resolve live (phantom muzzlePoint member removed)
direction.Subtract(muzzle, from); // FUN_00408644
}
//
// @004b9948 -- resolve the weapon's muzzle/site world position by looking up its
// segment in the owning Mech's segment table and transforming it.
//
void
MechWeapon::GetMuzzlePoint(Point3D &point)
{
// @004b9948 -- resolve the weapon's mount segment (its index is the inherited
// subsystem slot at this+0xdc, == binary param_1+0xdc) in the owning Mech's
// segment table and transform it to world (FUN_00424da8). Reads live each call
// (the phantom cached `muzzlePoint` member is removed -- it collided with the
// Emitter subclass's own fields at 0x3F0+).
int segIndex = *(int *)((char *)this + 0xdc); // inherited segment index (byte-exact base)
BTResolveWeaponMuzzle(owner, segIndex, point); // mech4.cpp bridge (null-guarded)
}
//
// @004b9864 -- build an aim orientation from a direction vector (yaw/pitch via
// atan2), producing a rotation matrix.
//
void
MechWeapon::ComputeAimOrientation(LinearMatrix &orientation, const Vector3D &direction)
{
// Build a yaw/pitch aim orientation from a direction vector (atan2), roll 0.
Scalar yaw = (Scalar)atan2(-direction.x, -direction.z); // FUN_004dc8ec
Scalar len = (Scalar)Sqrt(direction.z * direction.z + direction.x * direction.x);
Scalar pitch = (Scalar)atan2(direction.y, len); // FUN_004dc8ec
EulerAngles angles(
Radian(Radian::Normalize(pitch)),
Radian(Radian::Normalize(yaw)),
Radian(0.0f)
);
orientation = angles; // AffineMatrix::operator=(EulerAngles)
}
//
// @004b9728 -- register the targeting "pip" / fire marker with the owning Mech's
// cockpit HUD manager (entity+0x434). Skipped for non-pip weapons (the
// IsDerivedFrom(0x50bdb4) / segment-index guard). Builds a HUD element record
// {priority 100, kind 0x12, flags} carrying the pip transform and submits it.
//
void
MechWeapon::DrawWeaponPip(const LinearMatrix &transform)
{
// @004b9728 -- register the targeting "pip" with the owning Mech's cockpit
// HUD manager. Skipped for non-pip weapons (pipSegment == -1). The HUD
// element record {priority 100, kind 0x12, enabled, team colour, pipPosition,
// pipExtendedRange, transform, pipSegment} is submitted to the cockpit HUD.
//
// CROSS-FAMILY (hud): the cockpit HUD manager AddElement entry point lives in
// the HUD module; the submission is wired there. We retain the gating and
// transform reference here.
if (useConfiguredPip) // this+0x3E0 (weapon draws a configured pip)
{
(void)transform;
(void)pipPosition;
(void)pipExtendedRange;
(void)pipColor;
// owningHud->AddElement( ...pip record... ); // FUN_0049b6d8(entity+0x434, ..)
}
}
//#############################################################################
// CreateStreamedSubsystem
//
// @004b9d10 -- parse the MechWeapon-specific resource fields after the
// PoweredSubsystem base parser. On the first pass every field is primed to the
// "unset" sentinel (-1.0f == _DAT_004ba37c, or -1 for ints); a field is an error
// only if both the read fails AND the field is still unset. Stamps
// subsystemModelSize = 0x1BC and classID = MechWeaponClassID.
//
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( // FUN_004b13ac
model_file, model_name, subsystem_name,
subsystem_resource, subsystem_file, directories, passes
)
)
{
return False;
}
subsystem_resource->subsystemModelSize = sizeof(*subsystem_resource); // 0x1BC
subsystem_resource->classID = (RegisteredClass::ClassID)Mech::MechWeaponClassID; // 0xBCD
if (passes == 1)
{
// prime all weapon fields to "unset"
subsystem_resource->rechargeRate = -1.0f;
subsystem_resource->weaponRange = -1.0f;
subsystem_resource->explosionResourceID = -1;
subsystem_resource->damageAmount = -1.0f;
subsystem_resource->heatCostToFire = -1.0f;
subsystem_resource->pipPosition = -1;
subsystem_resource->pipColor.Red = -1.0f;
subsystem_resource->pipColor.Green = -1.0f;
subsystem_resource->pipColor.Blue = -1.0f;
subsystem_resource->pipExtendedRange = -1;
}
// "SegmentPageName" must be present on streaming passes after the first.
if (subsystem_resource->segmentIndex == -1 && passes > 1) // +0x28
{
DEBUG_STREAM << subsystem_name << " missing SegmentPageName!";
return False;
}
if (
!model_file->GetEntry(subsystem_name, "DamageAmount", &subsystem_resource->damageAmount)
&& subsystem_resource->damageAmount == -1.0f
)
{
DEBUG_STREAM << model_name << " Missing DamageAmount!" << std::endl;
return False;
}
if (
!model_file->GetEntry(subsystem_name, "HeatCostToFire", &subsystem_resource->heatCostToFire)
&& subsystem_resource->heatCostToFire == -1.0f
)
{
DEBUG_STREAM << subsystem_name << " missing HeatCostToFire!";
return False;
}
if (
!model_file->GetEntry(subsystem_name, "PipPosition", &subsystem_resource->pipPosition)
&& subsystem_resource->pipPosition == -1
)
{
DEBUG_STREAM << subsystem_name << " missing PipPosition!";
return False;
}
//
// PipColor: optional named colour; "Unspecified" leaves the resource value,
// otherwise it is parsed. A missing/short value is a hard error.
//
const char *pipColorName = "Unspecified";
if (!model_file->GetEntry(subsystem_name, "PipColor", &pipColorName))
{
// no value supplied -- require the resource already to hold one
RGBColor probe(-1.0f, -1.0f, -1.0f);
if (probe == subsystem_resource->pipColor)
{
DEBUG_STREAM << subsystem_name << " missing PipColor!";
return False;
}
}
if (strcmp(pipColorName, "Unspecified") != 0)
{
// FUN_00406824 -- the shipped build mapped a colour token to an RGB triple;
// accept an explicit "R G B" triple here (named-colour table is data-driven).
float r = -1.0f, g = -1.0f, b = -1.0f;
if (sscanf(pipColorName, "%f %f %f", &r, &g, &b) == 3)
{
subsystem_resource->pipColor.Red = r;
subsystem_resource->pipColor.Green = g;
subsystem_resource->pipColor.Blue = b;
}
}
if (
!model_file->GetEntry(subsystem_name, "PipExtendedRange", &subsystem_resource->pipExtendedRange)
&& subsystem_resource->pipExtendedRange == -1
)
{
DEBUG_STREAM << subsystem_name << " missing PipExtendedRange!" << std::endl;
return False;
}
if (
!model_file->GetEntry(subsystem_name, "RechargeRate", &subsystem_resource->rechargeRate)
&& subsystem_resource->rechargeRate == -1.0f
)
{
DEBUG_STREAM << model_name << " Missing RechargeRate!" << std::endl;
return False;
}
if (
!model_file->GetEntry(subsystem_name, "WeaponRange", &subsystem_resource->weaponRange)
&& subsystem_resource->weaponRange == -1.0f
)
{
DEBUG_STREAM << model_name << " Missing WeaponRange!" << std::endl;
return False;
}
//
// DamageType: maps a name onto the Damage::DamageType enum.
//
const char *damageTypeName = "Unspecified";
if (
!model_file->GetEntry(subsystem_name, "DamageType", &damageTypeName)
&& subsystem_resource->damageAmount == -1.0f
)
{
DEBUG_STREAM << model_name << " missing DamageType!";
return False;
}
if (strcmp(damageTypeName, "Unspecified") != 0)
{
if (strcmp(damageTypeName, "CollisionDamage") == 0)
subsystem_resource->damageType = Damage::CollisionDamageType; // 0
else if (strcmp(damageTypeName, "BallisticDamage") == 0)
subsystem_resource->damageType = Damage::BallisticDamageType; // 1
else if (strcmp(damageTypeName, "ExplosiveDamage") == 0)
subsystem_resource->damageType = Damage::ExplosiveDamageType; // 2
else if (strcmp(damageTypeName, "LaserDamage") == 0)
subsystem_resource->damageType = Damage::LaserDamageType; // 3
else if (strcmp(damageTypeName, "EnergyDamage") == 0)
subsystem_resource->damageType = Damage::EnergyDamageType; // 4
else
{
DEBUG_STREAM << model_name << " has an unknown DamageType of "
<< damageTypeName << std::endl;
return False;
}
}
//
// ExplosionModelFile: optional; "Unspecified" leaves it unset, otherwise the
// named model is looked up in the resource file.
//
const char *explosionModelName = "Unspecified";
if (
!model_file->GetEntry(subsystem_name, "ExplosionModelFile", &explosionModelName)
&& subsystem_resource->explosionResourceID == -1
)
{
DEBUG_STREAM << model_name << " missing ExplosionModelFile!";
return False;
}
ResourceDescription *explosion = 0;
if (strcmp(explosionModelName, "Unspecified") != 0)
{
explosion = resource_file->FindResourceDescription( // FUN_00406ff8
explosionModelName, (ResourceDescription::ResourceType)1, -1);
}
if (explosion == 0 && subsystem_resource->explosionResourceID == -1)
{
DEBUG_STREAM << model_name << " cannot find " << explosionModelName
<< " in resource file!";
return False;
}
if (explosion != 0)
{
subsystem_resource->explosionResourceID = explosion->resourceID;
}
return True;
}