//===========================================================================// // File: mechsub.cpp // // Project: BattleTech Brick: Mech subsystems // // Contents: Implementation details for the MechSubsystem base // //---------------------------------------------------------------------------// // Copyright (C) 1995, Virtual World Entertainment, Inc. // // All Rights reserved worldwide // // This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL // //===========================================================================// #include #pragma hdrstop #if !defined(MECHSUB_HPP) # include #endif #if !defined(MECH_HPP) # include #endif #if !defined(DAMAGE_HPP) # include #endif #if !defined(BTPLAYER_HPP) # include #endif #if !defined(BTMSSN_HPP) # include #endif // //############################################################################# // Shared data support -- MechSubsystem adds no message handlers or attributes // that are boot-critical, so it reuses the base Subsystem sets (which are the // inherited Simulation sets). //############################################################################# // Derivation MechSubsystem::ClassDerivations( Subsystem::ClassDerivations, "MechSubsystem" ); MechSubsystem::SharedData MechSubsystem::DefaultData( MechSubsystem::ClassDerivations, Subsystem::MessageHandlers, Subsystem::AttributeIndex, Subsystem::StateCount ); // //############################################################################# // Light constructor (name + classID). For a subsystem created without a // streamed resource; builds a trivial (armour-free) damage zone named by the // base. //############################################################################# // MechSubsystem::MechSubsystem( Mech *owner, int subsystem_ID, const char *subsystem_name, RegisteredClass::ClassID class_id, SharedData &shared_data ): Subsystem(owner, subsystem_ID, subsystem_name, class_id, shared_data) { this->owner = owner; vitalSubsystem = False; alarmModel = ResourceDescription::NullResourceID; criticalReference = 0.0f; collisionCriticalHitWeight = 0.0f; printSimulationState = False; configureActivePress = -1; resource = NULL; statusAlarm.SetLevel(0); // // The base Subsystem ctor leaves damageZone NULL; give this subsystem a // trivial (armour-free) zone. // damageZone = new DamageZone(owner, 0); Register_Object(damageZone); } // //############################################################################# // Resource constructor. Chains the base Subsystem resource ctor and seeds the // mech-specific fields from the subsystem resource. // // The 4.10 damage model is per-damage-TYPE: the damage zone owns // defaultArmorPoints + damageScale[5] and streams them itself (a // Mech__DamageZone built from the model's damage-zone stream). Wiring that // stream through from the resource is deferred (see MECHSUB.NOTES.md); until // then the subsystem gets a trivial armour-free zone so it constructs and takes // damage with default (unscaled) behaviour. //############################################################################# // MechSubsystem::MechSubsystem( Mech *owner, int subsystem_ID, SubsystemResource *subsystem_resource, SharedData &shared_data ): Subsystem(owner, subsystem_ID, subsystem_resource, shared_data) { Check_Pointer(subsystem_resource); this->owner = owner; resource = subsystem_resource; vitalSubsystem = (subsystem_resource->vitalSubsystemIndex == 1); alarmModel = subsystem_resource->alarmModel; printSimulationState = subsystem_resource->printSimulationState; criticalReference = subsystem_resource->criticalReference; collisionCriticalHitWeight = subsystem_resource->collisionCriticalHitWeight; configureActivePress = -1; statusAlarm.SetLevel(0); // // The subsystem's PRIVATE damage zone (binary @4abf28: `new // DamageZone(this, 0)` -- owned by the SUBSYSTEM, index 0, NOT an entry // in the entity's hull-zone array). The resource authors its armor the // same way the hull stream does -- structureReference points + // armorByFacing[5] per-type values -- normalized with the same rule as // Mech::DamageZone (scale = 1/(raw x points); already-normalized cells // pass through). This is what makes critical hits MEASURABLE // (ApplyDamageAndMeasure) and the crit-allotment push meaningful. // damageZone = new DamageZone(this, 0); Register_Object(damageZone); damageZone->damageZoneName = GetName(); if (subsystem_resource->structureReference > 0.0f) { damageZone->defaultArmorPoints = subsystem_resource->structureReference; for (int tt = 0; tt < Damage::DamageTypeCount; ++tt) { Scalar raw = subsystem_resource->armorByFacing[tt]; Scalar even = 1.0f / subsystem_resource->structureReference; Scalar diff = raw - even; if (diff < 0.0f) { diff = -diff; } if (diff > 1.0e-4f && raw > 0.0f) { damageZone->damageScale[tt] = 1.0f / (raw * subsystem_resource->structureReference); } else { damageZone->damageScale[tt] = raw; } } } } // //############################################################################# //############################################################################# // MechSubsystem::~MechSubsystem() { if (damageZone) { Unregister_Object(damageZone); delete damageZone; damageZone = NULL; } } // //############################################################################# //############################################################################# // Logical MechSubsystem::TestClass(Mech &) { return True; } Logical MechSubsystem::TestInstance() const { return IsDerivedFrom(ClassDerivations); } // //############################################################################# // Damage support -- the 4.10 damage zone tracks damageLevel in [0,1] // (0 = intact, 1 = destroyed). //############################################################################# // Scalar MechSubsystem::GetSubsystemDamageLevel() const { Check(this); return damageZone ? damageZone->damageLevel : 0.0f; } void MechSubsystem::SetSubsystemDamageLevel(Scalar level) { Check(this); if (damageZone) { damageZone->damageLevel = level; } } void MechSubsystem::ForceCriticalFailure() { Check(this); statusAlarm.SetLevel(MechSubsystem::Destroyed); // // DestroyedState is the hard-failure gate the weapon simulations poll // (`GetSimulationState() == 1`): a destroyed weapon drops its charge / // pins the unavailable alarm from the next frame on. // SetSimulationState(DestroyedState); if (damageZone) { damageZone->SetDamageZoneState(DamageZone::BurningState); } } // //############################################################################# // ApplyDamageAndMeasure (binary @4ac07c): apply through the virtual // TakeDamage and report the zone-level delta -- the critical accountant // (Mech::DamageZone::CriticalHit) charges the delta against the entry's // damagePercentage allotment. //############################################################################# // Scalar MechSubsystem::ApplyDamageAndMeasure(Damage &damage) { Check(this); Scalar before = (damageZone != NULL) ? damageZone->damageLevel : 0.0f; TakeDamage(damage); Scalar after = (damageZone != NULL) ? damageZone->damageLevel : 0.0f; return after - before; } // //############################################################################# // TakeDamage Forward to the damage zone (per-type scaling happens there). //############################################################################# // void MechSubsystem::TakeDamage(Damage &damage) { Check(this); Check_Pointer(&damage); if (damageZone) { damageZone->TakeDamage(damage); } } // //############################################################################# // CreateStreamedSubsystem Model-load-time construction of the subsystem // resource + its damage-zone stream. Not yet reconstructed (see // MECHSUB.NOTES.md -- the 4.10 per-type damage-zone stream format). //############################################################################# // int MechSubsystem::CreateStreamedSubsystem( NotationFile *, const char *, const char *, SubsystemResource *, NotationFile *, const ResourceDirectories *, int ) { Fail("MechSubsystem::CreateStreamedSubsystem -- mechsub.cpp not yet reconstructed"); return 0; } // //############################################################################# // NoviceLockout -- the novice-experience lockout predicate (binary @4ac9c8): // owner mech -> Entity::playerLink -> the BTPlayer's experience level == 0. // Locks the advanced cockpit controls (condenser valves, coolant flush, // cooling toggles). An unlinked mech reads UNLOCKED (dev-permissive). //############################################################################# // Logical MechSubsystem::NoviceLockout() { Check(this); if (owner == NULL) { return False; } BTPlayer *player = Cast_Object(BTPlayer *, owner->GetPlayerLink()); if (player == NULL) { return False; } return (player->GetExperienceLevel() == BTMission::NoviceMode) ? True : False; }