浏览代码

anim-clip -- sequence selection by afx effects

Marc Chapman 8 年之前
父节点
当前提交
ab88b8f489

+ 65 - 11
Engine/source/T3D/aiPlayer.cpp

@@ -20,6 +20,11 @@
 // IN THE SOFTWARE.
 // IN THE SOFTWARE.
 //-----------------------------------------------------------------------------
 //-----------------------------------------------------------------------------
 
 
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
+// Copyright (C) 2015 Faust Logic, Inc.
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
 #include "platform/platform.h"
 #include "platform/platform.h"
 #include "T3D/aiPlayer.h"
 #include "T3D/aiPlayer.h"
 
 
@@ -97,6 +102,9 @@ AIPlayer::AIPlayer()
    mMoveSlowdown = true;
    mMoveSlowdown = true;
    mMoveState = ModeStop;
    mMoveState = ModeStop;
 
 
+   // This new member saves the movement state of the AI so that
+   // it can be restored after a substituted animation is finished.
+   mMoveState_saved = -1;
    mAimObject = 0;
    mAimObject = 0;
    mAimLocationSet = false;
    mAimLocationSet = false;
    mTargetInLOS = false;
    mTargetInLOS = false;
@@ -547,23 +555,27 @@ bool AIPlayer::getAIMove(Move *movePtr)
             mMoveState = ModeMove;
             mMoveState = ModeMove;
          }
          }
 
 
-         if (mMoveStuckTestCountdown > 0)
-            --mMoveStuckTestCountdown;
-         else
-         {
-            // We should check to see if we are stuck...
-            F32 locationDelta = (location - mLastLocation).len();
+         // Don't check for ai stuckness if animation during
+         // an anim-clip effect override.
+         if (mDamageState == Enabled && !(anim_clip_flags & ANIM_OVERRIDDEN) && !isAnimationLocked()) {
+	         if (mMoveStuckTestCountdown > 0)
+	            --mMoveStuckTestCountdown;
+	         else
+	         {
+	            // We should check to see if we are stuck...
+	            F32 locationDelta = (location - mLastLocation).len();
             if (locationDelta < mMoveStuckTolerance && mDamageState == Enabled) 
             if (locationDelta < mMoveStuckTolerance && mDamageState == Enabled) 
             {
             {
                // If we are slowing down, then it's likely that our location delta will be less than
                // If we are slowing down, then it's likely that our location delta will be less than
                // our move stuck tolerance. Because we can be both slowing and stuck
                // our move stuck tolerance. Because we can be both slowing and stuck
                // we should TRY to check if we've moved. This could use better detection.
                // we should TRY to check if we've moved. This could use better detection.
                if ( mMoveState != ModeSlowing || locationDelta == 0 )
                if ( mMoveState != ModeSlowing || locationDelta == 0 )
-               {
-                  mMoveState = ModeStuck;
-                  onStuck();
-               }
-            }
+	               {
+	                  mMoveState = ModeStuck;
+	                  onStuck();
+	               }
+	            }
+	         }
          }
          }
       }
       }
    }
    }
@@ -626,6 +638,7 @@ bool AIPlayer::getAIMove(Move *movePtr)
    }
    }
 #endif // TORQUE_NAVIGATION_ENABLED
 #endif // TORQUE_NAVIGATION_ENABLED
 
 
+   if (!(anim_clip_flags & ANIM_OVERRIDDEN) && !isAnimationLocked())
    mLastLocation = location;
    mLastLocation = location;
 
 
    return true;
    return true;
@@ -1415,6 +1428,47 @@ DefineEngineMethod( AIPlayer, clearMoveTriggers, void, ( ),,
    object->clearMoveTriggers();
    object->clearMoveTriggers();
 }
 }
 
 
+// These changes coordinate with anim-clip mods to parent class, Player.
+
+// New method, restartMove(), restores the AIPlayer to its normal move-state
+// following animation overrides from AFX. The tag argument is used to match
+// the latest override and prevents interruption of overlapping animation
+// overrides. See related anim-clip changes in Player.[h,cc].
+void AIPlayer::restartMove(U32 tag)
+{
+   if (tag != 0 && tag == last_anim_tag)
+   {
+      if (mMoveState_saved != -1)
+      {
+         mMoveState = (MoveState) mMoveState_saved;
+         mMoveState_saved = -1;
+      }
+
+      bool is_death_anim = ((anim_clip_flags & IS_DEATH_ANIM) != 0);
+
+      last_anim_tag = 0;
+      anim_clip_flags &= ~(ANIM_OVERRIDDEN | IS_DEATH_ANIM);
+
+      if (mDamageState != Enabled)
+      {
+         if (!is_death_anim)
+         {
+            // this is a bit hardwired and desperate,
+            // but if he's dead he needs to look like it.
+            setActionThread("death10", false, false, false);
+         }
+      }
+   }
+}
+
+// New method, saveMoveState(), stores the current movement state
+// so that it can be restored when restartMove() is called.
+void AIPlayer::saveMoveState()
+{
+   if (mMoveState_saved == -1)
+      mMoveState_saved = (S32) mMoveState;
+}
+
 F32 AIPlayer::getTargetDistance(GameBase* target, bool _checkEnabled)
 F32 AIPlayer::getTargetDistance(GameBase* target, bool _checkEnabled)
 {
 {
    if (!isServerObject()) return false;
    if (!isServerObject()) return false;

+ 17 - 0
Engine/source/T3D/aiPlayer.h

@@ -20,6 +20,11 @@
 // IN THE SOFTWARE.
 // IN THE SOFTWARE.
 //-----------------------------------------------------------------------------
 //-----------------------------------------------------------------------------
 
 
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
+// Copyright (C) 2015 Faust Logic, Inc.
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
 #ifndef _AIPLAYER_H_
 #ifndef _AIPLAYER_H_
 #define _AIPLAYER_H_
 #define _AIPLAYER_H_
 
 
@@ -225,6 +230,18 @@ public:
 
 
    /// @}
    /// @}
 #endif // TORQUE_NAVIGATION_ENABLED
 #endif // TORQUE_NAVIGATION_ENABLED
+   // New method, restartMove(), restores the AIPlayer to its normal move-state
+   // following animation overrides from AFX. The tag argument is used to match
+   // the latest override and prevents interruption of overlapping animation
+   // overrides.
+   // New method, saveMoveState(), stores the current movement state
+   // so that it can be restored when restartMove() is called.
+   // See related anim-clip changes in Player.[h,cc].
+private:
+   S32 mMoveState_saved;
+public:
+   void restartMove(U32 tag);
+   void saveMoveState();
 };
 };
 
 
 #endif
 #endif

+ 184 - 3
Engine/source/T3D/player.cpp

@@ -2746,7 +2746,12 @@ void Player::updateMove(const Move* move)
    // Desired move direction & speed
    // Desired move direction & speed
    VectorF moveVec;
    VectorF moveVec;
    F32 moveSpeed;
    F32 moveSpeed;
-   if ((mState == MoveState || (mState == RecoverState && mDataBlock->recoverRunForceScale > 0.0f)) && mDamageState == Enabled)
+   // If BLOCK_USER_CONTROL is set in anim_clip_flags, the user won't be able to
+   // resume control over the player character. This generally happens for
+   // short periods of time synchronized with script driven animation at places
+   // where it makes sense that user motion is prohibited, such as when the 
+   // player is lifted off the ground or knocked down.
+   if ((mState == MoveState || (mState == RecoverState && mDataBlock->recoverRunForceScale > 0.0f)) && mDamageState == Enabled && !isAnimationLocked())
    {
    {
       zRot.getColumn(0,&moveVec);
       zRot.getColumn(0,&moveVec);
       moveVec *= (move->x * (mPose == SprintPose ? mDataBlock->sprintStrafeScale : 1.0f));
       moveVec *= (move->x * (mPose == SprintPose ? mDataBlock->sprintStrafeScale : 1.0f));
@@ -3031,7 +3036,9 @@ void Player::updateMove(const Move* move)
       mContactTimer++;   
       mContactTimer++;   
 
 
    // Acceleration from Jumping
    // Acceleration from Jumping
-   if (move->trigger[sJumpTrigger] && canJump())// !isMounted() && 
+   // While BLOCK_USER_CONTROL is set in anim_clip_flags, the user won't be able to
+   // make the player character jump.
+   if (move->trigger[sJumpTrigger] && canJump() && !isAnimationLocked())
    {
    {
       // Scale the jump impulse base on maxJumpSpeed
       // Scale the jump impulse base on maxJumpSpeed
       F32 zSpeedScale = mVelocity.z;
       F32 zSpeedScale = mVelocity.z;
@@ -3539,6 +3546,8 @@ void Player::updateLookAnimation(F32 dt)
 
 
 bool Player::inDeathAnim()
 bool Player::inDeathAnim()
 {
 {
+   if ((anim_clip_flags & ANIM_OVERRIDDEN) != 0 && (anim_clip_flags & IS_DEATH_ANIM) == 0)
+      return false;
    if (mActionAnimation.thread && mActionAnimation.action >= 0)
    if (mActionAnimation.thread && mActionAnimation.action >= 0)
       if (mActionAnimation.action < mDataBlock->actionCount)
       if (mActionAnimation.action < mDataBlock->actionCount)
          return mDataBlock->actionList[mActionAnimation.action].death;
          return mDataBlock->actionList[mActionAnimation.action].death;
@@ -3748,6 +3757,8 @@ bool Player::setArmThread(U32 action)
 
 
 bool Player::setActionThread(const char* sequence,bool hold,bool wait,bool fsp)
 bool Player::setActionThread(const char* sequence,bool hold,bool wait,bool fsp)
 {
 {
+   if (anim_clip_flags & ANIM_OVERRIDDEN)
+      return false;
    for (U32 i = 1; i < mDataBlock->actionCount; i++)
    for (U32 i = 1; i < mDataBlock->actionCount; i++)
    {
    {
       PlayerData::ActionAnimation &anim = mDataBlock->actionList[i];
       PlayerData::ActionAnimation &anim = mDataBlock->actionList[i];
@@ -3947,8 +3958,10 @@ void Player::updateActionThread()
       pickActionAnimation();
       pickActionAnimation();
    }
    }
 
 
+   // prevent scaling of AFX picked actions
    if ( (mActionAnimation.action != PlayerData::LandAnim) &&
    if ( (mActionAnimation.action != PlayerData::LandAnim) &&
-        (mActionAnimation.action != PlayerData::NullAnimation) )
+        (mActionAnimation.action != PlayerData::NullAnimation) &&
+        !(anim_clip_flags & ANIM_OVERRIDDEN))
    {
    {
       // Update action animation time scale to match ground velocity
       // Update action animation time scale to match ground velocity
       PlayerData::ActionAnimation &anim =
       PlayerData::ActionAnimation &anim =
@@ -4566,6 +4579,10 @@ void Player::updateAnimation(F32 dt)
    if (mImageStateThread)
    if (mImageStateThread)
       mShapeInstance->advanceTime(dt,mImageStateThread);
       mShapeInstance->advanceTime(dt,mImageStateThread);
 
 
+   // update any active blend clips
+   if (isGhost())
+      for (S32 i = 0; i < blend_clips.size(); i++)
+         mShapeInstance->advanceTime(dt, blend_clips[i].thread);
    // If we are the client's player on this machine, then we need
    // If we are the client's player on this machine, then we need
    // to make sure the transforms are up to date as they are used
    // to make sure the transforms are up to date as they are used
    // to setup the camera.
    // to setup the camera.
@@ -4579,6 +4596,11 @@ void Player::updateAnimation(F32 dt)
       else
       else
       {
       {
          updateAnimationTree(false);
          updateAnimationTree(false);
+         // This addition forces recently visible players to animate their
+         // skeleton now rather than in pre-render so that constrained effects
+         // get up-to-date node transforms.
+         if (didRenderLastRender())
+            mShapeInstance->animate();
       }
       }
    }
    }
 }
 }
@@ -7249,6 +7271,165 @@ void Player::afx_unpackUpdate(NetConnection* con, BitStream* stream)
       mark_fx_c_triggers = mask;
       mark_fx_c_triggers = mask;
    }
    }
 }
 }
+
+// Code for overriding player's animation with sequences selected by the
+// anim-clip component effect.
+
+void Player::restoreAnimation(U32 tag)
+{
+   // check if this is a blended clip
+   if ((tag & BLENDED_CLIP) != 0)
+   {
+      restoreBlendAnimation(tag);
+      return;
+   }
+
+   if (tag != 0 && tag == last_anim_tag)
+   {
+      bool is_death_anim = ((anim_clip_flags & IS_DEATH_ANIM) != 0);
+
+      anim_clip_flags &= ~(ANIM_OVERRIDDEN | IS_DEATH_ANIM);
+
+      if (isClientObject())
+      {
+         if (mDamageState != Enabled)
+         {
+            if (!is_death_anim)
+            {
+               // this is a bit hardwired and desperate,
+               // but if he's dead he needs to look like it.
+               setActionThread("death10", false, false, false);
+            }
+         }
+         else if (mState != MoveState)
+         {
+            // not sure what happens here
+         }
+         else
+         {
+            pickActionAnimation();
+         }
+      }
+
+      last_anim_tag = 0;
+      last_anim_id = -1;
+   }
+}
+
+U32 Player::getAnimationID(const char* name)
+{
+   for (U32 i = 0; i < mDataBlock->actionCount; i++)
+   {
+      PlayerData::ActionAnimation &anim = mDataBlock->actionList[i];
+      if (dStricmp(anim.name, name) == 0)
+         return i;
+   }
+
+   Con::errorf("Player::getAnimationID() -- Player does not contain a sequence that matches the name, %s.", name);
+   return BAD_ANIM_ID;
+}
+
+U32 Player::playAnimationByID(U32 anim_id, F32 pos, F32 rate, F32 trans, bool hold, bool wait, bool is_death_anim)
+{
+   if (anim_id == BAD_ANIM_ID)
+      return 0;
+
+   S32 seq_id = mDataBlock->actionList[anim_id].sequence;
+   if (seq_id == -1)
+   {
+      Con::errorf("Player::playAnimation() problem. BAD_SEQ_ID");
+      return 0;
+   }
+
+   if (mShapeInstance->getShape()->sequences[seq_id].isBlend())
+      return playBlendAnimation(seq_id, pos, rate);
+
+   if (isClientObject())
+   {
+      PlayerData::ActionAnimation &anim = mDataBlock->actionList[anim_id];
+      if (anim.sequence != -1) 
+      {
+         mActionAnimation.action          = anim_id;
+         mActionAnimation.forward         = (rate >= 0);
+         mActionAnimation.firstPerson     = false;
+         mActionAnimation.holdAtEnd       = hold;
+         mActionAnimation.waitForEnd      = hold? true: wait;
+         mActionAnimation.animateOnServer = false;
+         mActionAnimation.atEnd           = false;
+         mActionAnimation.delayTicks      = (S32)sNewAnimationTickTime;
+
+         F32 transTime = (trans < 0) ? sAnimationTransitionTime : trans;  
+
+         mShapeInstance->setTimeScale(mActionAnimation.thread, rate);
+         mShapeInstance->transitionToSequence(mActionAnimation.thread,anim.sequence,
+            pos, transTime, true);
+      }
+   }
+
+   if (is_death_anim)
+      anim_clip_flags |= IS_DEATH_ANIM;
+   else
+      anim_clip_flags &= ~IS_DEATH_ANIM;
+
+   anim_clip_flags |= ANIM_OVERRIDDEN;
+   last_anim_tag = unique_anim_tag_counter++;
+   last_anim_id = anim_id;
+
+   return last_anim_tag;
+}
+
+F32 Player::getAnimationDurationByID(U32 anim_id)
+{
+   if (anim_id == BAD_ANIM_ID)
+      return 0.0f;
+   S32 seq_id = mDataBlock->actionList[anim_id].sequence;
+   if (seq_id >= 0 && seq_id < mDataBlock->mShape->sequences.size())
+      return mDataBlock->mShape->sequences[seq_id].duration;
+
+   return 0.0f;
+}
+
+bool Player::isBlendAnimation(const char* name)
+{
+   U32 anim_id = getAnimationID(name);
+   if (anim_id == BAD_ANIM_ID)
+      return false;
+
+   S32 seq_id = mDataBlock->actionList[anim_id].sequence;
+   if (seq_id >= 0 && seq_id < mDataBlock->mShape->sequences.size())
+      return mDataBlock->mShape->sequences[seq_id].isBlend();
+
+   return false;
+}
+
+const char* Player::getLastClipName(U32 clip_tag)
+{ 
+   if (clip_tag != last_anim_tag || last_anim_id >= PlayerData::NumActionAnims)
+      return "";
+
+   return mDataBlock->actionList[last_anim_id].name;
+}
+
+void Player::unlockAnimation(U32 tag, bool force)
+{
+   if ((tag != 0 && tag == last_anim_lock_tag) || force)
+      anim_clip_flags &= ~BLOCK_USER_CONTROL;
+}
+
+U32 Player::lockAnimation()
+{
+   anim_clip_flags |= BLOCK_USER_CONTROL;
+   last_anim_lock_tag = unique_anim_tag_counter++;
+
+   return last_anim_lock_tag;
+}
+
+ConsoleMethod(Player, isAnimationLocked, bool, 2, 2, "isAnimationLocked()")
+{
+   return object->isAnimationLocked();
+}
+
+
 #ifdef TORQUE_OPENVR
 #ifdef TORQUE_OPENVR
 void Player::setControllers(Vector<OpenVRTrackedObject*> controllerList)
 void Player::setControllers(Vector<OpenVRTrackedObject*> controllerList)
 {
 {

+ 11 - 0
Engine/source/T3D/player.h

@@ -793,6 +793,17 @@ private:
 private:
 private:
    static bool  sCorpsesHiddenFromRayCast;
    static bool  sCorpsesHiddenFromRayCast;
    
    
+public:
+   virtual void restoreAnimation(U32 tag);
+   virtual U32 getAnimationID(const char* name);
+   virtual U32 playAnimationByID(U32 anim_id, F32 pos, F32 rate, F32 trans, bool hold, bool wait, bool is_death_anim);
+   virtual F32 getAnimationDurationByID(U32 anim_id);
+   virtual bool isBlendAnimation(const char* name);
+   virtual const char* getLastClipName(U32 clip_tag);
+   virtual void unlockAnimation(U32 tag, bool force=false);
+   virtual U32 lockAnimation();
+   virtual bool isAnimationLocked() const { return ((anim_clip_flags & BLOCK_USER_CONTROL) != 0); }
+   
 };
 };
 
 
 typedef Player::Pose PlayerPose;
 typedef Player::Pose PlayerPose;

+ 197 - 0
Engine/source/T3D/shapeBase.cpp

@@ -1045,6 +1045,13 @@ ShapeBase::ShapeBase()
 
 
    for (i = 0; i < MaxTriggerKeys; i++)
    for (i = 0; i < MaxTriggerKeys; i++)
       mTrigger[i] = false;
       mTrigger[i] = false;
+   anim_clip_flags = 0;
+   last_anim_id = -1;
+   last_anim_tag = 0;         
+   last_anim_lock_tag = 0;
+   saved_seq_id = -1;
+   saved_pos = 0.0f;
+   saved_rate = 1.0f;
 }
 }
 
 
 
 
@@ -1183,6 +1190,16 @@ void ShapeBase::onSceneRemove()
 
 
 bool ShapeBase::onNewDataBlock( GameBaseData *dptr, bool reload )
 bool ShapeBase::onNewDataBlock( GameBaseData *dptr, bool reload )
 {
 {
+   // need to destroy blend-clips or we crash
+   if (isGhost())
+   {
+      for (S32 i = 0; i < blend_clips.size(); i++)
+      {
+         if (blend_clips[i].thread)
+            mShapeInstance->destroyThread(blend_clips[i].thread);
+         blend_clips.erase_fast(i);
+      }
+   }
    ShapeBaseData *prevDB = dynamic_cast<ShapeBaseData*>( mDataBlock );
    ShapeBaseData *prevDB = dynamic_cast<ShapeBaseData*>( mDataBlock );
 
 
    bool isInitialDataBlock = ( mDataBlock == 0 );
    bool isInitialDataBlock = ( mDataBlock == 0 );
@@ -5101,6 +5118,186 @@ DefineEngineMethod( ShapeBase, getModelFile, const char *, (),,
    return datablock->getDataField( fieldName, NULL );
    return datablock->getDataField( fieldName, NULL );
 }
 }
 
 
+
+U32 ShapeBase::unique_anim_tag_counter = 1;
+
+U32 ShapeBase::playBlendAnimation(S32 seq_id, F32 pos, F32 rate)
+{
+   BlendThread blend_clip; 
+   blend_clip.tag = ((unique_anim_tag_counter++) | BLENDED_CLIP);
+   blend_clip.thread = 0;
+
+   if (isClientObject())
+   {
+      blend_clip.thread = mShapeInstance->addThread();
+      mShapeInstance->setSequence(blend_clip.thread, seq_id, pos);
+      mShapeInstance->setTimeScale(blend_clip.thread, rate);
+   }
+
+   blend_clips.push_back(blend_clip);
+
+   return blend_clip.tag;
+}
+
+void ShapeBase::restoreBlendAnimation(U32 tag)
+{
+   for (S32 i = 0; i < blend_clips.size(); i++)
+   {
+      if (blend_clips[i].tag == tag)
+      {
+         if (blend_clips[i].thread)
+         {
+            mShapeInstance->destroyThread(blend_clips[i].thread);
+         }
+         blend_clips.erase_fast(i);
+         break;
+      }
+   }
+}
+
+//
+
+void ShapeBase::restoreAnimation(U32 tag)
+{
+   if (!isClientObject())
+      return;
+
+   // check if this is a blended clip
+   if ((tag & BLENDED_CLIP) != 0)
+   {
+      restoreBlendAnimation(tag);
+      return;
+   }
+
+   if (tag != 0 && tag == last_anim_tag)
+   {
+      anim_clip_flags &= ~(ANIM_OVERRIDDEN | IS_DEATH_ANIM);
+
+      stopThread(0);
+
+      if (saved_seq_id != -1)
+      {
+         setThreadSequence(0, saved_seq_id);
+         setThreadPosition(0, saved_pos);
+         setThreadTimeScale(0, saved_rate);
+         setThreadDir(0, (saved_rate >= 0));
+         playThread(0);
+
+         saved_seq_id = -1;
+         saved_pos = 0.0f;
+         saved_rate = 1.0f;
+      }
+
+      last_anim_tag = 0;
+      last_anim_id = -1;
+   }
+}
+
+U32 ShapeBase::getAnimationID(const char* name)
+{
+   const TSShape* ts_shape = getShape();
+   S32 seq_id = (ts_shape) ? ts_shape->findSequence(name) : -1;
+   return (seq_id >= 0) ? (U32) seq_id : BAD_ANIM_ID;
+}
+
+U32 ShapeBase::playAnimationByID(U32 anim_id, F32 pos, F32 rate, F32 trans, bool hold, bool wait, bool is_death_anim)
+{
+   if (!isClientObject())
+      return 0;
+
+   if (anim_id == BAD_ANIM_ID)
+      return 0;
+
+   const TSShape* ts_shape = getShape();
+   if (!ts_shape)
+      return 0;
+
+   S32 seq_id = (S32) anim_id;
+   if (mShapeInstance->getShape()->sequences[seq_id].isBlend())
+      return playBlendAnimation(seq_id, pos, rate);
+
+   if (last_anim_tag == 0)
+   {
+      // try to save state of playing animation
+      Thread& st = mScriptThread[0];
+      if (st.sequence != -1)
+      {
+         saved_seq_id = st.sequence;
+         saved_pos = st.position;
+         saved_rate = st.timescale;
+      }
+   }
+
+   // START OR TRANSITION TO SEQUENCE HERE
+   setThreadSequence(0, seq_id);
+   setThreadPosition(0, pos);
+   setThreadTimeScale(0, rate);
+   setThreadDir(0, (rate >= 0));
+   playThread(0);
+
+   if (is_death_anim)
+      anim_clip_flags |= IS_DEATH_ANIM;
+   else
+      anim_clip_flags &= ~IS_DEATH_ANIM;
+
+   anim_clip_flags |= ANIM_OVERRIDDEN;
+   last_anim_tag = unique_anim_tag_counter++;
+   last_anim_id = anim_id;
+
+   return last_anim_tag;
+}
+
+F32 ShapeBase::getAnimationDurationByID(U32 anim_id)
+{
+   if (anim_id == BAD_ANIM_ID)
+      return 0.0f;
+
+   S32 seq_id = (S32) anim_id;
+   if (seq_id >= 0 && seq_id < mDataBlock->mShape->sequences.size())
+      return mDataBlock->mShape->sequences[seq_id].duration;
+
+   return 0.0f;
+}
+
+bool ShapeBase::isBlendAnimation(const char* name)
+{
+   U32 anim_id = getAnimationID(name);
+   if (anim_id == BAD_ANIM_ID)
+      return false;
+
+   S32 seq_id = (S32) anim_id;
+   if (seq_id >= 0 && seq_id < mDataBlock->mShape->sequences.size())
+      return mDataBlock->mShape->sequences[seq_id].isBlend();
+
+   return false;
+}
+
+const char* ShapeBase::getLastClipName(U32 clip_tag)
+{ 
+   if (clip_tag != last_anim_tag)
+      return "";
+
+   S32 seq_id = (S32) last_anim_id;
+
+   S32 idx = mDataBlock->mShape->sequences[seq_id].nameIndex;
+   if (idx < 0 || idx >= mDataBlock->mShape->names.size())
+      return 0;
+
+   return mDataBlock->mShape->names[idx];
+}
+
+//
+
+U32 ShapeBase::playAnimation(const char* name, F32 pos, F32 rate, F32 trans, bool hold, bool wait, bool is_death_anim)
+{
+   return playAnimationByID(getAnimationID(name), pos, rate, trans, hold, wait, is_death_anim);
+}
+
+F32 ShapeBase::getAnimationDuration(const char* name)
+{
+   return getAnimationDurationByID(getAnimationID(name));
+}
+
 void ShapeBase::setSelectionFlags(U8 flags)
 void ShapeBase::setSelectionFlags(U8 flags)
 {
 {
    Parent::setSelectionFlags(flags);
    Parent::setSelectionFlags(flags);

+ 37 - 0
Engine/source/T3D/shapeBase.h

@@ -1861,6 +1861,43 @@ public:
 protected:
 protected:
    DECLARE_CALLBACK( F32, validateCameraFov, (F32 fov) );
    DECLARE_CALLBACK( F32, validateCameraFov, (F32 fov) );
 
 
+protected:
+   enum { 
+      ANIM_OVERRIDDEN     = BIT(0),
+      BLOCK_USER_CONTROL  = BIT(1),
+      IS_DEATH_ANIM       = BIT(2),
+      BAD_ANIM_ID         = 999999999,
+      BLENDED_CLIP        = 0x80000000,
+   };
+   struct BlendThread
+   {
+      TSThread* thread;
+      U32       tag;
+   };
+   Vector<BlendThread> blend_clips;
+   static U32 unique_anim_tag_counter;
+   U8 anim_clip_flags;
+   S32 last_anim_id;
+   U32 last_anim_tag;
+   U32 last_anim_lock_tag;
+   S32 saved_seq_id;
+   F32 saved_pos;
+   F32 saved_rate;
+   U32 playBlendAnimation(S32 seq_id, F32 pos, F32 rate);
+   void restoreBlendAnimation(U32 tag);
+public:
+   U32 playAnimation(const char* name, F32 pos, F32 rate, F32 trans, bool hold, bool wait, bool is_death_anim);
+   F32 getAnimationDuration(const char* name);
+
+   virtual void restoreAnimation(U32 tag);
+   virtual U32 getAnimationID(const char* name);
+   virtual U32 playAnimationByID(U32 anim_id, F32 pos, F32 rate, F32 trans, bool hold, bool wait, bool is_death_anim);
+   virtual F32 getAnimationDurationByID(U32 anim_id);
+   virtual bool isBlendAnimation(const char* name);
+   virtual const char* getLastClipName(U32 clip_tag);
+   virtual void unlockAnimation(U32 tag, bool force=false) { }
+   virtual U32 lockAnimation() { return 0; }
+   virtual bool isAnimationLocked() const { return false; }
 
 
    virtual void setSelectionFlags(U8 flags);
    virtual void setSelectionFlags(U8 flags);
 };
 };