Game Development Community

TGEA 1.7.1 (and all prev + TGE) Recoil broken + fix

by Fyodor -bank- Osokin · in Torque Game Engine Advanced · 06/19/2008 (5:17 am) · 6 replies

Hello.
I've just found that weapon "recoil" is broken.
Not completely, but in case you specify more than 1 recoil animation in the PlayerData, it will always pick up the last specified.
Look at this code (I'm taking the 1.7.1 but it's the same with all TGE and TGEA releases):
// Recoil thread. The server player does not play this animation.
   mRecoilThread = 0;
   if (isGhost())
      for (U32 s = 0; s < PlayerData::NumRecoilSequences; s++)
         if (mDataBlock->recoilSequence[s] != -1) {
            mRecoilThread = mShapeInstance->addThread();
            mShapeInstance->setSequence(mRecoilThread,mDataBlock->recoilSequence[s],0);
            mShapeInstance->setTimeScale(mRecoilThread,0);
         }
The mRecoilThread being set by ::onNewDataBlock and never changes.
If you have multiple recoils (upto three in stock engine) it will set the mRecoilThread to the thread associated with last recoil specified in datablock.
So, using the following code:
datablock TSShapeConstructor(testPlayerDTS)
{
   baseShape = "./testPlayer.dts";
   ...
   sequence[x] = "./recoil1.dsq light_recoil";
   sequence[x+1] = "./recoil2.dsq medium_recoil";
   sequence[x+2] = "./recoil3.dsq heavy_recoil";
   ...
};
Will make your player to always use "heavy_recoil" animation. Of course, if the ShapeBaseImageData's recoil state set to something but not the "NoRecoil".

If you look further into the code, we can see that this code:
void Player::onImageRecoil(U32,ShapeBaseImageData::StateData::RecoilState)
{
   if (mRecoilThread)
   {
      mShapeInstance->setPos(mRecoilThread,0);
      mShapeInstance->setTimeScale(mRecoilThread,1);
   }
}
does not use the passed recoil state at all!!

So, for me, the easiest (and fastest) "fix" was making separate threads for every recoil state.

See next post for code.

#1
06/19/2008 (5:18 am)
Patch for fixing that (based on stock TGEA 1.7.1 SDK)
Index: engine/source/T3D/player.cpp
===================================================================
--- engine/source/T3D/player.cpp	(revision 1)
+++ engine/source/T3D/player.cpp	(working copy)
@@ -846,7 +846,9 @@
    mHead = delta.head;
    mVelocity.set(0.0f, 0.0f, 0.0f);
    mDataBlock = 0;
-   mHeadHThread = mHeadVThread = mRecoilThread = 0;
+   mHeadHThread = mHeadVThread = 0;
+   for(S32 s=0;s<PlayerData::NumRecoilSequences;s++)
+      mRecoilThread[s] = 0;
    mArmAnimation.action = PlayerData::NullAnimation;
    mArmAnimation.thread = 0;
    mActionAnimation.action = PlayerData::NullAnimation;
@@ -1054,13 +1056,13 @@
       mHeadHThread = 0;
 
    // Recoil thread. The server player does not play this animation.
-   mRecoilThread = 0;
+   //mRecoilThread = 0;
    if (isGhost())
       for (U32 s = 0; s < PlayerData::NumRecoilSequences; s++)
          if (mDataBlock->recoilSequence[s] != -1) {
-            mRecoilThread = mShapeInstance->addThread();
-            mShapeInstance->setSequence(mRecoilThread,mDataBlock->recoilSequence[s],0);
-            mShapeInstance->setTimeScale(mRecoilThread,0);
+            mRecoilThread[s] = mShapeInstance->addThread();
+            mShapeInstance->setSequence(mRecoilThread[s],mDataBlock->recoilSequence[s],0);
+            mShapeInstance->setTimeScale(mRecoilThread[s],0);
          }
 
    // Initialize the primary thread, the actual sequence is
@@ -2479,12 +2481,12 @@
    setActionThread(action,forward,false,false);
 }
 
-void Player::onImageRecoil(U32,ShapeBaseImageData::StateData::RecoilState)
+void Player::onImageRecoil(U32,ShapeBaseImageData::StateData::RecoilState state)
 {
-   if (mRecoilThread)
+   if (mRecoilThread[state-1])
    {
-      mShapeInstance->setPos(mRecoilThread,0);
-      mShapeInstance->setTimeScale(mRecoilThread,1);
+      mShapeInstance->setPos(mRecoilThread[state-1],0);
+      mShapeInstance->setTimeScale(mRecoilThread[state-1],1);
    }
 }
 
@@ -2511,8 +2513,11 @@
 {
    if ((isGhost() || mActionAnimation.animateOnServer) && mActionAnimation.thread)
       mShapeInstance->advanceTime(dt,mActionAnimation.thread);
-   if (mRecoilThread)
-      mShapeInstance->advanceTime(dt,mRecoilThread);
+   // Recoil thread. The server player does not play this animation.
+   if (isGhost())
+      for(S32 s=0;s<PlayerData::NumRecoilSequences;s++)
+         if (mRecoilThread[s])
+            mShapeInstance->advanceTime(dt,mRecoilThread[s]);
 
    // 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
Index: engine/source/T3D/player.h
===================================================================
--- engine/source/T3D/player.h	(revision 1)
+++ engine/source/T3D/player.h	(working copy)
@@ -356,7 +356,7 @@
 
    TSThread* mHeadVThread;
    TSThread* mHeadHThread;
-   TSThread* mRecoilThread;
+   TSThread* mRecoilThread[PlayerData::NumRecoilSequences];
    static Range mArmRange;
    static Range mHeadVRange;
    static Range mHeadHRange;

The same applied to the TGE SDK.
Confirmed to behave as expected without any affect to anything else (except, that now client processing all recoil threads - advanceTime used for all existed recoils, so it "eats" a bit of processor time, but that's not a big issue at all IMHO).
#2
06/19/2008 (5:20 am)
In Player::onImageRecoil I'm using state-1 because ShapeBaseImageData::StateData::RecoilState starts with NoRecoil=0, so we need to decrease the passed value by one to make it "the same level" as recoil stated on Player.

Hope this helps :)
#3
09/15/2009 (4:52 pm)
Same applies to T3D, only line numbers differs.
#6
06/15/2010 (7:00 pm)
Incredible that this has not yet been fixed in standard installs!
But nice fix anyway - that really explained some issues ive been having with different weapon poses and recoils!

nice!