//===========================================================================// // File: mech2.cpp // // Project: BattleTech Brick: Entity Manager // // Contents: Mech gait animation -- the transition machine // //---------------------------------------------------------------------------// // Date Who Modification // // -------- --- ---------------------------------------------------------- // // // //---------------------------------------------------------------------------// // Copyright (C) 1995, Virtual World Entertainment, Inc. // // All Rights reserved worldwide // // This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL // //===========================================================================// #include #pragma hdrstop #if !defined(MECH_HPP) # include #endif #if !defined(MECHMPPR_HPP) # include #endif #if !defined(APP_HPP) # include #endif // //############################################################################# // A mech walks on two parallel clip channels. // // The LEG channel is the locally-simulated gait. Its transitions read the // LIVE commanded speed out of the controls mapper, so it responds to the // stick the instant it moves. // // The BODY channel is the displayed motion, and the distance IT advances is // what carries the mech forward. Its transitions read bodyTargetSpeed -- // a snapshot -- which is what lets a dead-reckoned or networked mech walk // properly with no controls mapper of its own. // // Both channels run the same state machine over the same clips; only the // speed they consult differs. That is the whole reason the two ClipFinished // functions below are near-twins rather than one shared routine, and the // symmetry is load-bearing: where the binary's two jump tables agree, a // disagreement in this file is a bug. // // Each clip is ONE STRIDE, which is why every state is handed. A walk is // Right, Left, Right, ... and each entry to and exit from a cycle has its own // handed pair so the mech always leaves on the correct foot. // // The machine only ever runs at END OF CLIP. SequenceController::Advance // calls the channel's finished callback, which picks the next state, re-arms // the channel, and spends the leftover time in the new clip -- returning the // distance that leftover covered so Advance can fold it in. Getting that // contract wrong double-counts the mech's forward motion. //############################################################################# // // //############################################################################# // @004a7fc4 -- bind the leg channel to a state's clip and record the state. //############################################################################# // void Mech::SetLegAnimation(int state) { Check(this); // // Bounded by the SLOT count, not the name count: slot 0x20 is the // bump/crash clip and is legitimately bound on a wall impact. // Verify(state >= 0 && state < AnimationSlotCount); legAnimation.SelectSequence( animationClips[state], (void *)Mech::LegClipFinished, 0, 0); legStateAlarm.SetLevel((unsigned)state); } // //############################################################################# // @004a800c -- the body channel's equivalent. // // This one also drives the animation StateIndicators, which is how the audio // subsystem's watchers learn a gait changed. Guarded to the constructed // range so an out-of-range clip cannot trip StateIndicator's own Verify. //############################################################################# // void Mech::SetBodyAnimation(int state) { Check(this); Verify(state >= 0 && state < AnimationSlotCount); bodyAnimation.SelectSequence( animationClips[state], (void *)Mech::BodyClipFinished, 0, 0); bodyStateAlarm.SetLevel((unsigned)state); animationState.SetState(state); replicantAnimationState.SetState(state); } // //############################################################################# // The shared tails (@0x4a6a06 leg / @0x4a6e66 body) every handler ends in: // bind the next state, then spend the carryover inside it. //############################################################################# // Scalar Mech::LegTransition(int next_state, Scalar advance_time, int move_joints) { Check(this); SetLegAnimation(next_state); return legAnimation.Advance(advance_time, move_joints); } Scalar Mech::BodyTransition(int next_state, Scalar advance_time, int move_joints) { Check(this); SetBodyAnimation(next_state); return bodyAnimation.Advance(advance_time, move_joints); } // //############################################################################# // @004a6928 -- the LEG channel's end-of-clip machine (jump table @0x4a69aa). // // Reads the live commanded speed from the controls mapper. A mech with no // mapper reads zero and simply idles, which is the correct behaviour for a // replicant. //############################################################################# // Scalar Mech::LegClipFinished( Mech *mech, unsigned /* callback_arg */, Scalar carryover, int move_joints ) { Check(mech); // // A LIMPING mech's transitions run the limp machine instead (movement // mode 3 = left-leg limp, 4 = right; the damage model's own "limp gait // graphic (left 3 / right 4)"). Guarded on the clip set existing -- // LoadLocomotionClips leaves hasGimpClips 0 for a model without it. // { int mode = mech->MovementMode(); if ((mode == 3 || mode == 4) && mech->hasGimpClips) { return mech->GimpLegClipFinished(carryover); } } // // The binary reads subsystemArray[0] -- the roster's controls-mapper slot. // A mech without one (a replicant) reads zero and idles, which is right. // Scalar demand = 0.0f; if (mech->subsystemArray != NULL && mech->subsystemArray[0] != NULL) { demand = ((MechControlsMapper *)mech->subsystemArray[0])->GetSpeedDemand(); } Scalar cycle_rate = mech->forwardCycleRate, time_scale = mech->globalTimeScale, cycle = mech->legCycleSpeed, tail_time = carryover * time_scale; switch (mech->legStateAlarm.GetLevel()) { // // Standing and the idle group -- nothing to transition to. // case 0: case 1: case 22: case 23: case 24: case 25: case 26: case 27: return 0.0f; case 2: mech->legStateAlarm.SetLevel(1); return 0.0f; // // The transition-END clips: having arrived, fall back to standing. // case 3: case 4: case 8: case 9: case 20: case 21: case 28: case 29: case 30: case 31: case 32: mech->legStateAlarm.SetLevel(0); return 0.0f; // // Walking, right foot down (@0x4a6aad). Three ways out: stop if the // demand has fallen below the "moving at all" threshold, step up toward // the run cycle if it is over the walk cap, otherwise take the next // stride on the other foot. // // The stop and step-up tests each check the DEMAND and the CURRENT CYCLE // SPEED slewed by one carryover -- so a momentary flick of the stick // cannot yank the mech out of a stride it has already committed to. // case 5: case 6: case 14: if ( demand < mech->standSpeed && (cycle - cycle_rate * carryover) < mech->standSpeed ) { return mech->LegTransition(9, tail_time, move_joints); } if ( demand > mech->walkStrideLength && (cycle + cycle_rate * carryover) > mech->walkStrideLength ) { return mech->LegTransition(0xb, tail_time, move_joints); } return mech->LegTransition( 7, carryover * cycle * time_scale / mech->walkStrideLength, move_joints); // // Walking, left foot down (@0x4a69d6) -- the mirror. // case 7: case 15: if ( demand < mech->standSpeed && (cycle - cycle_rate * carryover) < mech->standSpeed ) { return mech->LegTransition(8, tail_time, move_joints); } if ( demand > mech->walkStrideLength && (cycle + cycle_rate * carryover) > mech->walkStrideLength ) { return mech->LegTransition(0xa, tail_time, move_joints); } return mech->LegTransition( 6, carryover * cycle * time_scale / mech->walkStrideLength, move_joints); // // Running / reversing (@0x4a6bdb and @0x4a6b63): drop back to the walk // cycle when the demand decays, else alternate feet. // case 10: case 12: if ( demand < mech->reverseSpeedMax && (cycle - cycle_rate * carryover) < mech->reverseSpeedMax ) { return mech->LegTransition(0xf, tail_time, move_joints); } return mech->LegTransition( 0xd, carryover * cycle * time_scale / mech->reverseStrideLength, move_joints); case 11: case 13: if ( demand < mech->reverseSpeedMax && (cycle - cycle_rate * carryover) < mech->reverseSpeedMax ) { return mech->LegTransition(0xe, tail_time, move_joints); } return mech->LegTransition( 0xc, carryover * cycle * time_scale / mech->reverseStrideLength, move_joints); // // Limping (@0x4a6c17 and @0x4a6cc4). gimpStrideLength is authored // NEGATIVE, so the cycle time comes out negative and has to be folded // positive before it can be spent -- the binary does exactly this at // @0x4a6c6e / @0x4a6d3d. // case 16: case 18: if ( demand > mech->gimpSpeedMax && (mech->gimpCycleRate * carryover + cycle) > mech->gimpSpeedMax ) { return mech->LegTransition(0x15, tail_time, move_joints); } { Scalar cycle_time = carryover * cycle * time_scale / mech->gimpStrideLength; if (cycle_time <= 0.0f) { cycle_time = -cycle_time; } return mech->LegTransition(0x13, cycle_time, move_joints); } case 17: case 19: if ( demand > mech->gimpSpeedMax && (mech->gimpCycleRate * carryover + cycle) > mech->gimpSpeedMax ) { return mech->LegTransition(0x14, tail_time, move_joints); } { Scalar cycle_time = carryover * cycle * time_scale / mech->gimpStrideLength; if (cycle_time <= 0.0f) { cycle_time = -cycle_time; } return mech->LegTransition(0x12, cycle_time, move_joints); } } // // Falls, crashes and the death clips play out and stop here. // return 0.0f; } // //############################################################################# // @004a6d8c -- the BODY channel's end-of-clip machine (jump table @0x4a6e0a). // // Structurally identical to the leg machine above, reading bodyTargetSpeed // instead of the live mapper demand. Kept as its own routine because that is // how the binary has it, and because the two tables are each other's check. //############################################################################# // Scalar Mech::BodyClipFinished( Mech *mech, unsigned /* callback_arg */, Scalar carryover, int move_joints ) { Check(mech); { int mode = mech->MovementMode(); if ((mode == 3 || mode == 4) && mech->hasGimpClips) { return mech->GimpBodyClipFinished(carryover, move_joints); } } Scalar cycle_rate = mech->forwardCycleRate, time_scale = mech->globalTimeScale, cycle = mech->bodyCycleSpeed, demand = mech->bodyTargetSpeed, tail_time = carryover * time_scale; switch (mech->bodyStateAlarm.GetLevel()) { case 0: case 1: case 22: case 23: case 24: case 25: case 26: case 27: return 0.0f; case 2: mech->bodyStateAlarm.SetLevel(1); return 0.0f; case 3: case 4: case 8: case 9: case 20: case 21: case 28: case 29: case 30: case 31: case 32: mech->bodyStateAlarm.SetLevel(0); return 0.0f; // // Walking, right foot down (@0x4a6f11). // case 5: case 6: case 14: if ( demand < mech->standSpeed && (cycle - cycle_rate * carryover) < mech->standSpeed ) { return mech->BodyTransition(9, tail_time, move_joints); } if ( demand > mech->walkStrideLength && (cycle + cycle_rate * carryover) > mech->walkStrideLength ) { return mech->BodyTransition(0xb, tail_time, move_joints); } return mech->BodyTransition( 7, carryover * cycle * time_scale / mech->walkStrideLength, move_joints); // // Walking, left foot down (@0x4a6e36). // case 7: case 15: if ( demand < mech->standSpeed && (cycle - cycle_rate * carryover) < mech->standSpeed ) { return mech->BodyTransition(8, tail_time, move_joints); } if ( demand > mech->walkStrideLength && (cycle + cycle_rate * carryover) > mech->walkStrideLength ) { return mech->BodyTransition(0xa, tail_time, move_joints); } return mech->BodyTransition( 6, carryover * cycle * time_scale / mech->walkStrideLength, move_joints); // // Running / reversing (@0x4a7041 and @0x4a6fc7). // case 10: case 12: if ( demand < mech->reverseSpeedMax && (cycle - cycle_rate * carryover) < mech->reverseSpeedMax ) { return mech->BodyTransition(0xf, tail_time, move_joints); } return mech->BodyTransition( 0xd, carryover * cycle * time_scale / mech->reverseStrideLength, move_joints); case 11: case 13: if ( demand < mech->reverseSpeedMax && (cycle - cycle_rate * carryover) < mech->reverseSpeedMax ) { return mech->BodyTransition(0xe, tail_time, move_joints); } return mech->BodyTransition( 0xc, carryover * cycle * time_scale / mech->reverseStrideLength, move_joints); // // The reverse cycle (@0x4a707d and @0x4a712c). Note these are the BACK // gait, not a limp, despite sharing the gimp caps: while the demand stays // below gimpSpeedMax the cycle alternates 0x12 <-> 0x13, and a forward // demand leaves through the back-to-stand pair. Reading them as "gimp, // fall back to standing" makes the body loop stand -> reverse-entry // forever, which is a slow reverse with a wrong-footed exit. // case 16: case 18: if ( demand > mech->gimpSpeedMax && (mech->gimpCycleRate * carryover + cycle) > mech->gimpSpeedMax ) { return mech->BodyTransition(0x15, tail_time, move_joints); } { Scalar cycle_time = carryover * cycle * time_scale / mech->gimpStrideLength; if (cycle_time <= 0.0f) { cycle_time = -cycle_time; } return mech->BodyTransition(0x13, cycle_time, move_joints); } case 17: case 19: if ( demand > mech->gimpSpeedMax && (mech->gimpCycleRate * carryover + cycle) > mech->gimpSpeedMax ) { return mech->BodyTransition(0x14, tail_time, move_joints); } { Scalar cycle_time = carryover * cycle * time_scale / mech->gimpStrideLength; if (cycle_time <= 0.0f) { cycle_time = -cycle_time; } return mech->BodyTransition(0x12, cycle_time, move_joints); } } return 0.0f; } // //############################################################################# // @004a5028 -- the LEG channel's per-frame update (ground flavour). // // Reads the LIVE demand from the controls mapper, arms the death clips off // the movement mode, slews legCycleSpeed toward the demand inside each // cycle's caps, and advances the clip -- which is what writes the leg // joints. Returns the cycle distance covered this frame. // // Reconstructed from the RAW decomp rather than the BT411 donor: the donor // carries port-era replicant accommodations and a relocated turn-in-place // dispatcher that belong to ITS network model, not to the binary. In the // binary the trn dispatcher lives in the master performance (mech4), and a // replicant's mapper cell replicates -- so this function reads the mapper // unconditionally, exactly as decompiled. //############################################################################# // Scalar Mech::AdvanceLegAnimation(Scalar time_slice) { Check(this); // // The binary reads the roster's slot 0 with no null check -- a mech // always has its controls mapper by the time it ticks. // MechControlsMapper *mapper = (MechControlsMapper *)subsystemArray[0]; Check_Pointer(mapper); Scalar demand = mapper->GetSpeedDemand(), distance = 0.0f; // // One-shot: movement modes 5..8 are the falls/deaths; latch the matching // crash clip exactly once. // if (!deathAnimationLatched) { switch (MovementMode()) { case 5: SetLegAnimation(0x1c); deathAnimationLatched = 1; break; case 6: SetLegAnimation(0x1d); deathAnimationLatched = 1; break; case 7: SetLegAnimation(0x1e); deathAnimationLatched = 1; break; case 8: SetLegAnimation(0x1f); deathAnimationLatched = 1; break; } } // // Wind-down: once the cycle speed has decayed to nothing during a // walk-transition state, drop straight to standing. // { int state = (int)legStateAlarm.GetLevel(); if ( legCycleSpeed <= 0.0f && (state == 6 || state == 7 || state == 8 || state == 9) ) { legStateAlarm.SetLevel(0); legResetLatch = 1; } } switch (legStateAlarm.GetLevel()) { case 0: // // Standing. A demand above standSpeed begins the walk; a NEGATIVE // demand backs up; anything in between stays put. Arming a state // FALLS THROUGH so the new clip advances this same frame. // if (standSpeed < demand) { SetLegAnimation(5); } else { distance = 0.0f; if (demand >= 0.0f) { break; } SetLegAnimation(0x10); } // fall through case 2: case 3: case 5: case 8: case 9: case 10: case 0xb: case 0xe: case 0xf: case 0x10: case 0x11: case 0x14: case 0x15: case 0x1c: case 0x1d: case 0x1e: case 0x1f: case 0x20: advance_clip: // // The plain-advance group: transitions, falls and deaths play at the // global rate scaled by the idle/transition stride scale. The // Standing guard is the binary's own (MECH2.CPP:0xd3) -- unreachable // through the fall-through above (arming rewrote the level), it // catches a DIRECT entry with the alarm still at 0. // if (legStateAlarm.GetLevel() == 0) { Fail("Standing Not Supported"); } distance = legAnimation.Advance( time_slice * globalTimeScale * idleStrideScale, 1); legCycleSpeed = distance / time_slice; break; case 1: distance = 0.0f; break; case 4: // // Turn-in-place. A demand outside [0, standSpeed] abandons the turn // -- drop to standing and request the leg-state update record -- // otherwise the turn clip advances like any transition. (What ARMS // state 4 is the master performance's dispatcher, mech4 -- not here.) // if (standSpeed < demand) { legStateAlarm.SetLevel(0); ForceUpdate(8); break; } distance = 0.0f; if (demand < 0.0f) { legStateAlarm.SetLevel(0); ForceUpdate(8); break; } goto advance_clip; case 6: case 7: // // The walk cycle. Slew the cycle speed toward the demand at // forwardCycleRate: upward capped by the demand then the walk // stride, downward floored by the demand then standSpeed. The clip // advances at (cycle / walkStride) of its authored rate -- a slow // walk IS the walk clip played slow. // if (demand > legCycleSpeed) { legCycleSpeed += forwardCycleRate * time_slice; if (legCycleSpeed > demand) { legCycleSpeed = demand; } if (legCycleSpeed > walkStrideLength) { legCycleSpeed = walkStrideLength; } } else if (demand < legCycleSpeed) { legCycleSpeed -= forwardCycleRate * time_slice; if (legCycleSpeed < demand) { legCycleSpeed = demand; } if (legCycleSpeed < standSpeed) { legCycleSpeed = standSpeed; } } distance = legAnimation.Advance( time_slice * (legCycleSpeed / walkStrideLength) * globalTimeScale, 1); break; case 0xc: case 0xd: // // The run cycle -- same slew, its own caps: up to runSpeedMax, down // no further than reverseSpeedMax (the drop-out threshold the // ClipFinished handler tests). // if (demand > legCycleSpeed) { legCycleSpeed += forwardCycleRate * time_slice; if (legCycleSpeed > demand) { legCycleSpeed = demand; } if (legCycleSpeed > runSpeedMax) { legCycleSpeed = runSpeedMax; } } else if (demand < legCycleSpeed) { legCycleSpeed -= forwardCycleRate * time_slice; if (legCycleSpeed < demand) { legCycleSpeed = demand; } if (legCycleSpeed < reverseSpeedMax) { legCycleSpeed = reverseSpeedMax; } } distance = legAnimation.Advance( time_slice * (legCycleSpeed / reverseStrideLength) * globalTimeScale, 1); break; case 0x12: case 0x13: // // The reverse cycle. Everything is NEGATIVE here -- the demand, the // cycle speed, and both caps (gimpSpeedMax ~ -4, gimpStrideLength // ~ -20 on the Mad Cat), so "up" slews toward zero and "down" toward // full reverse, at the reverse's own gimpCycleRate. The advance // ratio is folded positive: a reverse clip is authored backward, it // is not played backward. // if (demand > legCycleSpeed) { legCycleSpeed += gimpCycleRate * time_slice; if (legCycleSpeed > demand) { legCycleSpeed = demand; } if (legCycleSpeed > gimpSpeedMax) { legCycleSpeed = gimpSpeedMax; } } else if (demand < legCycleSpeed) { legCycleSpeed -= gimpCycleRate * time_slice; if (legCycleSpeed < demand) { legCycleSpeed = demand; } if (legCycleSpeed < gimpStrideLength) { legCycleSpeed = gimpStrideLength; } } { Scalar ratio = legCycleSpeed / gimpStrideLength; if (ratio <= 0.0f) { ratio = -ratio; } distance = legAnimation.Advance( ratio * time_slice * globalTimeScale, 1); } break; case 0x16: case 0x17: case 0x18: case 0x19: case 0x1a: case 0x1b: // // The reset group -- limp exits and the four falls. Clear the // motion-event state and every one-shot, drop to standing, and put // the skeleton back to its neutral pose. // motionEventName = ""; motionEventArmed = 0; legResetLatch = 0; deathAnimationLatched = 0; legStateAlarm.SetLevel(0); legAnimation.Reset(1); break; default: Fail("Unsupported mech animation"); } return distance; } // //############################################################################# // @004a5678 -- the BODY channel's per-frame update (ground flavour). // // The displayed-motion twin. Differences from the leg version, all // binary-verified: the demand is bodyTargetSpeed (the snapshot, no mapper // access); there is NO wind-down block and NO turn-in-place case (state 4 // sits in the plain group); move_joints arrives as a parameter and reaches // every Advance AND the reset's Reset call -- so the caller decides whether // this channel poses the skeleton or only measures the stride. //############################################################################# // Scalar Mech::AdvanceBodyAnimation(Scalar time_slice, int move_joints) { Check(this); Scalar demand = bodyTargetSpeed, distance = 0.0f; if (!deathAnimationLatched) { switch (MovementMode()) { case 5: SetBodyAnimation(0x1c); deathAnimationLatched = 1; break; case 6: SetBodyAnimation(0x1d); deathAnimationLatched = 1; break; case 7: SetBodyAnimation(0x1e); deathAnimationLatched = 1; break; case 8: SetBodyAnimation(0x1f); deathAnimationLatched = 1; break; } } switch (bodyStateAlarm.GetLevel()) { case 0: distance = 0.0f; if (standSpeed < demand) { SetBodyAnimation(5); } else { if (demand >= 0.0f) { break; } SetBodyAnimation(0x10); } // fall through case 2: case 3: case 4: case 5: case 8: case 9: case 10: case 0xb: case 0xe: case 0xf: case 0x10: case 0x11: case 0x14: case 0x15: case 0x1c: case 0x1d: case 0x1e: case 0x1f: case 0x20: distance = bodyAnimation.Advance( time_slice * globalTimeScale * idleStrideScale, move_joints); bodyCycleSpeed = distance / time_slice; break; case 1: distance = 0.0f; break; case 6: case 7: if (demand > bodyCycleSpeed) { bodyCycleSpeed += forwardCycleRate * time_slice; if (bodyCycleSpeed > demand) { bodyCycleSpeed = demand; } if (bodyCycleSpeed > walkStrideLength) { bodyCycleSpeed = walkStrideLength; } } else if (demand < bodyCycleSpeed) { bodyCycleSpeed -= forwardCycleRate * time_slice; if (bodyCycleSpeed < demand) { bodyCycleSpeed = demand; } if (bodyCycleSpeed < standSpeed) { bodyCycleSpeed = standSpeed; } } distance = bodyAnimation.Advance( time_slice * (bodyCycleSpeed / walkStrideLength) * globalTimeScale, move_joints); break; case 0xc: case 0xd: if (demand > bodyCycleSpeed) { bodyCycleSpeed += forwardCycleRate * time_slice; if (bodyCycleSpeed > demand) { bodyCycleSpeed = demand; } if (bodyCycleSpeed > runSpeedMax) { bodyCycleSpeed = runSpeedMax; } } else if (demand < bodyCycleSpeed) { bodyCycleSpeed -= forwardCycleRate * time_slice; if (bodyCycleSpeed < demand) { bodyCycleSpeed = demand; } if (bodyCycleSpeed < reverseSpeedMax) { bodyCycleSpeed = reverseSpeedMax; } } distance = bodyAnimation.Advance( time_slice * (bodyCycleSpeed / reverseStrideLength) * globalTimeScale, move_joints); break; case 0x12: case 0x13: if (demand > bodyCycleSpeed) { bodyCycleSpeed += gimpCycleRate * time_slice; if (bodyCycleSpeed > demand) { bodyCycleSpeed = demand; } if (bodyCycleSpeed > gimpSpeedMax) { bodyCycleSpeed = gimpSpeedMax; } } else if (demand < bodyCycleSpeed) { bodyCycleSpeed -= gimpCycleRate * time_slice; if (bodyCycleSpeed < demand) { bodyCycleSpeed = demand; } if (bodyCycleSpeed < gimpStrideLength) { bodyCycleSpeed = gimpStrideLength; } } { Scalar ratio = bodyCycleSpeed / gimpStrideLength; if (ratio <= 0.0f) { ratio = -ratio; } distance = bodyAnimation.Advance( ratio * time_slice * globalTimeScale, move_joints); } break; case 0x16: case 0x17: case 0x18: case 0x19: case 0x1a: case 0x1b: motionEventName = ""; motionEventArmed = 0; bodyResetLatch = 0; deathAnimationLatched = 0; bodyStateAlarm.SetLevel(0); bodyAnimation.Reset(move_joints); break; default: Fail("Unsupported mech animation"); } return distance; } // //############################################################################# // THE LIMP MACHINES (@004a7970 leg / @004a6344 body) -- the transition // tables that run while movement mode is 3 (left-leg limp) or 4 (right). // // The limp replaces ONE stride: limping left, the right stride (6) hands off // to the left limp figure (0x16 -> the 0x18 cycle); limping right, the left // stride (7) hands off to the right figure (0x17 -> 0x19). The other leg's // clips keep their normal alternation, which is what makes it read as a limp // rather than a different gait. // // Both machines CLAMP THEIR DEMAND while in any cycle -- the leg machine // writes the mapper's own speedDemand cell down to the damaged side's cap, // the body machine clamps bodyTargetSpeed -- so a limping mech cannot // command more speed than its limp figure carries, and cannot command a // reverse out of a forward cycle (the clamp floors at zero). //############################################################################# // Scalar Mech::GimpLegClipFinished(Scalar carryover) { Check(this); MechControlsMapper *mapper = (MechControlsMapper *)subsystemArray[0]; Check_Pointer(mapper); int mode = MovementMode(), state = (int)legStateAlarm.GetLevel(); // // The demand clamp (see the block comment above). // if ( state == 6 || state == 7 || state == 0xc || state == 0xd || state == 0x12 || state == 0x13 ) { Scalar cap = (mode == 3) ? gimpLeftSpeedMax : gimpRightSpeedMax, demand = mapper->GetSpeedDemand(); if (demand > cap) { demand = cap; } if (demand < 0.0f) { demand = 0.0f; } mapper->SetSpeedDemand(demand); } Scalar demand = mapper->GetSpeedDemand(), cycle_rate = forwardCycleRate, time_scale = globalTimeScale, cycle = legCycleSpeed; int plain_next; switch (state) { case 2: legStateAlarm.SetLevel(1); return 0.0f; case 3: case 8: case 9: case 0x14: case 0x15: case 0x1a: case 0x1b: case 4: case 0x20: legStateAlarm.SetLevel(0); return 0.0f; // // The right-stride walk family. Continuing hands off to the LEFT // stride when the RIGHT leg is the good one (mode 4), and to the left // limp figure when the left leg is the bad one (mode 3). // case 5: case 6: case 0xe: if ( demand < standSpeed && (cycle - cycle_rate * carryover) < standSpeed ) { plain_next = 9; break; } if ( demand > walkStrideLength && (cycle + cycle_rate * carryover) > walkStrideLength ) { plain_next = 0xb; break; } SetLegAnimation((mode == 4) ? 7 : 0x16); return legAnimation.Advance( carryover * cycle * time_scale / walkStrideLength, 1); case 7: case 0xf: if ( demand < standSpeed && (cycle - cycle_rate * carryover) < standSpeed ) { plain_next = 8; break; } if ( demand > walkStrideLength && (cycle + cycle_rate * carryover) > walkStrideLength ) { plain_next = 10; break; } SetLegAnimation((mode == 3) ? 6 : 0x17); return legAnimation.Advance( carryover * cycle * time_scale / walkStrideLength, 1); case 10: case 0xc: if ( demand < reverseSpeedMax && (cycle - cycle_rate * carryover) < reverseSpeedMax ) { plain_next = 0xf; break; } SetLegAnimation(0xd); return legAnimation.Advance( carryover * cycle * time_scale / reverseStrideLength, 1); case 0xb: case 0xd: if ( demand < reverseSpeedMax && (cycle - cycle_rate * carryover) < reverseSpeedMax ) { plain_next = 0xe; break; } SetLegAnimation(0xc); return legAnimation.Advance( carryover * cycle * time_scale / reverseStrideLength, 1); case 0x10: case 0x12: if ( demand > gimpSpeedMax && (gimpCycleRate * carryover + cycle) > gimpSpeedMax ) { plain_next = 0x15; break; } SetLegAnimation(0x13); { Scalar cycle_time = carryover * cycle * time_scale / gimpStrideLength; if (cycle_time <= 0.0f) { cycle_time = -cycle_time; } return legAnimation.Advance(cycle_time, 1); } case 0x11: case 0x13: if ( demand > gimpSpeedMax && (gimpCycleRate * carryover + cycle) > gimpSpeedMax ) { plain_next = 0x14; break; } SetLegAnimation(0x12); { Scalar cycle_time = carryover * cycle * time_scale / gimpStrideLength; if (cycle_time <= 0.0f) { cycle_time = -cycle_time; } return legAnimation.Advance(cycle_time, 1); } // // The limp figures themselves: keep cycling while the demand holds, // exit toward standing when it drops. // case 0x16: case 0x18: if ( demand < gimpLeftSpeedMax && (cycle - cycle_rate * carryover) < gimpLeftSpeedMax ) { plain_next = 0x1a; break; } SetLegAnimation(0x18); return legAnimation.Advance( carryover * cycle * time_scale / gimpLeftStrideLength, 1); case 0x17: case 0x19: if ( demand < gimpRightSpeedMax && (cycle - cycle_rate * carryover) < gimpRightSpeedMax ) { plain_next = 0x1b; break; } SetLegAnimation(0x19); return legAnimation.Advance( carryover * cycle * time_scale / gimpRightStrideLength, 1); default: return 0.0f; } // // The plain tail every non-cycle exit lands in. // SetLegAnimation(plain_next); return legAnimation.Advance(carryover * time_scale, 1); } Scalar Mech::GimpBodyClipFinished(Scalar carryover, int move_joints) { Check(this); int mode = MovementMode(), state = (int)bodyStateAlarm.GetLevel(); if ( state == 6 || state == 7 || state == 0xc || state == 0xd || state == 0x12 || state == 0x13 ) { Scalar cap = (mode == 3) ? gimpLeftSpeedMax : gimpRightSpeedMax; if (bodyTargetSpeed > cap) { bodyTargetSpeed = cap; } if (bodyTargetSpeed < 0.0f) { bodyTargetSpeed = 0.0f; } } Scalar demand = bodyTargetSpeed, cycle_rate = forwardCycleRate, time_scale = globalTimeScale, cycle = bodyCycleSpeed; int plain_next; switch (state) { case 2: bodyStateAlarm.SetLevel(1); return 0.0f; case 3: case 8: case 9: case 0x14: case 0x15: case 0x1a: case 0x1b: case 4: case 0x20: bodyStateAlarm.SetLevel(0); return 0.0f; case 5: case 6: case 0xe: if ( demand < standSpeed && (cycle - cycle_rate * carryover) < standSpeed ) { plain_next = 9; break; } if ( demand > walkStrideLength && (cycle + cycle_rate * carryover) > walkStrideLength ) { plain_next = 0xb; break; } SetBodyAnimation((mode == 4) ? 7 : 0x16); return bodyAnimation.Advance( carryover * cycle * time_scale / walkStrideLength, move_joints); case 7: case 0xf: if ( demand < standSpeed && (cycle - cycle_rate * carryover) < standSpeed ) { plain_next = 8; break; } if ( demand > walkStrideLength && (cycle + cycle_rate * carryover) > walkStrideLength ) { plain_next = 10; break; } SetBodyAnimation((mode == 3) ? 6 : 0x17); return bodyAnimation.Advance( carryover * cycle * time_scale / walkStrideLength, move_joints); case 10: case 0xc: if ( demand < reverseSpeedMax && (cycle - cycle_rate * carryover) < reverseSpeedMax ) { plain_next = 0xf; break; } SetBodyAnimation(0xd); return bodyAnimation.Advance( carryover * cycle * time_scale / reverseStrideLength, move_joints); case 0xb: case 0xd: if ( demand < reverseSpeedMax && (cycle - cycle_rate * carryover) < reverseSpeedMax ) { plain_next = 0xe; break; } SetBodyAnimation(0xc); return bodyAnimation.Advance( carryover * cycle * time_scale / reverseStrideLength, move_joints); case 0x10: case 0x12: if ( demand > gimpSpeedMax && (gimpCycleRate * carryover + cycle) > gimpSpeedMax ) { plain_next = 0x15; break; } SetBodyAnimation(0x13); { Scalar cycle_time = carryover * cycle * time_scale / gimpStrideLength; if (cycle_time <= 0.0f) { cycle_time = -cycle_time; } return bodyAnimation.Advance(cycle_time, move_joints); } case 0x11: case 0x13: if ( demand > gimpSpeedMax && (gimpCycleRate * carryover + cycle) > gimpSpeedMax ) { plain_next = 0x14; break; } SetBodyAnimation(0x12); { Scalar cycle_time = carryover * cycle * time_scale / gimpStrideLength; if (cycle_time <= 0.0f) { cycle_time = -cycle_time; } return bodyAnimation.Advance(cycle_time, move_joints); } case 0x16: case 0x18: if ( demand < gimpLeftSpeedMax && (cycle - cycle_rate * carryover) < gimpLeftSpeedMax ) { plain_next = 0x1a; break; } SetBodyAnimation(0x18); return bodyAnimation.Advance( carryover * cycle * time_scale / gimpLeftStrideLength, move_joints); case 0x17: case 0x19: if ( demand < gimpRightSpeedMax && (cycle - cycle_rate * carryover) < gimpRightSpeedMax ) { plain_next = 0x1b; break; } SetBodyAnimation(0x19); return bodyAnimation.Advance( carryover * cycle * time_scale / gimpRightStrideLength, move_joints); default: return 0.0f; } SetBodyAnimation(plain_next); return bodyAnimation.Advance(carryover * time_scale, move_joints); } // //############################################################################# // THE LIMP ADVANCERS (@004a71f4 leg / @004a5bf8 body). Selected instead of // the normal pair while limping, and structurally different in exactly the // ways a limp needs: // // * States 0x16-0x1b are LIVE here -- the limp entries advance in the // plain group and the 0x18/0x19 figures get their own slewed cycles with // the damaged side's caps. (The NORMAL advancers treat those states as // the reset group, which is why these flavours must be selected while // limping -- the normal one would neutralize the figure mid-cycle.) // // * No death latch and no wind-down: the movement modes are exclusive, so // a limping mech is by definition not falling. // // * The same demand clamp as the transition machines, applied per frame. //############################################################################# // Scalar Mech::AdvanceLegAnimationGimp(Scalar time_slice) { Check(this); MechControlsMapper *mapper = (MechControlsMapper *)subsystemArray[0]; Check_Pointer(mapper); int mode = MovementMode(), state = (int)legStateAlarm.GetLevel(); if ( state == 6 || state == 7 || state == 0xc || state == 0xd || state == 0x12 || state == 0x13 ) { Scalar cap = (mode == 3) ? gimpLeftSpeedMax : gimpRightSpeedMax, clamped = mapper->GetSpeedDemand(); if (clamped > cap) { clamped = cap; } if (clamped < 0.0f) { clamped = 0.0f; } mapper->SetSpeedDemand(clamped); } Scalar demand = mapper->GetSpeedDemand(), distance = 0.0f; switch (legStateAlarm.GetLevel()) { case 0: if (demand <= standSpeed) { break; } SetLegAnimation(5); // fall through case 2: case 3: case 5: case 8: case 9: case 10: case 0xb: case 0xe: case 0xf: case 0x10: case 0x11: case 0x14: case 0x15: case 0x16: case 0x17: case 0x1a: case 0x1b: case 0x20: advance_clip: distance = legAnimation.Advance( time_slice * globalTimeScale * idleStrideScale, 1); legCycleSpeed = distance / time_slice; break; case 1: break; case 4: if (standSpeed < demand) { legStateAlarm.SetLevel(0); ForceUpdate(8); break; } goto advance_clip; case 6: case 7: if (demand > legCycleSpeed) { legCycleSpeed += forwardCycleRate * time_slice; if (legCycleSpeed > demand) { legCycleSpeed = demand; } if (legCycleSpeed > walkStrideLength) { legCycleSpeed = walkStrideLength; } } else if (demand < legCycleSpeed) { legCycleSpeed -= forwardCycleRate * time_slice; if (legCycleSpeed < demand) { legCycleSpeed = demand; } if (legCycleSpeed < standSpeed) { legCycleSpeed = standSpeed; } } distance = legAnimation.Advance( time_slice * (legCycleSpeed / walkStrideLength) * globalTimeScale, 1); break; case 0xc: case 0xd: if (demand > legCycleSpeed) { legCycleSpeed += forwardCycleRate * time_slice; if (legCycleSpeed > demand) { legCycleSpeed = demand; } if (legCycleSpeed > runSpeedMax) { legCycleSpeed = runSpeedMax; } } else if (demand < legCycleSpeed) { legCycleSpeed -= forwardCycleRate * time_slice; if (legCycleSpeed < demand) { legCycleSpeed = demand; } if (legCycleSpeed < reverseSpeedMax) { legCycleSpeed = reverseSpeedMax; } } distance = legAnimation.Advance( time_slice * (legCycleSpeed / reverseStrideLength) * globalTimeScale, 1); break; case 0x12: case 0x13: if (demand > legCycleSpeed) { legCycleSpeed += gimpCycleRate * time_slice; if (legCycleSpeed > demand) { legCycleSpeed = demand; } if (legCycleSpeed > gimpSpeedMax) { legCycleSpeed = gimpSpeedMax; } } else if (demand < legCycleSpeed) { legCycleSpeed -= gimpCycleRate * time_slice; if (legCycleSpeed < demand) { legCycleSpeed = demand; } if (legCycleSpeed < gimpStrideLength) { legCycleSpeed = gimpStrideLength; } } { Scalar ratio = legCycleSpeed / gimpStrideLength; if (ratio <= 0.0f) { ratio = -ratio; } distance = legAnimation.Advance( ratio * time_slice * globalTimeScale, 1); } break; // // The limp figures: slewed like a walk, inside the damaged side's caps. // case 0x18: case 0x19: { Scalar speed_cap = (mode == 3) ? gimpLeftSpeedMax : gimpRightSpeedMax, stride = (mode == 3) ? gimpLeftStrideLength : gimpRightStrideLength; if (demand > legCycleSpeed) { legCycleSpeed += forwardCycleRate * time_slice; if (legCycleSpeed > demand) { legCycleSpeed = demand; } if (legCycleSpeed > stride) { legCycleSpeed = stride; } } else if (demand < legCycleSpeed) { legCycleSpeed -= forwardCycleRate * time_slice; if (legCycleSpeed < demand) { legCycleSpeed = demand; } if (legCycleSpeed < speed_cap) { legCycleSpeed = speed_cap; } } distance = legAnimation.Advance( time_slice * (legCycleSpeed / stride) * globalTimeScale, 1); } break; default: Fail("Unsupported mech animation"); } return distance; } Scalar Mech::AdvanceBodyAnimationGimp(Scalar time_slice, int move_joints) { Check(this); int mode = MovementMode(), state = (int)bodyStateAlarm.GetLevel(); if ( state == 6 || state == 7 || state == 0xc || state == 0xd || state == 0x12 || state == 0x13 ) { Scalar cap = (mode == 3) ? gimpLeftSpeedMax : gimpRightSpeedMax; if (bodyTargetSpeed > cap) { bodyTargetSpeed = cap; } if (bodyTargetSpeed < 0.0f) { bodyTargetSpeed = 0.0f; } } Scalar demand = bodyTargetSpeed, distance = 0.0f; switch (bodyStateAlarm.GetLevel()) { case 0: if (demand <= standSpeed) { break; } SetBodyAnimation(5); // fall through case 2: case 3: case 4: case 5: case 8: case 9: case 10: case 0xb: case 0xe: case 0xf: case 0x10: case 0x11: case 0x14: case 0x15: case 0x16: case 0x17: case 0x1a: case 0x1b: case 0x20: distance = bodyAnimation.Advance( time_slice * globalTimeScale * idleStrideScale, move_joints); bodyCycleSpeed = distance / time_slice; break; case 1: break; case 6: case 7: if (demand > bodyCycleSpeed) { bodyCycleSpeed += forwardCycleRate * time_slice; if (bodyCycleSpeed > demand) { bodyCycleSpeed = demand; } if (bodyCycleSpeed > walkStrideLength) { bodyCycleSpeed = walkStrideLength; } } else if (demand < bodyCycleSpeed) { bodyCycleSpeed -= forwardCycleRate * time_slice; if (bodyCycleSpeed < demand) { bodyCycleSpeed = demand; } if (bodyCycleSpeed < standSpeed) { bodyCycleSpeed = standSpeed; } } distance = bodyAnimation.Advance( time_slice * (bodyCycleSpeed / walkStrideLength) * globalTimeScale, move_joints); break; case 0xc: case 0xd: if (demand > bodyCycleSpeed) { bodyCycleSpeed += forwardCycleRate * time_slice; if (bodyCycleSpeed > demand) { bodyCycleSpeed = demand; } if (bodyCycleSpeed > runSpeedMax) { bodyCycleSpeed = runSpeedMax; } } else if (demand < bodyCycleSpeed) { bodyCycleSpeed -= forwardCycleRate * time_slice; if (bodyCycleSpeed < demand) { bodyCycleSpeed = demand; } if (bodyCycleSpeed < reverseSpeedMax) { bodyCycleSpeed = reverseSpeedMax; } } distance = bodyAnimation.Advance( time_slice * (bodyCycleSpeed / reverseStrideLength) * globalTimeScale, move_joints); break; case 0x12: case 0x13: if (demand > bodyCycleSpeed) { bodyCycleSpeed += gimpCycleRate * time_slice; if (bodyCycleSpeed > demand) { bodyCycleSpeed = demand; } if (bodyCycleSpeed > gimpSpeedMax) { bodyCycleSpeed = gimpSpeedMax; } } else if (demand < bodyCycleSpeed) { bodyCycleSpeed -= gimpCycleRate * time_slice; if (bodyCycleSpeed < demand) { bodyCycleSpeed = demand; } if (bodyCycleSpeed < gimpStrideLength) { bodyCycleSpeed = gimpStrideLength; } } { Scalar ratio = bodyCycleSpeed / gimpStrideLength; if (ratio <= 0.0f) { ratio = -ratio; } distance = bodyAnimation.Advance( ratio * time_slice * globalTimeScale, move_joints); } break; case 0x18: case 0x19: { Scalar speed_cap = (mode == 3) ? gimpLeftSpeedMax : gimpRightSpeedMax, stride = (mode == 3) ? gimpLeftStrideLength : gimpRightStrideLength; if (demand > bodyCycleSpeed) { bodyCycleSpeed += forwardCycleRate * time_slice; if (bodyCycleSpeed > demand) { bodyCycleSpeed = demand; } if (bodyCycleSpeed > stride) { bodyCycleSpeed = stride; } } else if (demand < bodyCycleSpeed) { bodyCycleSpeed -= forwardCycleRate * time_slice; if (bodyCycleSpeed < demand) { bodyCycleSpeed = demand; } if (bodyCycleSpeed < speed_cap) { bodyCycleSpeed = speed_cap; } } distance = bodyAnimation.Advance( time_slice * (bodyCycleSpeed / stride) * globalTimeScale, move_joints); } break; default: Fail("Unsupported mech animation"); } return distance; } // //############################################################################# // @004a7f50 -- prefix + suffix -> the clip's resource ID. // // The clip names are the model's 3-char animation prefix with a 3-char gait // suffix appended ("mad" + "wwr" = madwwr), resolved by name over the // animation resources. Returns a pointer to the found description's // resourceID; NULL when the model has no such clip -- which is a REAL case // (the limp set is optional), so callers must tolerate it. //############################################################################# // ResourceDescription::ResourceID * Mech::ResolveAnimationClip(const char *prefix, const char *suffix) { Check(this); Check_Pointer(prefix); Check_Pointer(suffix); char clip_name[12]; strcpy(clip_name, prefix); strcat(clip_name, suffix); ResourceDescription *description = application->GetResourceFile()->FindResourceDescription( clip_name, ResourceDescription::AnimationResourceType, ResourceDescription::NullResourceID); return (description != NULL) ? &description->resourceID : NULL; } // //############################################################################# // @004a8054 -- bind the clip at animationClips[slot] into the leg channel and // integrate its keyframe strides. Returns (via the out parameters) the total // cycle distance and the final keyframe time; the loader divides total by // time to recover a cycle speed. // // The callback is NULL on purpose: measurement only ever PARSES the clip // (SelectSequence), it never plays it, so the finished callback can never // fire. The binary passes a live pointer here; NULL is behaviourally // identical and avoids arming a transition machine mid-load. [T3] //############################################################################# // void Mech::MeasureClipStride(int slot, Scalar *total, Scalar *last_key) { Check(this); Verify(slot >= 0 && slot < AnimationSlotCount); legAnimation.SelectSequence(animationClips[slot], NULL, 0, 0); *total = 0.0f; *last_key = 0.0f; int frame; for (frame = 0; frame < legAnimation.keyframeCount; ++frame) { Scalar frame_time = legAnimation.keyframeTimes[frame]; *total += (frame_time - *last_key) * legAnimation.keyframeData[frame].stride; *last_key = frame_time; } } // //############################################################################# // @004a80d4 -- resolve and cache every gait clip, measuring the gait // constants from the clips themselves as it goes. This is where standSpeed, // walkStrideLength, reverseSpeedMax, reverseStrideLength, gimpSpeedMax and // gimpStrideLength actually COME FROM -- they are properties of the authored // animations, not authored numbers. // // Two binary behaviours reproduced deliberately; neither is a transcription // slip. See the sidecar before "fixing" either: // // * The speed caps read keyframeData[keyframeCount] -- one entry PAST the // last frame (the binary reads 0x690 + 8 + [0x670]*0xc). // // * The reverse-cycle stride divides the bbl measurement by STALE data: // both bbr and bbl are measured into the same pair, so the divide takes // its second terms from whatever the run cycle left behind. A 1995 // copy-paste bug, shipped, and therefore reproduced -- the walk and run // cycles above it show what was obviously intended. // // DIVERGENCE FROM THE BINARY, on purpose: the binary dereferences every // ResolveAnimationClip result unguarded -- a mech whose model lacks a // MANDATORY clip crashes on load. Here a miss stores NullResourceID (which // SelectSequence resolves to an empty, inert controller) and the dependent // measurement is skipped, leaving the bring-up default in place. [T3: keeps // the current boot alive on models whose clip sets have not been verified; // revisit once every fleet mech is known-good.] //############################################################################# // // // Resolve one slot: store the clip ID or NullResourceID. Returns whether the // clip exists, so dependent measurements can be skipped on a miss. // int Mech::LoadClipSlot(int slot, const char *prefix, const char *suffix) { ResourceDescription::ResourceID *clip_ID = ResolveAnimationClip(prefix, suffix); animationClips[slot] = (clip_ID != NULL) ? *clip_ID : ResourceDescription::NullResourceID; return clip_ID != NULL; } void Mech::LoadLocomotionClips(ModelResource *model) { Check(this); Check_Pointer(model); const char *prefix = model->animationPrefix; // // Zero-initialized because the guarded skips below can reach the reverse // divide with the run pair unmeasured -- a path the (unguarded) binary // does not have, so the stale-pair reproduction must not become an // uninitialized read on top of it. // Scalar total_a = 0.0f, last_a = 0.0f, total_b = 0.0f, last_b = 0.0f; gyroRumbleTimer = 0.0f; // // Stand -> walk. standSpeed is the clip's final-entry stride. // if (LoadClipSlot(5, prefix, "swr")) { legAnimation.SelectSequence(animationClips[5], NULL, 0, 0); standSpeed = legAnimation.keyframeData[legAnimation.keyframeCount].stride; } // // The forward walk cycle: stride = (s6 + s7) / (d6 + d7). // if ( LoadClipSlot(6, prefix, "wwr") && LoadClipSlot(7, prefix, "wwl") ) { MeasureClipStride(6, &total_a, &last_a); MeasureClipStride(7, &total_b, &last_b); walkStrideLength = (total_a + total_b) / (last_a + last_b); } LoadClipSlot(8, prefix, "wsr"); LoadClipSlot(9, prefix, "wsl"); // // Walk -> run. reverseSpeedMax is measured from wrr the same way // standSpeed is from swr. // if (LoadClipSlot(10, prefix, "wrr")) { legAnimation.SelectSequence(animationClips[10], NULL, 0, 0); reverseSpeedMax = legAnimation.keyframeData[legAnimation.keyframeCount].stride; } LoadClipSlot(11, prefix, "wrl"); // // The run cycle. // if ( LoadClipSlot(12, prefix, "rrr") && LoadClipSlot(13, prefix, "rrl") ) { MeasureClipStride(12, &total_a, &last_a); MeasureClipStride(13, &total_b, &last_b); reverseStrideLength = (total_a + total_b) / (last_a + last_b); } LoadClipSlot(14, prefix, "rwr"); LoadClipSlot(15, prefix, "rwl"); // // The bump/crash stagger clip, slot 0x20 -- the reason the clip array is // bigger than the state-name table. // LoadClipSlot(0x20, prefix, "bmp"); // // The reverse set. gimpSpeedMax is measured from the entry clip; the // cycle stride divide below reproduces the binary's stale-pair bug (see // the header comment) and is negated exactly where the binary negates. // if (LoadClipSlot(16, prefix, "sbr")) { legAnimation.SelectSequence(animationClips[16], NULL, 0, 0); gimpSpeedMax = legAnimation.keyframeData[legAnimation.keyframeCount].stride; } LoadClipSlot(17, prefix, "sbl"); LoadClipSlot(20, prefix, "bsr"); LoadClipSlot(21, prefix, "bsl"); if ( LoadClipSlot(18, prefix, "bbr") && LoadClipSlot(19, prefix, "bbl") ) { MeasureClipStride(18, &total_a, &last_a); MeasureClipStride(19, &total_a, &last_a); // the binary's stale pair: // total_b/last_b still hold // the run-cycle figures gimpStrideLength = (total_a + total_b) / (last_a + last_b); gimpStrideLength = -gimpStrideLength; } // // The OPTIONAL limp set. Probe for wgl; a model without it has no limp // clips at all, and the limp machine must never be entered for it. // hasGimpClips = 0; if (ResolveAnimationClip(prefix, "wgl") != NULL) { hasGimpClips = 1; if (LoadClipSlot(22, prefix, "wgl")) { legAnimation.SelectSequence(animationClips[22], NULL, 0, 0); gimpLeftSpeedMax = legAnimation.keyframeData[legAnimation.keyframeCount].stride; } if (LoadClipSlot(23, prefix, "wgr")) { legAnimation.SelectSequence(animationClips[23], NULL, 0, 0); gimpRightSpeedMax = legAnimation.keyframeData[legAnimation.keyframeCount].stride; } if (LoadClipSlot(24, prefix, "ggr")) { MeasureClipStride(24, &total_a, &last_a); gimpLeftStrideLength = total_a / last_a; } if (LoadClipSlot(25, prefix, "ggl")) { MeasureClipStride(25, &total_a, &last_a); gimpRightStrideLength = total_a / last_a; } LoadClipSlot(26, prefix, "gsl"); LoadClipSlot(27, prefix, "gsr"); } Check_Fpu(); }