//===========================================================================// // File: myomers.cpp // // Project: BattleTech Brick: Mech subsystems // // Contents: Myomers -- artificial-muscle locomotion power // //---------------------------------------------------------------------------// // Copyright (C) 1995, Virtual World Entertainment, Inc. // // All Rights reserved worldwide // // This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL // //===========================================================================// #include #pragma hdrstop #if !defined(MYOMERS_HPP) # include #endif #if !defined(MECH_HPP) # include #endif Derivation Myomers::ClassDerivations( PoweredSubsystem::ClassDerivations, "Myomers" ); const Myomers::IndexEntry Myomers::AttributePointers[]= { ATTRIBUTE_ENTRY(Myomers, SpeedEffect, speedEffect), ATTRIBUTE_ENTRY(Myomers, CurrentSeekVoltageIndex, currentSeekVoltageIndex), ATTRIBUTE_ENTRY(Myomers, RecommendedSeekVoltageIndex, recommendedSeekVoltageIndex), ATTRIBUTE_ENTRY(Myomers, MinSeekVoltageIndex, minSeekVoltageIndex), ATTRIBUTE_ENTRY(Myomers, MaxSeekVoltageIndex, maxSeekVoltageIndex), { (int)Myomers::SeekVoltageAttributeID, "SeekVoltage", (Simulation::AttributePointer)&Myomers::seekVoltage } }; Myomers::AttributeIndexSet Myomers::AttributeIndex( ELEMENTS(Myomers::AttributePointers), Myomers::AttributePointers, PoweredSubsystem::AttributeIndex ); // // The shared data must carry the POWERED tables, not Subsystem's -- the // same miswiring found on MissileLauncher (5.3.33). Myomers derives from // PoweredSubsystem, so wiring it to Subsystem's dropped every powered // attribute AND the powered message handlers (ToggleSeekVoltage and the // generator-select family) on the way past. // Myomers::SharedData Myomers::DefaultData( Myomers::ClassDerivations, PoweredSubsystem::MessageHandlers, Myomers::AttributeIndex, Subsystem::StateCount ); Myomers::Myomers( Mech *owner, int subsystem_ID, SubsystemResource *resource, SharedData &shared_data ): PoweredSubsystem(owner, subsystem_ID, resource, shared_data) { Check(owner); Check_Pointer(resource); speedEffect = 1.0f; heatRange = failureTemperature - degradationTemperature; heatRangeSquared = heatRange * heatRange; velocityEfficiency = resource->velocityEfficiency; accelerationEfficiency = resource->accelerationEfficiency; // // Build the seek-voltage drive table: each resource fraction scaled by the // powering generator's rated voltage. The voltage source is resolved once // AttachToVoltageSource runs (phase-4 wiring); until then it is null and the // table scales to zero. // Generator *source = (Generator *)ResolveVoltageSource(); Scalar srcRated = (source != NULL) ? source->ratedVoltage : 0.0f; maxSeekVoltageIndex = -1; int count = 0; while (count < 5) { if (resource->seekVoltage[count] == -1.0f) { maxSeekVoltageIndex = count - 1; break; } seekVoltage[count] = resource->seekVoltage[count] * srcRated; ++count; } if (maxSeekVoltageIndex < 0) { maxSeekVoltageIndex = count - 1; } minSeekVoltageIndex = 0; recommendedSeekVoltageIndex = resource->seekVoltageRecommendedIndex; currentSeekVoltageIndex = recommendedSeekVoltageIndex; // // The master instance runs the myomer per-frame (the same gate every // sibling uses; the binary's is the segment-copy/master flag pair). // // The binary's master gate: MasterInstance + DynamicFlag -- the // simulationFlags 0xC/0x100 pair, now decoded as engine enum identities // (InstanceBits=2 makes InstanceMask 0xC; DynamicBit=8 makes DynamicFlag // 0x100; Entity::DefaultFlags carries both, seeded from the MakeMessage). if ( (owner->simulationFlags & Entity::InstanceMask) == Entity::MasterInstance && (owner->simulationFlags & Entity::DynamicFlag) != 0 ) { SetPerformance(&Myomers::MyomersSimulation); } Check_Fpu(); } Myomers::~Myomers() { } Logical Myomers::TestClass(Mech &) { return True; } Logical Myomers::TestInstance() const { return IsDerivedFrom(ClassDerivations); } // //############################################################################# // @004b8ac0 -- the drive computation. base = the mech base speed scaled by // the gear ratio (input over the RECOMMENDED gear's voltage); a heat factor // bleeds it off quadratically across the degradation band and kills it past // failure; and the subsystem's own accumulated zone damage takes its cut. //############################################################################# // Scalar Myomers::AvailableOutput(Scalar input_voltage) { Check(this); Scalar base = ((Mech *)GetEntity())->BaseSpeedOf() * (input_voltage / seekVoltage[recommendedSeekVoltageIndex]); Scalar heat_factor; if (degradationTemperature <= currentTemperature) { if (failureTemperature <= currentTemperature) { heat_factor = 0.0f; } else { Scalar band = currentTemperature - degradationTemperature; heat_factor = (base == 0.0f) ? 1.0f : (base - band * band * (base / heatRangeSquared)) / base; } } else { heat_factor = 1.0f; } return (1.0f - GetSubsystemDamageLevel()) * base * heat_factor; } // //############################################################################# // @004b8ef0 -- raise the owner's run-speed cap to this myomer's // full-throttle output. An idempotent max: repeat calls never lower it, // and the best available output only falls as heat and damage degrade -- // so re-registering per tick converges on the binary's once-at-assembly // value while staying live to degradation. //############################################################################# // void Myomers::RegisterMaxOutput() { Check(this); Scalar best = AvailableOutput(seekVoltage[maxSeekVoltageIndex]); ((Mech *)GetEntity())->RaiseRunSpeedMax(best); } // //############################################################################# // @004b8d18 -- the per-frame myomer. Five blocks, in the binary's order: // // 0. RegisterMaxOutput -- the assembly-time top-speed cap (see above). // 1. The base electrical state machine. FIRST, unconditionally -- gating // it behind anything denies power handling to most pilots. // 2. The un-powered SELF-REPAIR: while this myomer has NO VOLTAGE and its // zone is not destroyed, hand the zone a NEGATIVE explosive tick -- // muscle knits itself back together while unloaded. // 3. Republish the input voltage from the resolved source. // 4. Republish speedEffect, the live drive handed to the mover: the input // clamped to the SELECTED gear then the top gear, through // AvailableOutput, normalised by the mech base speed -- a 0..1 // fraction carrying the gear ratio, the thermal curve and the wear. //############################################################################# // static const Scalar MyomerRecoveryPerTick = -0.011f; // 0xbc343958 -- the heal is a // negative damage amount void Myomers::MyomersSimulation(Scalar time_slice) { Check(this); RegisterMaxOutput(); PoweredSubsystemSimulation(time_slice); if ( GetVoltageState() == PoweredSubsystem::NoVoltage && GetSubsystemDamageLevel() < 1.0f ) { Damage repair; repair.damageType = Damage::ExplosiveDamageType; repair.damageAmount = MyomerRecoveryPerTick; repair.impactPoint = ((Mech *)GetEntity())->localOrigin.linearPosition; repair.burstCount = 1; TakeDamage(repair); } Generator *source = (Generator *)ResolveVoltageSource(); outputVoltage = (GetVoltageState() == PoweredSubsystem::Ready && source != NULL) ? source->MeasuredVoltage() : 0.0f; if (GetVoltageState() != PoweredSubsystem::Ready) { speedEffect = 0.0f; } else { Scalar drive = outputVoltage; if (drive >= seekVoltage[currentSeekVoltageIndex]) { drive = seekVoltage[currentSeekVoltageIndex]; } if (drive > seekVoltage[maxSeekVoltageIndex]) { drive = seekVoltage[maxSeekVoltageIndex]; } speedEffect = AvailableOutput(drive) / ((Mech *)GetEntity())->BaseSpeedOf(); } Check_Fpu(); }