Эх сурвалжийг харах

Merge pull request #2056 from Bloodknight/afx_merge_main

Afx merge main
Areloch 8 жил өмнө
parent
commit
e023cf3a60
100 өөрчлөгдсөн 27317 нэмэгдсэн , 1454 устгасан
  1. 65 11
      Engine/source/T3D/aiPlayer.cpp
  2. 17 0
      Engine/source/T3D/aiPlayer.h
  3. 7 0
      Engine/source/T3D/camera.h
  4. 8 1
      Engine/source/T3D/containerQuery.cpp
  5. 104 4
      Engine/source/T3D/debris.cpp
  6. 16 0
      Engine/source/T3D/debris.h
  7. 144 6
      Engine/source/T3D/fx/explosion.cpp
  8. 17 0
      Engine/source/T3D/fx/explosion.h
  9. 16 1
      Engine/source/T3D/fx/fxFoliageReplicator.cpp
  10. 7 0
      Engine/source/T3D/fx/fxFoliageReplicator.h
  11. 181 20
      Engine/source/T3D/fx/particle.cpp
  12. 29 1
      Engine/source/T3D/fx/particle.h
  13. 395 30
      Engine/source/T3D/fx/particleEmitter.cpp
  14. 65 1
      Engine/source/T3D/fx/particleEmitter.h
  15. 39 2
      Engine/source/T3D/gameBase/gameBase.cpp
  16. 11 1
      Engine/source/T3D/gameBase/gameBase.h
  17. 609 1
      Engine/source/T3D/gameBase/gameConnection.cpp
  18. 68 0
      Engine/source/T3D/gameBase/gameConnection.h
  19. 21 0
      Engine/source/T3D/gameBase/gameConnectionEvents.cpp
  20. 20 0
      Engine/source/T3D/gameBase/processList.cpp
  21. 8 0
      Engine/source/T3D/gameBase/processList.h
  22. 8 0
      Engine/source/T3D/groundPlane.cpp
  23. 7 1
      Engine/source/T3D/lightBase.cpp
  24. 7 0
      Engine/source/T3D/lightBase.h
  25. 13 0
      Engine/source/T3D/objectTypes.h
  26. 200 24
      Engine/source/T3D/physicalZone.cpp
  27. 34 3
      Engine/source/T3D/physicalZone.h
  28. 476 13
      Engine/source/T3D/player.cpp
  29. 90 1
      Engine/source/T3D/player.h
  30. 62 3
      Engine/source/T3D/projectile.cpp
  31. 12 0
      Engine/source/T3D/projectile.h
  32. 397 0
      Engine/source/T3D/shapeBase.cpp
  33. 66 0
      Engine/source/T3D/shapeBase.h
  34. 12 0
      Engine/source/T3D/staticShape.cpp
  35. 9 0
      Engine/source/T3D/staticShape.h
  36. 102 0
      Engine/source/T3D/tsStatic.cpp
  37. 0 1330
      Engine/source/T3D/tsStatic.cpp.orig
  38. 19 0
      Engine/source/T3D/tsStatic.h
  39. 20 0
      Engine/source/T3D/turret/aiTurretShape.h
  40. 1189 0
      Engine/source/afx/afxCamera.cpp
  41. 189 0
      Engine/source/afx/afxCamera.h
  42. 1084 0
      Engine/source/afx/afxChoreographer.cpp
  43. 222 0
      Engine/source/afx/afxChoreographer.h
  44. 2613 0
      Engine/source/afx/afxConstraint.cpp
  45. 677 0
      Engine/source/afx/afxConstraint.h
  46. 114 0
      Engine/source/afx/afxEffectDefs.h
  47. 270 0
      Engine/source/afx/afxEffectGroup.cpp
  48. 103 0
      Engine/source/afx/afxEffectGroup.h
  49. 358 0
      Engine/source/afx/afxEffectVector.cpp
  50. 86 0
      Engine/source/afx/afxEffectVector.h
  51. 1193 0
      Engine/source/afx/afxEffectWrapper.cpp
  52. 392 0
      Engine/source/afx/afxEffectWrapper.h
  53. 1119 0
      Engine/source/afx/afxEffectron.cpp
  54. 216 0
      Engine/source/afx/afxEffectron.h
  55. 2116 0
      Engine/source/afx/afxMagicMissile.cpp
  56. 435 0
      Engine/source/afx/afxMagicMissile.h
  57. 2709 0
      Engine/source/afx/afxMagicSpell.cpp
  58. 390 0
      Engine/source/afx/afxMagicSpell.h
  59. 196 0
      Engine/source/afx/afxPhrase.cpp
  60. 87 0
      Engine/source/afx/afxPhrase.h
  61. 176 0
      Engine/source/afx/afxRenderHighlightMgr.cpp
  62. 76 0
      Engine/source/afx/afxRenderHighlightMgr.h
  63. 469 0
      Engine/source/afx/afxResidueMgr.cpp
  64. 179 0
      Engine/source/afx/afxResidueMgr.h
  65. 1173 0
      Engine/source/afx/afxSelectron.cpp
  66. 258 0
      Engine/source/afx/afxSelectron.h
  67. 357 0
      Engine/source/afx/afxSpellBook.cpp
  68. 142 0
      Engine/source/afx/afxSpellBook.h
  69. 311 0
      Engine/source/afx/afxZodiacGroundPlaneRenderer_T3D.cpp
  70. 91 0
      Engine/source/afx/afxZodiacGroundPlaneRenderer_T3D.h
  71. 302 0
      Engine/source/afx/afxZodiacMeshRoadRenderer_T3D.cpp
  72. 92 0
      Engine/source/afx/afxZodiacMeshRoadRenderer_T3D.h
  73. 420 0
      Engine/source/afx/afxZodiacPolysoupRenderer_T3D.cpp
  74. 92 0
      Engine/source/afx/afxZodiacPolysoupRenderer_T3D.h
  75. 344 0
      Engine/source/afx/afxZodiacTerrainRenderer_T3D.cpp
  76. 92 0
      Engine/source/afx/afxZodiacTerrainRenderer_T3D.h
  77. 959 0
      Engine/source/afx/arcaneFX.cpp
  78. 214 0
      Engine/source/afx/arcaneFX.h
  79. 217 0
      Engine/source/afx/ce/afxAnimClip.cpp
  80. 81 0
      Engine/source/afx/ce/afxAnimClip.h
  81. 95 0
      Engine/source/afx/ce/afxAnimLock.cpp
  82. 48 0
      Engine/source/afx/ce/afxAnimLock.h
  83. 121 0
      Engine/source/afx/ce/afxAreaDamage.cpp
  84. 62 0
      Engine/source/afx/ce/afxAreaDamage.h
  85. 246 0
      Engine/source/afx/ce/afxAudioBank.cpp
  86. 67 0
      Engine/source/afx/ce/afxAudioBank.h
  87. 294 0
      Engine/source/afx/ce/afxBillboard.cpp
  88. 131 0
      Engine/source/afx/ce/afxBillboard.h
  89. 145 0
      Engine/source/afx/ce/afxBillboard_T3D.cpp
  90. 132 0
      Engine/source/afx/ce/afxCameraPuppet.cpp
  91. 64 0
      Engine/source/afx/ce/afxCameraPuppet.h
  92. 118 0
      Engine/source/afx/ce/afxCameraShake.cpp
  93. 57 0
      Engine/source/afx/ce/afxCameraShake.h
  94. 103 0
      Engine/source/afx/ce/afxCollisionEvent.cpp
  95. 59 0
      Engine/source/afx/ce/afxCollisionEvent.h
  96. 41 0
      Engine/source/afx/ce/afxComponentEffect.h
  97. 93 0
      Engine/source/afx/ce/afxConsoleMessage.cpp
  98. 54 0
      Engine/source/afx/ce/afxConsoleMessage.h
  99. 140 0
      Engine/source/afx/ce/afxDamage.cpp
  100. 63 0
      Engine/source/afx/ce/afxDamage.h

+ 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

+ 7 - 0
Engine/source/T3D/camera.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 _CAMERA_H_
 #ifndef _CAMERA_H_
 #define _CAMERA_H_
 #define _CAMERA_H_
 
 
@@ -246,6 +251,8 @@ class Camera: public ShapeBase
       DECLARE_CONOBJECT( Camera );
       DECLARE_CONOBJECT( Camera );
       DECLARE_CATEGORY( "Game" );
       DECLARE_CATEGORY( "Game" );
       DECLARE_DESCRIPTION( "Represents a position, direction and field of view to render a scene from." );
       DECLARE_DESCRIPTION( "Represents a position, direction and field of view to render a scene from." );
+      static F32 getMovementSpeed() { return smMovementSpeed; }
+      bool isCamera() const { return true; }
 };
 };
 
 
 typedef Camera::CameraMotionMode CameraMotionMode;
 typedef Camera::CameraMotionMode CameraMotionMode;

+ 8 - 1
Engine/source/T3D/containerQuery.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/containerQuery.h"
 #include "T3D/containerQuery.h"
 
 
@@ -91,7 +96,9 @@ void physicalZoneFind(SceneObject* obj, void *key)
 
 
    if (pz->isActive()) {
    if (pz->isActive()) {
       info->gravityScale *= pz->getGravityMod();
       info->gravityScale *= pz->getGravityMod();
-      info->appliedForce += pz->getForce();
+      Point3F center; 
+      info->box.getCenter(&center);
+      info->appliedForce += pz->getForce(&center);
    }
    }
 }
 }
 
 

+ 104 - 4
Engine/source/T3D/debris.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/debris.h"
 #include "T3D/debris.h"
 
 
@@ -113,6 +118,85 @@ DebrisData::DebrisData()
    ignoreWater = true;
    ignoreWater = true;
 }
 }
 
 
+//#define TRACK_DEBRIS_DATA_CLONES
+
+#ifdef TRACK_DEBRIS_DATA_CLONES
+static int debris_data_clones = 0;
+#endif
+
+DebrisData::DebrisData(const DebrisData& other, bool temp_clone) : GameBaseData(other, temp_clone)
+{
+#ifdef TRACK_DEBRIS_DATA_CLONES
+   debris_data_clones++;
+   if (debris_data_clones == 1)
+      Con::errorf("DebrisData -- Clones are on the loose!");
+#endif
+   velocity = other.velocity;
+   velocityVariance = other.velocityVariance;
+   friction = other.friction;
+   elasticity = other.elasticity;
+   lifetime = other.lifetime;
+   lifetimeVariance = other.lifetimeVariance;
+   numBounces = other.numBounces;
+   bounceVariance = other.bounceVariance;
+   minSpinSpeed = other.minSpinSpeed;
+   maxSpinSpeed = other.maxSpinSpeed;
+   explodeOnMaxBounce = other.explodeOnMaxBounce;
+   staticOnMaxBounce = other.staticOnMaxBounce;
+   snapOnMaxBounce = other.snapOnMaxBounce;
+   fade = other.fade;
+   useRadiusMass = other.useRadiusMass;
+   baseRadius = other.baseRadius;
+   gravModifier = other.gravModifier;
+   terminalVelocity = other.terminalVelocity;
+   ignoreWater = other.ignoreWater;
+   shapeName = other.shapeName;
+   shape = other.shape; // -- TSShape loaded using shapeName
+   textureName = other.textureName;
+   explosionId = other.explosionId; // -- for pack/unpack of explosion ptr
+   explosion = other.explosion;
+   dMemcpy( emitterList, other.emitterList, sizeof( emitterList ) );
+   dMemcpy( emitterIDList, other.emitterIDList, sizeof( emitterIDList ) ); // -- for pack/unpack of emitterList ptrs
+}
+
+DebrisData::~DebrisData()
+{
+   if (!isTempClone())
+      return;
+
+#ifdef TRACK_DEBRIS_DATA_CLONES
+   if (debris_data_clones > 0)
+   {
+      debris_data_clones--;
+      if (debris_data_clones == 0)
+         Con::errorf("DebrisData -- Clones eliminated!");
+   }
+   else
+      Con::errorf("DebrisData -- Too many clones deleted!");
+#endif
+}
+
+DebrisData* DebrisData::cloneAndPerformSubstitutions(const SimObject* owner, S32 index)
+{
+   if (!owner || getSubstitutionCount() == 0)
+      return this;
+
+   DebrisData* sub_debris_db = new DebrisData(*this, true);
+   performSubstitutions(sub_debris_db, owner, index);
+
+   return sub_debris_db;
+}
+
+void DebrisData::onPerformSubstitutions() 
+{ 
+   if( shapeName && shapeName[0] != '\0')
+   {
+      shape = ResourceManager::get().load(shapeName);
+      if( bool(shape) == false )
+         Con::errorf("DebrisData::onPerformSubstitutions(): failed to load shape \"%s\"", shapeName);
+   }
+}
+
 bool DebrisData::onAdd()
 bool DebrisData::onAdd()
 {
 {
    if(!Parent::onAdd())
    if(!Parent::onAdd())
@@ -269,6 +353,9 @@ void DebrisData::initPersistFields()
    addField("ignoreWater",          TypeBool,                    Offset(ignoreWater,         DebrisData), "If true, this debris object will not collide with water, acting as if the water is not there.");
    addField("ignoreWater",          TypeBool,                    Offset(ignoreWater,         DebrisData), "If true, this debris object will not collide with water, acting as if the water is not there.");
    endGroup("Behavior");
    endGroup("Behavior");
 
 
+   // disallow some field substitutions
+   onlyKeepClearSubstitutions("emitters"); // subs resolving to "~~", or "~0" are OK
+   onlyKeepClearSubstitutions("explosion");
    Parent::initPersistFields();
    Parent::initPersistFields();
 }
 }
 
 
@@ -451,6 +538,8 @@ Debris::Debris()
 
 
    // Only allocated client side.
    // Only allocated client side.
    mNetFlags.set( IsGhost );
    mNetFlags.set( IsGhost );
+   ss_object = 0;
+   ss_index = 0;
 }
 }
 
 
 Debris::~Debris()
 Debris::~Debris()
@@ -466,6 +555,12 @@ Debris::~Debris()
       delete mPart;
       delete mPart;
       mPart = NULL;
       mPart = NULL;
    }
    }
+   
+   if (mDataBlock && mDataBlock->isTempClone())
+   { 
+      delete mDataBlock;
+      mDataBlock = 0;
+   }
 }
 }
 
 
 void Debris::initPersistFields()
 void Debris::initPersistFields()
@@ -495,6 +590,8 @@ bool Debris::onNewDataBlock( GameBaseData *dptr, bool reload )
    if( !mDataBlock || !Parent::onNewDataBlock( dptr, reload ) )
    if( !mDataBlock || !Parent::onNewDataBlock( dptr, reload ) )
       return false;
       return false;
 
 
+   if (mDataBlock->isTempClone())
+      return true;
    scriptOnNewDataBlock();
    scriptOnNewDataBlock();
    return true;
    return true;
 
 
@@ -519,7 +616,7 @@ bool Debris::onAdd()
       if( mDataBlock->emitterList[i] != NULL )
       if( mDataBlock->emitterList[i] != NULL )
       {
       {
          ParticleEmitter * pEmitter = new ParticleEmitter;
          ParticleEmitter * pEmitter = new ParticleEmitter;
-         pEmitter->onNewDataBlock( mDataBlock->emitterList[i], false );
+         pEmitter->onNewDataBlock(mDataBlock->emitterList[i]->cloneAndPerformSubstitutions(ss_object, ss_index), false);
          if( !pEmitter->registerObject() )
          if( !pEmitter->registerObject() )
          {
          {
             Con::warnf( ConsoleLogEntry::General, "Could not register emitter for particle of class: %s", mDataBlock->getName() );
             Con::warnf( ConsoleLogEntry::General, "Could not register emitter for particle of class: %s", mDataBlock->getName() );
@@ -537,7 +634,8 @@ bool Debris::onAdd()
    {
    {
       sizeList[0] = mSize * 0.5;
       sizeList[0] = mSize * 0.5;
       sizeList[1] = mSize;
       sizeList[1] = mSize;
-      sizeList[2] = mSize * 1.5;
+      for (U32 i = 2; i < ParticleData::PDC_NUM_KEYS; i++)
+         sizeList[i] = mSize * 1.5;
 
 
       mEmitterList[0]->setSizes( sizeList );
       mEmitterList[0]->setSizes( sizeList );
    }
    }
@@ -546,7 +644,8 @@ bool Debris::onAdd()
    {
    {
       sizeList[0] = 0.0;
       sizeList[0] = 0.0;
       sizeList[1] = mSize * 0.5;
       sizeList[1] = mSize * 0.5;
-      sizeList[2] = mSize;
+      for (U32 i = 2; i < ParticleData::PDC_NUM_KEYS; i++)
+         sizeList[i] = mSize;
 
 
       mEmitterList[1]->setSizes( sizeList );
       mEmitterList[1]->setSizes( sizeList );
    }
    }
@@ -798,7 +897,8 @@ void Debris::explode()
    Point3F explosionPos = getPosition();
    Point3F explosionPos = getPosition();
 
 
    Explosion* pExplosion = new Explosion;
    Explosion* pExplosion = new Explosion;
-   pExplosion->onNewDataBlock(mDataBlock->explosion, false);
+   pExplosion->setSubstitutionData(ss_object, ss_index);
+   pExplosion->onNewDataBlock(mDataBlock->explosion->cloneAndPerformSubstitutions(ss_object, ss_index), false);
 
 
    MatrixF trans( true );
    MatrixF trans( true );
    trans.setPosition( getPosition() );
    trans.setPosition( getPosition() );

+ 16 - 0
Engine/source/T3D/debris.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 _DEBRIS_H_
 #ifndef _DEBRIS_H_
 #define _DEBRIS_H_
 #define _DEBRIS_H_
 
 
@@ -97,6 +102,12 @@ struct DebrisData : public GameBaseData
 
 
    DECLARE_CONOBJECT(DebrisData);
    DECLARE_CONOBJECT(DebrisData);
 
 
+public:
+   /*C*/        DebrisData(const DebrisData&, bool = false);
+   /*D*/        ~DebrisData();
+   DebrisData*  cloneAndPerformSubstitutions(const SimObject*, S32 index=0);
+   virtual void onPerformSubstitutions();
+   virtual bool allowSubstitutions() const { return true; }
 };
 };
 
 
 //**************************************************************************
 //**************************************************************************
@@ -165,6 +176,11 @@ public:
 
 
    DECLARE_CONOBJECT(Debris);
    DECLARE_CONOBJECT(Debris);
 
 
+private:
+   SimObject*   ss_object;
+   S32          ss_index;
+public:
+   void         setSubstitutionData(SimObject* obj, S32 idx=0) { ss_object = obj; ss_index = idx; }
 };
 };
 
 
 
 

+ 144 - 6
Engine/source/T3D/fx/explosion.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/fx/explosion.h"
 #include "T3D/fx/explosion.h"
 
 
@@ -50,6 +55,8 @@
 #include "renderInstance/renderPassManager.h"
 #include "renderInstance/renderPassManager.h"
 #include "console/engineAPI.h"
 #include "console/engineAPI.h"
 
 
+#include "sfx/sfxProfile.h"
+
 IMPLEMENT_CONOBJECT(Explosion);
 IMPLEMENT_CONOBJECT(Explosion);
 
 
 ConsoleDocClass( Explosion,
 ConsoleDocClass( Explosion,
@@ -281,6 +288,105 @@ ExplosionData::ExplosionData()
    lightNormalOffset = 0.1f;
    lightNormalOffset = 0.1f;
 }
 }
 
 
+//#define TRACK_EXPLOSION_DATA_CLONES
+
+#ifdef TRACK_EXPLOSION_DATA_CLONES
+static int explosion_data_clones = 0;
+#endif
+
+ExplosionData::ExplosionData(const ExplosionData& other, bool temp_clone) : GameBaseData(other, temp_clone)
+{
+#ifdef TRACK_EXPLOSION_DATA_CLONES
+   explosion_data_clones++;
+   if (explosion_data_clones == 1)
+      Con::errorf("ExplosionData -- Clones are on the loose!");
+#endif
+
+   dtsFileName = other.dtsFileName;
+   faceViewer = other.faceViewer;
+   particleDensity = other.particleDensity;
+   particleRadius = other.particleRadius;
+   soundProfile = other.soundProfile;
+   particleEmitter = other.particleEmitter;
+   particleEmitterId = other.particleEmitterId; // -- for pack/unpack of particleEmitter ptr 
+   explosionScale = other.explosionScale;
+   playSpeed = other.playSpeed;
+   explosionShape = other.explosionShape; // -- TSShape loaded using dtsFileName
+   explosionAnimation = other.explosionAnimation; // -- from explosionShape sequence "ambient"
+   dMemcpy( emitterList, other.emitterList, sizeof( emitterList ) );
+   dMemcpy( emitterIDList, other.emitterIDList, sizeof( emitterIDList ) ); // -- for pack/unpack of emitterList ptrs
+   dMemcpy( debrisList, other.debrisList, sizeof( debrisList ) );
+   dMemcpy( debrisIDList, other.debrisIDList, sizeof( debrisIDList ) ); // -- for pack/unpack of debrisList ptrs 
+   debrisThetaMin = other.debrisThetaMin;
+   debrisThetaMax = other.debrisThetaMax;
+   debrisPhiMin = other.debrisPhiMin;
+   debrisPhiMax = other.debrisPhiMax;
+   debrisNum = other.debrisNum;
+   debrisNumVariance = other.debrisNumVariance;
+   debrisVelocity = other.debrisVelocity;
+   debrisVelocityVariance = other.debrisVelocityVariance;
+   dMemcpy( explosionList, other.explosionList, sizeof( explosionList ) );
+   dMemcpy( explosionIDList, other.explosionIDList, sizeof( explosionIDList ) ); // -- for pack/unpack of explosionList ptrs
+   delayMS = other.delayMS;
+   delayVariance = other.delayVariance;
+   lifetimeMS = other.lifetimeMS;
+   lifetimeVariance = other.lifetimeVariance;
+   offset = other.offset;
+   dMemcpy( sizes, other.times, sizeof( sizes ) );
+   dMemcpy( times, other.times, sizeof( times ) );
+   shakeCamera = other.shakeCamera;
+   camShakeFreq = other.camShakeFreq;
+   camShakeAmp = other.camShakeAmp;
+   camShakeDuration = other.camShakeDuration;
+   camShakeRadius = other.camShakeRadius;
+   camShakeFalloff = other.camShakeFalloff;
+   lightStartRadius = other.lightStartRadius;
+   lightEndRadius = other.lightEndRadius;
+   lightStartColor = other.lightStartColor;
+   lightEndColor = other.lightEndColor;
+   lightStartBrightness = other.lightStartBrightness;
+   lightEndBrightness = other.lightEndBrightness;
+   lightNormalOffset = other.lightNormalOffset;
+   // Note - Explosion calls mDataBlock->getName() in warning messages but
+   //   that should be safe.
+}
+
+ExplosionData::~ExplosionData()
+{
+   if (!isTempClone())
+      return;
+
+   if (soundProfile && soundProfile->isTempClone())
+   {
+      delete soundProfile;
+      soundProfile = 0;
+   }
+
+   // particleEmitter, emitterList[*], debrisList[*], explosionList[*] will delete themselves
+
+#ifdef TRACK_EXPLOSION_DATA_CLONES
+   if (explosion_data_clones > 0)
+   {
+      explosion_data_clones--;
+      if (explosion_data_clones == 0)
+         Con::errorf("ExplosionData -- Clones eliminated!");
+   }
+   else
+      Con::errorf("ExplosionData -- Too many clones deleted!");
+#endif
+}
+
+ExplosionData* ExplosionData::cloneAndPerformSubstitutions(const SimObject* owner, S32 index)
+{
+   if (!owner || getSubstitutionCount() == 0)
+      return this;
+
+   ExplosionData* sub_explosion_db = new ExplosionData(*this, true);
+   performSubstitutions(sub_explosion_db, owner, index);
+
+   return sub_explosion_db;
+}
+
 void ExplosionData::initPersistFields()
 void ExplosionData::initPersistFields()
 {
 {
    addField( "explosionShape", TypeShapeFilename, Offset(dtsFileName, ExplosionData),
    addField( "explosionShape", TypeShapeFilename, Offset(dtsFileName, ExplosionData),
@@ -412,6 +518,12 @@ void ExplosionData::initPersistFields()
       "Distance (in the explosion normal direction) of the PointLight position "
       "Distance (in the explosion normal direction) of the PointLight position "
       "from the explosion center." );
       "from the explosion center." );
 
 
+   // disallow some field substitutions
+   onlyKeepClearSubstitutions("debris"); // subs resolving to "~~", or "~0" are OK
+   onlyKeepClearSubstitutions("emitter");
+   onlyKeepClearSubstitutions("particleEmitter");
+   onlyKeepClearSubstitutions("soundProfile");
+   onlyKeepClearSubstitutions("subExplosion");
    Parent::initPersistFields();
    Parent::initPersistFields();
 }
 }
 
 
@@ -808,6 +920,10 @@ Explosion::Explosion()
    mLight = LIGHTMGR->createLightInfo();
    mLight = LIGHTMGR->createLightInfo();
 
 
    mNetFlags.set( IsGhost );
    mNetFlags.set( IsGhost );
+   ss_object = 0;
+   ss_index = 0;
+   mDataBlock = 0;
+   soundProfile_clone = 0;
 }
 }
 
 
 Explosion::~Explosion()
 Explosion::~Explosion()
@@ -820,6 +936,18 @@ Explosion::~Explosion()
    }
    }
    
    
    SAFE_DELETE(mLight);
    SAFE_DELETE(mLight);
+   
+   if (soundProfile_clone)
+   { 
+      delete soundProfile_clone;
+      soundProfile_clone = 0;
+   }
+
+   if (mDataBlock && mDataBlock->isTempClone())
+   { 
+      delete mDataBlock;
+      mDataBlock = 0;
+   }
 }
 }
 
 
 
 
@@ -978,6 +1106,8 @@ bool Explosion::onNewDataBlock( GameBaseData *dptr, bool reload )
    if (!mDataBlock || !Parent::onNewDataBlock( dptr, reload ))
    if (!mDataBlock || !Parent::onNewDataBlock( dptr, reload ))
       return false;
       return false;
 
 
+   if (mDataBlock->isTempClone())
+      return true;
    scriptOnNewDataBlock();
    scriptOnNewDataBlock();
    return true;
    return true;
 }
 }
@@ -1190,7 +1320,8 @@ void Explosion::launchDebris( Point3F &axis )
       launchDir *= debrisVel;
       launchDir *= debrisVel;
 
 
       Debris *debris = new Debris;
       Debris *debris = new Debris;
-      debris->setDataBlock( mDataBlock->debrisList[0] );
+      debris->setSubstitutionData(ss_object, ss_index);
+      debris->setDataBlock(mDataBlock->debrisList[0]->cloneAndPerformSubstitutions(ss_object, ss_index));
       debris->setTransform( getTransform() );
       debris->setTransform( getTransform() );
       debris->init( pos, launchDir );
       debris->init( pos, launchDir );
 
 
@@ -1218,7 +1349,8 @@ void Explosion::spawnSubExplosions()
       {
       {
          MatrixF trans = getTransform();
          MatrixF trans = getTransform();
          Explosion* pExplosion = new Explosion;
          Explosion* pExplosion = new Explosion;
-         pExplosion->setDataBlock( mDataBlock->explosionList[i] );
+         pExplosion->setSubstitutionData(ss_object, ss_index);
+         pExplosion->setDataBlock(mDataBlock->explosionList[i]->cloneAndPerformSubstitutions(ss_object, ss_index));
          pExplosion->setTransform( trans );
          pExplosion->setTransform( trans );
          pExplosion->setInitialState( trans.getPosition(), mInitialNormal, 1);
          pExplosion->setInitialState( trans.getPosition(), mInitialNormal, 1);
          if (!pExplosion->registerObject())
          if (!pExplosion->registerObject())
@@ -1256,12 +1388,18 @@ bool Explosion::explode()
       resetWorldBox();
       resetWorldBox();
    }
    }
 
 
-   if (mDataBlock->soundProfile)
-      SFX->playOnce( mDataBlock->soundProfile, &getTransform() );
+   SFXProfile* sound_prof = dynamic_cast<SFXProfile*>(mDataBlock->soundProfile);
+   if (sound_prof)
+   {
+      soundProfile_clone = sound_prof->cloneAndPerformSubstitutions(ss_object, ss_index);
+      SFX->playOnce( soundProfile_clone, &getTransform() );
+      if (!soundProfile_clone->isTempClone())
+         soundProfile_clone = 0;
+   }
 
 
    if (mDataBlock->particleEmitter) {
    if (mDataBlock->particleEmitter) {
       mMainEmitter = new ParticleEmitter;
       mMainEmitter = new ParticleEmitter;
-      mMainEmitter->setDataBlock(mDataBlock->particleEmitter);
+      mMainEmitter->setDataBlock(mDataBlock->particleEmitter->cloneAndPerformSubstitutions(ss_object, ss_index));
       mMainEmitter->registerObject();
       mMainEmitter->registerObject();
 
 
       mMainEmitter->emitParticles(getPosition(), mInitialNormal, mDataBlock->particleRadius,
       mMainEmitter->emitParticles(getPosition(), mInitialNormal, mDataBlock->particleRadius,
@@ -1273,7 +1411,7 @@ bool Explosion::explode()
       if( mDataBlock->emitterList[i] != NULL )
       if( mDataBlock->emitterList[i] != NULL )
       {
       {
          ParticleEmitter * pEmitter = new ParticleEmitter;
          ParticleEmitter * pEmitter = new ParticleEmitter;
-         pEmitter->setDataBlock( mDataBlock->emitterList[i] );
+         pEmitter->setDataBlock(mDataBlock->emitterList[i]->cloneAndPerformSubstitutions(ss_object, ss_index));
          if( !pEmitter->registerObject() )
          if( !pEmitter->registerObject() )
          {
          {
             Con::warnf( ConsoleLogEntry::General, "Could not register emitter for particle of class: %s", mDataBlock->getName() );
             Con::warnf( ConsoleLogEntry::General, "Could not register emitter for particle of class: %s", mDataBlock->getName() );

+ 17 - 0
Engine/source/T3D/fx/explosion.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 _EXPLOSION_H_
 #ifndef _EXPLOSION_H_
 #define _EXPLOSION_H_
 #define _EXPLOSION_H_
 
 
@@ -42,6 +47,7 @@ class TSThread;
 class SFXTrack;
 class SFXTrack;
 struct DebrisData;
 struct DebrisData;
 
 
+class SFXProfile;
 //--------------------------------------------------------------------------
 //--------------------------------------------------------------------------
 class ExplosionData : public GameBaseData {
 class ExplosionData : public GameBaseData {
   public:
   public:
@@ -126,6 +132,11 @@ class ExplosionData : public GameBaseData {
    static void  initPersistFields();
    static void  initPersistFields();
    virtual void packData(BitStream* stream);
    virtual void packData(BitStream* stream);
    virtual void unpackData(BitStream* stream);
    virtual void unpackData(BitStream* stream);
+public:
+   /*C*/          ExplosionData(const ExplosionData&, bool = false);
+   /*D*/          ~ExplosionData();
+   ExplosionData* cloneAndPerformSubstitutions(const SimObject*, S32 index=0);
+   virtual bool   allowSubstitutions() const { return true; }
 };
 };
 
 
 
 
@@ -188,6 +199,12 @@ class Explosion : public GameBase, public ISceneLight
 
 
    DECLARE_CONOBJECT(Explosion);
    DECLARE_CONOBJECT(Explosion);
    static void initPersistFields();
    static void initPersistFields();
+private:
+   SimObject*     ss_object;
+   S32            ss_index;
+   SFXProfile*    soundProfile_clone;
+public:
+   void           setSubstitutionData(SimObject* obj, S32 idx=0) { ss_object = obj; ss_index = idx; }
 };
 };
 
 
 #endif // _H_EXPLOSION
 #endif // _H_EXPLOSION

+ 16 - 1
Engine/source/T3D/fx/fxFoliageReplicator.cpp

@@ -43,6 +43,11 @@
 // POTENTIAL TODO LIST:
 // POTENTIAL TODO LIST:
 //   TODO: Clamp item alpha to fog alpha
 //   TODO: Clamp item alpha to fog alpha
 
 
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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/fx/fxFoliageReplicator.h"
 #include "T3D/fx/fxFoliageReplicator.h"
 
 
@@ -402,6 +407,9 @@ void fxFoliageReplicator::initPersistFields()
       addField( "AllowedTerrainSlope", TypeS32,       Offset( mFieldData.mAllowedTerrainSlope,  fxFoliageReplicator ), "Maximum surface angle allowed for foliage instances." );
       addField( "AllowedTerrainSlope", TypeS32,       Offset( mFieldData.mAllowedTerrainSlope,  fxFoliageReplicator ), "Maximum surface angle allowed for foliage instances." );
    endGroup( "Restrictions" );	// MM: Added Group Footer.
    endGroup( "Restrictions" );	// MM: Added Group Footer.
 
 
+   addGroup( "AFX" );
+      addField( "AmbientModulationBias", TypeF32,     Offset( mFieldData.mAmbientModulationBias,fxFoliageReplicator ), "Multiplier controling amount foliage is modulated by sun's ambient." );
+   endGroup( "AFX" );
    // Initialise parents' persistent fields.
    // Initialise parents' persistent fields.
    Parent::initPersistFields();
    Parent::initPersistFields();
 }
 }
@@ -1564,7 +1572,12 @@ void fxFoliageReplicator::renderObject(ObjectRenderInst *ri, SceneRenderState *s
             mFoliageShaderConsts->setSafe(mFoliageShaderGroundAlphaSC, Point4F(mFieldData.mGroundAlpha, mFieldData.mGroundAlpha, mFieldData.mGroundAlpha, mFieldData.mGroundAlpha));
             mFoliageShaderConsts->setSafe(mFoliageShaderGroundAlphaSC, Point4F(mFieldData.mGroundAlpha, mFieldData.mGroundAlpha, mFieldData.mGroundAlpha, mFieldData.mGroundAlpha));
 
 
             if (mFoliageShaderAmbientColorSC->isValid())
             if (mFoliageShaderAmbientColorSC->isValid())
-               mFoliageShaderConsts->set(mFoliageShaderAmbientColorSC, state->getAmbientLightColor());
+            {
+               LinearColorF ambient = state->getAmbientLightColor();
+               LinearColorF ambient_inv(1.0f-ambient.red, 1.0f-ambient.green, 1.0f-ambient.blue, 0.0f);
+               ambient += ambient_inv*(1.0f - mFieldData.mAmbientModulationBias);
+               mFoliageShaderConsts->set(mFoliageShaderAmbientColorSC, ambient);
+            }
 
 
             GFX->setShaderConstBuffer(mFoliageShaderConsts);
             GFX->setShaderConstBuffer(mFoliageShaderConsts);
 
 
@@ -1705,6 +1718,7 @@ U32 fxFoliageReplicator::packUpdate(NetConnection * con, U32 mask, BitStream * s
       stream->writeFlag(mFieldData.mShowPlacementArea);				// Show Placement Area Flag.
       stream->writeFlag(mFieldData.mShowPlacementArea);				// Show Placement Area Flag.
       stream->write(mFieldData.mPlacementBandHeight);					// Placement Area Height.
       stream->write(mFieldData.mPlacementBandHeight);					// Placement Area Height.
       stream->write(mFieldData.mPlaceAreaColour);						// Placement Area Colour.
       stream->write(mFieldData.mPlaceAreaColour);						// Placement Area Colour.
+      stream->write(mFieldData.mAmbientModulationBias);
    }
    }
 
 
    // Were done ...
    // Were done ...
@@ -1782,6 +1796,7 @@ void fxFoliageReplicator::unpackUpdate(NetConnection * con, BitStream * stream)
       stream->read(&mFieldData.mPlacementBandHeight);					// Placement Area Height.
       stream->read(&mFieldData.mPlacementBandHeight);					// Placement Area Height.
       stream->read(&mFieldData.mPlaceAreaColour);
       stream->read(&mFieldData.mPlaceAreaColour);
 
 
+      stream->read(&mFieldData.mAmbientModulationBias);
       // Calculate Fade-In/Out Gradients.
       // Calculate Fade-In/Out Gradients.
       mFadeInGradient		= 1.0f / mFieldData.mFadeInRegion;
       mFadeInGradient		= 1.0f / mFieldData.mFadeInRegion;
       mFadeOutGradient	= 1.0f / mFieldData.mFadeOutRegion;
       mFadeOutGradient	= 1.0f / mFieldData.mFadeOutRegion;

+ 7 - 0
Engine/source/T3D/fx/fxFoliageReplicator.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 _FOLIAGEREPLICATOR_H_
 #ifndef _FOLIAGEREPLICATOR_H_
 #define _FOLIAGEREPLICATOR_H_
 #define _FOLIAGEREPLICATOR_H_
 
 
@@ -319,6 +324,7 @@ public:
       U32             mPlacementBandHeight;
       U32             mPlacementBandHeight;
       LinearColorF          mPlaceAreaColour;
       LinearColorF          mPlaceAreaColour;
 
 
+      F32             mAmbientModulationBias;
       tagFieldData()
       tagFieldData()
       {
       {
          // Set Defaults.
          // Set Defaults.
@@ -377,6 +383,7 @@ public:
          mShowPlacementArea    = true;
          mShowPlacementArea    = true;
          mPlacementBandHeight  = 25;
          mPlacementBandHeight  = 25;
          mPlaceAreaColour      .set(0.4f, 0, 0.8f);
          mPlaceAreaColour      .set(0.4f, 0, 0.8f);
+         mAmbientModulationBias = 1.0f;
       }
       }
 
 
    } mFieldData;
    } mFieldData;

+ 181 - 20
Engine/source/T3D/fx/particle.cpp

@@ -19,6 +19,12 @@
 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 // 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 "particle.h"
 #include "particle.h"
 #include "console/consoleTypes.h"
 #include "console/consoleTypes.h"
 #include "console/typeValidators.h"
 #include "console/typeValidators.h"
@@ -72,6 +78,8 @@ static const F32 sgDefaultSpinSpeed = 1.f;
 static const F32 sgDefaultSpinRandomMin = 0.f;
 static const F32 sgDefaultSpinRandomMin = 0.f;
 static const F32 sgDefaultSpinRandomMax = 0.f;
 static const F32 sgDefaultSpinRandomMax = 0.f;
 
 
+static const F32 sgDefaultSpinBias = 1.0f;
+static const F32 sgDefaultSizeBias = 1.0f;
 
 
 //-----------------------------------------------------------------------------
 //-----------------------------------------------------------------------------
 // Constructor
 // Constructor
@@ -102,9 +110,9 @@ ParticleData::ParticleData()
    }
    }
 
 
    times[0] = 0.0f;
    times[0] = 0.0f;
-   times[1] = 0.33f;
-   times[2] = 0.66f;
-   times[3] = 1.0f;
+   times[1] = 1.0f;
+   for (i = 2; i < PDC_NUM_KEYS; i++)
+     times[i] = -1.0f;
 
 
    texCoords[0].set(0.0,0.0);   // texture coords at 4 corners
    texCoords[0].set(0.0,0.0);   // texture coords at 4 corners
    texCoords[1].set(0.0,1.0);   // of particle quad
    texCoords[1].set(0.0,1.0);   // of particle quad
@@ -115,18 +123,20 @@ ParticleData::ParticleData()
    animTexUVs = NULL;           // array of tile vertex UVs
    animTexUVs = NULL;           // array of tile vertex UVs
    textureName = NULL;          // texture filename
    textureName = NULL;          // texture filename
    textureHandle = NULL;        // loaded texture handle
    textureHandle = NULL;        // loaded texture handle
+   textureExtName = NULL;
+   textureExtHandle = NULL;
+   constrain_pos = false;
+   start_angle = 0.0f;
+   angle_variance = 0.0f;
+   sizeBias = sgDefaultSizeBias;
+   spinBias = sgDefaultSpinBias;
+   randomizeSpinDir = false;
 }
 }
 
 
 //-----------------------------------------------------------------------------
 //-----------------------------------------------------------------------------
 // Destructor
 // Destructor
 //-----------------------------------------------------------------------------
 //-----------------------------------------------------------------------------
-ParticleData::~ParticleData()
-{
-   if (animTexUVs)
-   {
-      delete [] animTexUVs;
-   }
-}
+
 
 
 FRangeValidator dragCoefFValidator(0.f, 5.f);
 FRangeValidator dragCoefFValidator(0.f, 5.f);
 FRangeValidator gravCoefFValidator(-10.f, 10.f);
 FRangeValidator gravCoefFValidator(-10.f, 10.f);
@@ -214,6 +224,15 @@ void ParticleData::initPersistFields()
       "@brief Time keys used with the colors and sizes keyframes.\n\n"
       "@brief Time keys used with the colors and sizes keyframes.\n\n"
       "Values are from 0.0 (particle creation) to 1.0 (end of lifespace)." );
       "Values are from 0.0 (particle creation) to 1.0 (end of lifespace)." );
 
 
+   addGroup("AFX"); 
+   addField("textureExtName",       TypeFilename, Offset(textureExtName,     ParticleData));
+   addField("constrainPos",         TypeBool,     Offset(constrain_pos,      ParticleData));
+   addField("angle",                TypeF32,      Offset(start_angle,        ParticleData));
+   addField("angleVariance",        TypeF32,      Offset(angle_variance,     ParticleData));
+   addField("sizeBias",             TypeF32,      Offset(sizeBias,           ParticleData));
+   addField("spinBias",             TypeF32,      Offset(spinBias,           ParticleData));
+   addField("randomizeSpinDir",     TypeBool,     Offset(randomizeSpinDir,   ParticleData));
+   endGroup("AFX"); 
    Parent::initPersistFields();
    Parent::initPersistFields();
 }
 }
 
 
@@ -243,18 +262,22 @@ void ParticleData::packData(BitStream* stream)
       stream->writeInt((S32)(spinRandomMin + 1000), 11);
       stream->writeInt((S32)(spinRandomMin + 1000), 11);
       stream->writeInt((S32)(spinRandomMax + 1000), 11);
       stream->writeInt((S32)(spinRandomMax + 1000), 11);
    }
    }
+   if(stream->writeFlag(spinBias != sgDefaultSpinBias))
+      stream->write(spinBias);
+   stream->writeFlag(randomizeSpinDir);
    stream->writeFlag(useInvAlpha);
    stream->writeFlag(useInvAlpha);
 
 
    S32 i, count;
    S32 i, count;
 
 
    // see how many frames there are:
    // see how many frames there are:
-   for(count = 0; count < 3; count++)
+   for(count = 0; count < ParticleData::PDC_NUM_KEYS-1; count++)
       if(times[count] >= 1)
       if(times[count] >= 1)
          break;
          break;
 
 
    count++;
    count++;
 
 
-   stream->writeInt(count-1, 2);
+   // An extra bit is needed for 8 keys.
+   stream->writeInt(count-1, 3);
 
 
    for( i=0; i<count; i++ )
    for( i=0; i<count; i++ )
    {
    {
@@ -262,7 +285,8 @@ void ParticleData::packData(BitStream* stream)
       stream->writeFloat( colors[i].green, 7);
       stream->writeFloat( colors[i].green, 7);
       stream->writeFloat( colors[i].blue, 7);
       stream->writeFloat( colors[i].blue, 7);
       stream->writeFloat( colors[i].alpha, 7);
       stream->writeFloat( colors[i].alpha, 7);
-      stream->writeFloat( sizes[i]/MaxParticleSize, 14);
+      // AFX bits raised from 14 to 16 to allow larger sizes
+      stream->writeFloat( sizes[i]/MaxParticleSize, 16);
       stream->writeFloat( times[i], 8);
       stream->writeFloat( times[i], 8);
    }
    }
 
 
@@ -279,6 +303,13 @@ void ParticleData::packData(BitStream* stream)
       mathWrite(*stream, animTexTiling);
       mathWrite(*stream, animTexTiling);
       stream->writeInt(framesPerSec, 8);
       stream->writeInt(framesPerSec, 8);
    }
    }
+   if (stream->writeFlag(textureExtName && textureExtName[0]))
+     stream->writeString(textureExtName);
+   stream->writeFlag(constrain_pos);
+   stream->writeFloat(start_angle/360.0f, 11);
+   stream->writeFloat(angle_variance/180.0f, 10);
+   if(stream->writeFlag(sizeBias != sgDefaultSizeBias))
+      stream->write(sizeBias);
 }
 }
 
 
 //-----------------------------------------------------------------------------
 //-----------------------------------------------------------------------------
@@ -322,17 +353,24 @@ void ParticleData::unpackData(BitStream* stream)
       spinRandomMax = sgDefaultSpinRandomMax;
       spinRandomMax = sgDefaultSpinRandomMax;
    }
    }
 
 
+   if(stream->readFlag())
+      stream->read(&spinBias);
+   else
+      spinBias = sgDefaultSpinBias;
+   randomizeSpinDir = stream->readFlag();
    useInvAlpha = stream->readFlag();
    useInvAlpha = stream->readFlag();
 
 
    S32 i;
    S32 i;
-   S32 count = stream->readInt(2) + 1;
+   // An extra bit is needed for 8 keys.
+   S32 count = stream->readInt(3) + 1;
    for(i = 0;i < count; i++)
    for(i = 0;i < count; i++)
    {
    {
       colors[i].red = stream->readFloat(7);
       colors[i].red = stream->readFloat(7);
       colors[i].green = stream->readFloat(7);
       colors[i].green = stream->readFloat(7);
       colors[i].blue = stream->readFloat(7);
       colors[i].blue = stream->readFloat(7);
       colors[i].alpha = stream->readFloat(7);
       colors[i].alpha = stream->readFloat(7);
-      sizes[i] = stream->readFloat(14) * MaxParticleSize;
+      // AFX bits raised from 14 to 16 to allow larger sizes
+      sizes[i] = stream->readFloat(16) * MaxParticleSize;
       times[i] = stream->readFloat(8);
       times[i] = stream->readFloat(8);
    }
    }
    textureName = (stream->readFlag()) ? stream->readSTString() : 0;
    textureName = (stream->readFlag()) ? stream->readSTString() : 0;
@@ -346,6 +384,14 @@ void ParticleData::unpackData(BitStream* stream)
      mathRead(*stream, &animTexTiling);
      mathRead(*stream, &animTexTiling);
      framesPerSec = stream->readInt(8);
      framesPerSec = stream->readInt(8);
    }
    }
+   textureExtName = (stream->readFlag()) ? stream->readSTString() : 0;
+   constrain_pos = stream->readFlag();
+   start_angle = 360.0f*stream->readFloat(11);
+   angle_variance = 180.0f*stream->readFloat(10);
+   if(stream->readFlag())
+      stream->read(&sizeBias);
+   else
+      sizeBias = sgDefaultSizeBias;
 }
 }
 
 
 bool ParticleData::protectedSetSizes( void *object, const char *index, const char *data) 
 bool ParticleData::protectedSetSizes( void *object, const char *index, const char *data) 
@@ -427,11 +473,33 @@ bool ParticleData::onAdd()
    }
    }
 
 
    times[0] = 0.0f;
    times[0] = 0.0f;
-   for (U32 i = 1; i < 4; i++) {
-      if (times[i] < times[i-1]) {
-         Con::warnf(ConsoleLogEntry::General, "ParticleData(%s) times[%d] < times[%d]", getName(), i, i-1);
-         times[i] = times[i-1];
-      }
+   for (U32 i = 1; i < PDC_NUM_KEYS; i++) 
+   {
+     if (times[i] < 0.0f)
+       break;
+     if (times[i] < times[i-1]) 
+     {
+       Con::warnf(ConsoleLogEntry::General, "ParticleData(%s) times[%d] < times[%d]", getName(), i, i-1);
+       times[i] = times[i-1];
+     }
+   }
+
+   times[0] = 0.0f;
+
+   U32 last_idx = 0;
+   for (U32 i = 1; i < PDC_NUM_KEYS; i++)
+   {
+     if (times[i] < 0.0f)
+       break;
+     else
+       last_idx = i;
+   }
+
+   for (U32 i = last_idx+1; i < PDC_NUM_KEYS; i++) 
+   {
+      times[i] = times[last_idx];
+      colors[i] = colors[last_idx];
+      sizes[i] = sizes[last_idx];
    }
    }
 
 
    // Here we validate parameters
    // Here we validate parameters
@@ -470,6 +538,10 @@ bool ParticleData::onAdd()
      }
      }
    }
    }
 
 
+   start_angle = mFmod(start_angle, 360.0f);
+   if (start_angle < 0.0f)
+     start_angle += 360.0f;
+   angle_variance = mClampF(angle_variance, -180.0f, 180.0f);
    return true;
    return true;
 }
 }
 
 
@@ -495,6 +567,15 @@ bool ParticleData::preload(bool server, String &errorStr)
           error = true;
           error = true;
         }
         }
       }
       }
+      if (textureExtName && textureExtName[0])
+      {
+         textureExtHandle = GFXTexHandle(textureExtName, &GFXStaticTextureSRGBProfile, avar("%s() - textureExtHandle (line %d)", __FUNCTION__, __LINE__));
+         if (!textureExtHandle)
+         {
+            errorStr = String::ToString("Missing particle texture: %s", textureName);
+            error = true;
+         }
+      }
 
 
       if (animateTexture) 
       if (animateTexture) 
       {
       {
@@ -606,6 +687,11 @@ void ParticleData::initializeParticle(Particle* init, const Point3F& inheritVelo
 
 
    // assign spin amount
    // assign spin amount
    init->spinSpeed = spinSpeed * gRandGen.randF( spinRandomMin, spinRandomMax );
    init->spinSpeed = spinSpeed * gRandGen.randF( spinRandomMin, spinRandomMax );
+   // apply spin bias
+   init->spinSpeed *= spinBias;
+   // randomize spin direction
+   if (randomizeSpinDir && (gRandGen.randI( 0, 1 ) == 1))
+     init->spinSpeed = -init->spinSpeed;
 }
 }
 
 
 bool ParticleData::reload(char errorBuffer[256])
 bool ParticleData::reload(char errorBuffer[256])
@@ -653,3 +739,78 @@ DefineEngineMethod(ParticleData, reload, void, (),,
    char errorBuffer[256];
    char errorBuffer[256];
    object->reload(errorBuffer);
    object->reload(errorBuffer);
 }
 }
+//#define TRACK_PARTICLE_DATA_CLONES
+
+#ifdef TRACK_PARTICLE_DATA_CLONES
+static int particle_data_clones = 0;
+#endif
+
+ParticleData::ParticleData(const ParticleData& other, bool temp_clone) : SimDataBlock(other, temp_clone)
+{
+#ifdef TRACK_PARTICLE_DATA_CLONES
+   particle_data_clones++;
+   if (particle_data_clones == 1)
+     Con::errorf("ParticleData -- Clones are on the loose!");
+#endif
+
+  dragCoefficient = other.dragCoefficient;
+  windCoefficient = other.windCoefficient;
+  gravityCoefficient = other.gravityCoefficient;
+  inheritedVelFactor = other.inheritedVelFactor;
+  constantAcceleration = other.constantAcceleration;
+  lifetimeMS = other.lifetimeMS;
+  lifetimeVarianceMS = other.lifetimeVarianceMS;
+  spinSpeed = other.spinSpeed;
+  spinRandomMin = other.spinRandomMin;
+  spinRandomMax = other.spinRandomMax;
+  useInvAlpha = other.useInvAlpha;
+  animateTexture = other.animateTexture;
+  numFrames = other.numFrames; // -- calc from other fields
+  framesPerSec = other.framesPerSec;
+  dMemcpy( colors, other.colors, sizeof( colors ) );
+  dMemcpy( sizes, other.sizes, sizeof( sizes ) );
+  dMemcpy( times, other.times, sizeof( times ) );
+  animTexUVs = other.animTexUVs; // -- calc from other fields
+  dMemcpy( texCoords, other.texCoords, sizeof( texCoords ) );
+  animTexTiling = other.animTexTiling;
+  animTexFramesString = other.animTexFramesString;
+  animTexFrames = other.animTexFrames; // -- parsed from animTexFramesString
+  textureName = other.textureName;
+  textureHandle = other.textureHandle;
+  spinBias = other.spinBias;
+  randomizeSpinDir = other.randomizeSpinDir;
+  textureExtName = other.textureExtName;
+  textureExtHandle = other.textureExtHandle;
+  constrain_pos = other.constrain_pos;
+  start_angle = other.start_angle;
+  angle_variance = other.angle_variance;
+  sizeBias = other.sizeBias;
+}
+
+ParticleData::~ParticleData()
+{
+   if (animTexUVs)
+   {
+      delete [] animTexUVs;
+   }
+
+  if (!isTempClone())
+    return;
+
+#ifdef TRACK_PARTICLE_DATA_CLONES
+  if (particle_data_clones > 0)
+  {
+    particle_data_clones--;
+    if (particle_data_clones == 0)
+      Con::errorf("ParticleData -- Clones eliminated!");
+  }
+  else
+    Con::errorf("ParticleData -- Too many clones deleted!");
+#endif
+}
+
+void ParticleData::onPerformSubstitutions() 
+{ 
+  char errorBuffer[256];
+  reload(errorBuffer);
+}

+ 29 - 1
Engine/source/T3D/fx/particle.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 _PARTICLE_H_
 #ifndef _PARTICLE_H_
 #define _PARTICLE_H_
 #define _PARTICLE_H_
 
 
@@ -44,7 +49,9 @@ class ParticleData : public SimDataBlock
   public:
   public:
    enum PDConst
    enum PDConst
    {
    {
-      PDC_NUM_KEYS = 4,
+      // This increase the keyframes from 4 to 8. Especially useful for premult-alpha blended particles
+      // for which 4 keyframes is often not enough.
+      PDC_NUM_KEYS = 8,
    };
    };
 
 
    F32   dragCoefficient;
    F32   dragCoefficient;
@@ -97,6 +104,23 @@ class ParticleData : public SimDataBlock
    static void  initPersistFields();
    static void  initPersistFields();
 
 
    bool reload(char errorBuffer[256]);
    bool reload(char errorBuffer[256]);
+  public:
+   /*C*/  ParticleData(const ParticleData&, bool = false);
+   virtual void onPerformSubstitutions();
+   virtual bool allowSubstitutions() const { return true; }
+  protected:
+   F32   spinBias;
+   bool  randomizeSpinDir;
+   StringTableEntry  textureExtName;
+  public:
+   GFXTexHandle      textureExtHandle;
+   bool   constrain_pos;
+   F32    start_angle;
+   F32    angle_variance;
+   F32    sizeBias; 
+  public:
+   bool loadParameters();  
+   bool reload(String &errorStr);
 };
 };
 
 
 //*****************************************************************************
 //*****************************************************************************
@@ -123,6 +147,10 @@ struct Particle
 
 
    F32              spinSpeed;
    F32              spinSpeed;
    Particle *       next;
    Particle *       next;
+   Point3F  pos_local;
+   F32      t_last;
+   Point3F  radial_v;   // radial vector for concentric effects
+   // note -- for non-oriented particles, we use orientDir.x to store the billboard start angle.
 };
 };
 
 
 
 

+ 395 - 30
Engine/source/T3D/fx/particleEmitter.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/fx/particleEmitter.h"
 #include "T3D/fx/particleEmitter.h"
 
 
@@ -38,6 +43,10 @@
 #include "lighting/lightInfo.h"
 #include "lighting/lightInfo.h"
 #include "console/engineAPI.h"
 #include "console/engineAPI.h"
 
 
+#if defined(AFX_CAP_PARTICLE_POOLS) 
+#include "afx/util/afxParticlePool.h"
+#endif 
+
 Point3F ParticleEmitter::mWindVelocity( 0.0, 0.0, 0.0 );
 Point3F ParticleEmitter::mWindVelocity( 0.0, 0.0, 0.0 );
 const F32 ParticleEmitter::AgedSpinToRadians = (1.0f/1000.0f) * (1.0f/360.0f) * M_PI_F * 2.0f;
 const F32 ParticleEmitter::AgedSpinToRadians = (1.0f/1000.0f) * (1.0f/360.0f) * M_PI_F * 2.0f;
 
 
@@ -149,6 +158,21 @@ ParticleEmitterData::ParticleEmitterData()
    
    
    alignParticles = false;
    alignParticles = false;
    alignDirection = Point3F(0.0f, 1.0f, 0.0f);
    alignDirection = Point3F(0.0f, 1.0f, 0.0f);
+   
+   ejectionInvert = false;
+   fade_color    = false;
+   fade_alpha    = false;
+   fade_size     = false;
+   parts_per_eject = 1;
+   use_emitter_xfm = false;
+   
+#if defined(AFX_CAP_PARTICLE_POOLS) 
+   pool_datablock = 0;
+   pool_index = 0;
+   pool_depth_fade = false;
+   pool_radial_fade = false;
+   do_pool_id_convert = false;
+#endif
 }
 }
 
 
 
 
@@ -293,6 +317,26 @@ void ParticleEmitterData::initPersistFields()
 
 
    endGroup( "ParticleEmitterData" );
    endGroup( "ParticleEmitterData" );
 
 
+   addGroup("AFX");
+   addField("ejectionInvert",       TypeBool,    Offset(ejectionInvert,     ParticleEmitterData));
+   addField("fadeColor",            TypeBool,    Offset(fade_color,         ParticleEmitterData));
+   addField("fadeAlpha",            TypeBool,    Offset(fade_alpha,         ParticleEmitterData));
+   addField("fadeSize",             TypeBool,    Offset(fade_size,          ParticleEmitterData));
+   // useEmitterTransform currently does not work in TGEA or T3D
+   addField("useEmitterTransform",  TypeBool,    Offset(use_emitter_xfm,    ParticleEmitterData));
+   endGroup("AFX");
+
+#if defined(AFX_CAP_PARTICLE_POOLS)
+   addGroup("AFX Pooled Particles");
+   addField("poolData", TYPEID<afxParticlePoolData>(), Offset(pool_datablock, ParticleEmitterData));
+   addField("poolIndex",            TypeS32,                      Offset(pool_index,        ParticleEmitterData));
+   addField("poolDepthFade",        TypeBool,                     Offset(pool_depth_fade,   ParticleEmitterData));
+   addField("poolRadialFade",       TypeBool,                     Offset(pool_radial_fade,  ParticleEmitterData));
+   endGroup("AFX Pooled Particles");
+#endif
+   // disallow some field substitutions
+   disableFieldSubstitutions("particles");
+   onlyKeepClearSubstitutions("poolData"); // subs resolving to "~~", or "~0" are OK
    Parent::initPersistFields();
    Parent::initPersistFields();
 }
 }
 
 
@@ -358,6 +402,22 @@ void ParticleEmitterData::packData(BitStream* stream)
    stream->writeFlag(renderReflection);
    stream->writeFlag(renderReflection);
    stream->writeFlag(glow);
    stream->writeFlag(glow);
    stream->writeInt( blendStyle, 4 );
    stream->writeInt( blendStyle, 4 );
+
+   stream->writeFlag(ejectionInvert);
+   stream->writeFlag(fade_color);
+   stream->writeFlag(fade_alpha);
+   stream->writeFlag(fade_size);
+   stream->writeFlag(use_emitter_xfm);
+
+#if defined(AFX_CAP_PARTICLE_POOLS) 
+   if (stream->writeFlag(pool_datablock))
+   {
+     stream->writeRangedU32(packed ? SimObjectId((uintptr_t)pool_datablock) : pool_datablock->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast);
+     stream->write(pool_index);
+     stream->writeFlag(pool_depth_fade);
+     stream->writeFlag(pool_radial_fade);
+   }
+#endif 
 }
 }
 
 
 //-----------------------------------------------------------------------------
 //-----------------------------------------------------------------------------
@@ -421,6 +481,22 @@ void ParticleEmitterData::unpackData(BitStream* stream)
    renderReflection = stream->readFlag();
    renderReflection = stream->readFlag();
    glow = stream->readFlag();
    glow = stream->readFlag();
    blendStyle = stream->readInt( 4 );
    blendStyle = stream->readInt( 4 );
+   ejectionInvert = stream->readFlag();
+   fade_color = stream->readFlag();
+   fade_alpha = stream->readFlag();
+   fade_size = stream->readFlag();
+   use_emitter_xfm = stream->readFlag();
+   
+#if defined(AFX_CAP_PARTICLE_POOLS) 
+   if (stream->readFlag())
+   {
+      pool_datablock = (afxParticlePoolData*)stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast);
+      stream->read(&pool_index);
+      pool_depth_fade = stream->readFlag();
+      pool_radial_fade = stream->readFlag();
+      do_pool_id_convert = true;
+   }
+#endif 
 }
 }
 
 
 //-----------------------------------------------------------------------------
 //-----------------------------------------------------------------------------
@@ -600,6 +676,22 @@ bool ParticleEmitterData::preload(bool server, String &errorStr)
 
 
    if (!server)
    if (!server)
    {
    {
+#if defined(AFX_CAP_PARTICLE_POOLS)
+      if (do_pool_id_convert)
+      {
+        SimObjectId db_id = (SimObjectId)(uintptr_t)pool_datablock;
+        if (db_id != 0)
+        {
+          // try to convert id to pointer
+          if (!Sim::findObject(db_id, pool_datablock))
+          {
+            Con::errorf("ParticleEmitterData::reload() -- bad datablockId: 0x%x (poolData)", db_id);
+          }
+        }
+        do_pool_id_convert = false;
+      }
+#endif
+
      // load emitter texture if specified
      // load emitter texture if specified
      if (textureName && textureName[0])
      if (textureName && textureName[0])
      {
      {
@@ -669,6 +761,8 @@ void ParticleEmitterData::allocPrimBuffer( S32 overrideSize )
 
 
    partListInitSize = maxPartLife / (ejectionPeriodMS - periodVarianceMS);
    partListInitSize = maxPartLife / (ejectionPeriodMS - periodVarianceMS);
    partListInitSize += 8; // add 8 as "fudge factor" to make sure it doesn't realloc if it goes over by 1
    partListInitSize += 8; // add 8 as "fudge factor" to make sure it doesn't realloc if it goes over by 1
+   if (parts_per_eject > 1)
+     partListInitSize *= parts_per_eject;
 
 
    // if override size is specified, then the emitter overran its buffer and needs a larger allocation
    // if override size is specified, then the emitter overran its buffer and needs a larger allocation
    if( overrideSize != -1 )
    if( overrideSize != -1 )
@@ -705,6 +799,134 @@ void ParticleEmitterData::allocPrimBuffer( S32 overrideSize )
    delete [] indices;
    delete [] indices;
 }
 }
 
 
+//#define TRACK_PARTICLE_EMITTER_DATA_CLONES
+
+#ifdef TRACK_PARTICLE_EMITTER_DATA_CLONES
+static int emitter_data_clones = 0;
+#endif
+
+ParticleEmitterData::ParticleEmitterData(const ParticleEmitterData& other, bool temp_clone) : GameBaseData(other, temp_clone)
+{
+#ifdef TRACK_PARTICLE_EMITTER_DATA_CLONES
+   emitter_data_clones++;
+   if (emitter_data_clones == 1)
+     Con::errorf("ParticleEmitterData -- Clones are on the loose!");
+#endif
+
+   ejectionPeriodMS = other.ejectionPeriodMS;
+   periodVarianceMS = other.periodVarianceMS;
+   ejectionVelocity = other.ejectionVelocity;
+   velocityVariance = other.velocityVariance;
+   ejectionOffset = other.ejectionOffset;
+   ejectionOffsetVariance = other.ejectionOffsetVariance;
+   thetaMin = other.thetaMin;
+   thetaMax = other.thetaMax;
+   phiReferenceVel = other.phiReferenceVel;
+   phiVariance = other.phiVariance;
+   softnessDistance = other.softnessDistance;
+   ambientFactor = other.ambientFactor;
+   lifetimeMS = other.lifetimeMS;
+   lifetimeVarianceMS = other.lifetimeVarianceMS;
+   overrideAdvance = other.overrideAdvance;
+   orientParticles = other.orientParticles;
+   orientOnVelocity = other.orientOnVelocity;
+   useEmitterSizes = other.useEmitterSizes;
+   useEmitterColors = other.useEmitterColors;
+   alignParticles = other.alignParticles;
+   alignDirection = other.alignDirection;
+   particleString = other.particleString;
+   particleDataBlocks = other.particleDataBlocks; // -- derived from particleString
+   dataBlockIds = other.dataBlockIds; // -- derived from particleString
+   partListInitSize = other.partListInitSize; // -- approx calc from other fields
+   primBuff = other.primBuff;
+   blendStyle = other.blendStyle;
+   sortParticles = other.sortParticles;
+   reverseOrder = other.reverseOrder;
+   textureName = other.textureName;
+   textureHandle = other.textureHandle; // -- TextureHandle loads using textureName
+   highResOnly = other.highResOnly;
+   renderReflection = other.renderReflection;
+   fade_color = other.fade_color;
+   fade_size = other.fade_size;
+   fade_alpha = other.fade_alpha;
+   ejectionInvert = other.ejectionInvert;
+   parts_per_eject = other.parts_per_eject; // -- set to 1 (used by subclasses)
+   use_emitter_xfm = other.use_emitter_xfm;
+#if defined(AFX_CAP_PARTICLE_POOLS)
+   pool_datablock = other.pool_datablock;
+   pool_index = other.pool_index;
+   pool_depth_fade = other.pool_depth_fade;
+   pool_radial_fade = other.pool_radial_fade;
+   do_pool_id_convert = other.do_pool_id_convert; // -- flags pool id conversion need
+#endif
+}
+
+ParticleEmitterData::~ParticleEmitterData()
+{
+  if (!isTempClone())
+    return;
+
+  for (S32 i = 0; i < particleDataBlocks.size(); i++)
+  {
+    if (particleDataBlocks[i] && particleDataBlocks[i]->isTempClone())
+    {
+      delete particleDataBlocks[i];
+      particleDataBlocks[i] = 0;
+    }
+  }
+
+#ifdef TRACK_PARTICLE_EMITTER_DATA_CLONES
+  if (emitter_data_clones > 0)
+  {
+    emitter_data_clones--;
+    if (emitter_data_clones == 0)
+      Con::errorf("ParticleEmitterData -- Clones eliminated!");
+  }
+  else
+    Con::errorf("ParticleEmitterData -- Too many clones deleted!");
+#endif
+}
+
+ParticleEmitterData* ParticleEmitterData::cloneAndPerformSubstitutions(const SimObject* owner, S32 index)
+{
+  if (!owner)
+    return this;
+
+  bool clone_parts_db = false;
+
+  // note -- this could be checked when the particle blocks are evaluated
+  for (S32 i = 0; i < this->particleDataBlocks.size(); i++)
+  {
+    if (this->particleDataBlocks[i] && (this->particleDataBlocks[i]->getSubstitutionCount() > 0))
+    {
+      clone_parts_db = true;
+      break;
+    }
+  }
+
+  ParticleEmitterData* sub_emitter_db = this;
+
+  if (this->getSubstitutionCount() > 0 || clone_parts_db)
+  {
+    sub_emitter_db = new ParticleEmitterData(*this, true);
+    performSubstitutions(sub_emitter_db, owner, index);
+
+    if (clone_parts_db)
+    {
+      for (S32 i = 0; i < sub_emitter_db->particleDataBlocks.size(); i++)
+      {
+        if (sub_emitter_db->particleDataBlocks[i] && (sub_emitter_db->particleDataBlocks[i]->getSubstitutionCount() > 0))
+        {
+          ParticleData* orig_db = sub_emitter_db->particleDataBlocks[i];
+          sub_emitter_db->particleDataBlocks[i] = new ParticleData(*orig_db, true);
+          orig_db->performSubstitutions(sub_emitter_db->particleDataBlocks[i], owner, index);
+        }
+      }
+    }
+  }
+
+  return sub_emitter_db;
+}
 
 
 //-----------------------------------------------------------------------------
 //-----------------------------------------------------------------------------
 // ParticleEmitter
 // ParticleEmitter
@@ -736,6 +958,16 @@ ParticleEmitter::ParticleEmitter()
 
 
    // ParticleEmitter should be allocated on the client only.
    // ParticleEmitter should be allocated on the client only.
    mNetFlags.set( IsGhost );
    mNetFlags.set( IsGhost );
+   fade_amt = 1.0f;
+   forced_bbox = false;
+   db_temp_clone = false;
+   pos_pe.set(0,0,0);
+   sort_priority = 0;
+   mDataBlock = 0;
+   
+#if defined(AFX_CAP_PARTICLE_POOLS)
+   pool = 0;
+#endif 
 }
 }
 
 
 //-----------------------------------------------------------------------------
 //-----------------------------------------------------------------------------
@@ -747,6 +979,19 @@ ParticleEmitter::~ParticleEmitter()
    {
    {
       delete [] part_store[i];
       delete [] part_store[i];
    }
    }
+   if (db_temp_clone && mDataBlock && mDataBlock->isTempClone())
+   {
+     for (S32 i = 0; i < mDataBlock->particleDataBlocks.size(); i++)
+     {
+       if (mDataBlock->particleDataBlocks[i] && mDataBlock->particleDataBlocks[i]->isTempClone())
+       {
+         delete mDataBlock->particleDataBlocks[i];
+         mDataBlock->particleDataBlocks[i] = 0;
+       }
+     }
+     delete mDataBlock;
+     mDataBlock = 0;
+   }
 }
 }
 
 
 //-----------------------------------------------------------------------------
 //-----------------------------------------------------------------------------
@@ -771,6 +1016,11 @@ bool ParticleEmitter::onAdd()
    mObjBox.maxExtents = Point3F(radius, radius, radius);
    mObjBox.maxExtents = Point3F(radius, radius, radius);
    resetWorldBox();
    resetWorldBox();
 
 
+#if defined(AFX_CAP_PARTICLE_POOLS) 
+   if (pool)
+     pool->addParticleEmitter(this);
+#endif
+
    return true;
    return true;
 }
 }
 
 
@@ -780,6 +1030,14 @@ bool ParticleEmitter::onAdd()
 //-----------------------------------------------------------------------------
 //-----------------------------------------------------------------------------
 void ParticleEmitter::onRemove()
 void ParticleEmitter::onRemove()
 {
 {
+#if defined(AFX_CAP_PARTICLE_POOLS) 
+  if (pool)
+  {
+    pool->removeParticleEmitter(this);
+    pool = 0;
+  }
+#endif
+
    removeFromScene();
    removeFromScene();
    Parent::onRemove();
    Parent::onRemove();
 }
 }
@@ -825,6 +1083,11 @@ bool ParticleEmitter::onNewDataBlock( GameBaseData *dptr, bool reload )
       part_list_head.next = NULL;
       part_list_head.next = NULL;
       n_parts = 0;
       n_parts = 0;
    }
    }
+   if (mDataBlock->isTempClone())
+   {
+     db_temp_clone = true;
+     return true;
+   }
 
 
    scriptOnNewDataBlock();
    scriptOnNewDataBlock();
    return true;
    return true;
@@ -861,6 +1124,11 @@ LinearColorF ParticleEmitter::getCollectiveColor()
 //-----------------------------------------------------------------------------
 //-----------------------------------------------------------------------------
 void ParticleEmitter::prepRenderImage(SceneRenderState* state)
 void ParticleEmitter::prepRenderImage(SceneRenderState* state)
 {
 {
+#if defined(AFX_CAP_PARTICLE_POOLS)
+   if (pool)
+     return;
+#endif 
+
    if( state->isReflectPass() && !getDataBlock()->renderReflection )
    if( state->isReflectPass() && !getDataBlock()->renderReflection )
       return;
       return;
 
 
@@ -889,6 +1157,7 @@ void ParticleEmitter::prepRenderImage(SceneRenderState* state)
    ri->translucentSort = true;
    ri->translucentSort = true;
    ri->type = RenderPassManager::RIT_Particle;
    ri->type = RenderPassManager::RIT_Particle;
    ri->sortDistSq = getRenderWorldBox().getSqDistanceToPoint( camPos );
    ri->sortDistSq = getRenderWorldBox().getSqDistanceToPoint( camPos );
+   ri->defaultKey = (-sort_priority*100);
 
 
    // Draw the system offscreen unless the highResOnly flag is set on the datablock
    // Draw the system offscreen unless the highResOnly flag is set on the datablock
    ri->systemState = ( getDataBlock()->highResOnly ? PSS_AwaitingHighResDraw : PSS_AwaitingOffscreenDraw );
    ri->systemState = ( getDataBlock()->highResOnly ? PSS_AwaitingHighResDraw : PSS_AwaitingOffscreenDraw );
@@ -992,6 +1261,7 @@ void ParticleEmitter::emitParticles(const Point3F& point,
       return;
       return;
    }
    }
 
 
+   pos_pe = point;
    Point3F realStart;
    Point3F realStart;
    if( useLastPosition && mHasLastPosition )
    if( useLastPosition && mHasLastPosition )
       realStart = mLastPosition;
       realStart = mLastPosition;
@@ -1059,7 +1329,8 @@ void ParticleEmitter::emitParticles(const Point3F& start,
          // Create particle at the correct position
          // Create particle at the correct position
          Point3F pos;
          Point3F pos;
          pos.interpolate(start, end, F32(currTime) / F32(numMilliseconds));
          pos.interpolate(start, end, F32(currTime) / F32(numMilliseconds));
-         addParticle(pos, axis, velocity, axisx);
+         addParticle(pos, axis, velocity, axisx, numMilliseconds-currTime);
+
          particlesAdded = true;
          particlesAdded = true;
          mNextParticleTime = 0;
          mNextParticleTime = 0;
       }
       }
@@ -1089,7 +1360,7 @@ void ParticleEmitter::emitParticles(const Point3F& start,
       // Create particle at the correct position
       // Create particle at the correct position
       Point3F pos;
       Point3F pos;
       pos.interpolate(start, end, F32(currTime) / F32(numMilliseconds));
       pos.interpolate(start, end, F32(currTime) / F32(numMilliseconds));
-      addParticle(pos, axis, velocity, axisx);
+      addParticle(pos, axis, velocity, axisx, numMilliseconds-currTime);
       particlesAdded = true;
       particlesAdded = true;
 
 
       //   This override-advance code is restored in order to correctly adjust
       //   This override-advance code is restored in order to correctly adjust
@@ -1114,17 +1385,27 @@ void ParticleEmitter::emitParticles(const Point3F& start,
          {
          {
             if (advanceMS != 0)
             if (advanceMS != 0)
             {
             {
-              F32 t = F32(advanceMS) / 1000.0;
+               F32 t = F32(advanceMS) / 1000.0;
+
+               Point3F a = last_part->acc;
+               a -= last_part->vel * last_part->dataBlock->dragCoefficient;
+               a -= mWindVelocity * last_part->dataBlock->windCoefficient;
+               //a += Point3F(0.0f, 0.0f, -9.81f) * last_part->dataBlock->gravityCoefficient;
+               a.z += -9.81f*last_part->dataBlock->gravityCoefficient; // as long as gravity is a constant, this is faster
+
+               last_part->vel += a * t;
+               //last_part->pos += last_part->vel * t;
+               last_part->pos_local += last_part->vel * t;
 
 
-              Point3F a = last_part->acc;
-              a -= last_part->vel * last_part->dataBlock->dragCoefficient;
-              a -= mWindVelocity * last_part->dataBlock->windCoefficient;
-              a += Point3F(0.0f, 0.0f, -9.81f) * last_part->dataBlock->gravityCoefficient;
+               // AFX -- allow subclasses to adjust the particle params here
+               sub_particleUpdate(last_part);
 
 
-              last_part->vel += a * t;
-              last_part->pos += last_part->vel * t;
+               if (last_part->dataBlock->constrain_pos)
+                  last_part->pos = last_part->pos_local + this->pos_pe;
+               else
+                  last_part->pos = last_part->pos_local;
 
 
-              updateKeyData( last_part );
+               updateKeyData( last_part );
             }
             }
          }
          }
       }
       }
@@ -1196,7 +1477,7 @@ void ParticleEmitter::emitParticles(const Point3F& rCenter,
       axis.normalize();
       axis.normalize();
       pos += rCenter;
       pos += rCenter;
 
 
-      addParticle(pos, axis, velocity, axisz);
+      addParticle(pos, axis, velocity, axisz, 0);
    }
    }
 
 
    // Set world bounding box
    // Set world bounding box
@@ -1219,6 +1500,8 @@ void ParticleEmitter::emitParticles(const Point3F& rCenter,
 //-----------------------------------------------------------------------------
 //-----------------------------------------------------------------------------
 void ParticleEmitter::updateBBox()
 void ParticleEmitter::updateBBox()
 {
 {
+   if (forced_bbox)
+     return;
    Point3F minPt(1e10,   1e10,  1e10);
    Point3F minPt(1e10,   1e10,  1e10);
    Point3F maxPt(-1e10, -1e10, -1e10);
    Point3F maxPt(-1e10, -1e10, -1e10);
 
 
@@ -1239,15 +1522,18 @@ void ParticleEmitter::updateBBox()
    boxScale.y = getMax(boxScale.y, 1.0f);
    boxScale.y = getMax(boxScale.y, 1.0f);
    boxScale.z = getMax(boxScale.z, 1.0f);
    boxScale.z = getMax(boxScale.z, 1.0f);
    mBBObjToWorld.scale(boxScale);
    mBBObjToWorld.scale(boxScale);
+
+#if defined(AFX_CAP_PARTICLE_POOLS)
+   if (pool)
+     pool->updatePoolBBox(this);
+#endif
 }
 }
 
 
 //-----------------------------------------------------------------------------
 //-----------------------------------------------------------------------------
 // addParticle
 // addParticle
 //-----------------------------------------------------------------------------
 //-----------------------------------------------------------------------------
-void ParticleEmitter::addParticle(const Point3F& pos,
-                                  const Point3F& axis,
-                                  const Point3F& vel,
-                                  const Point3F& axisx)
+void ParticleEmitter::addParticle(const Point3F& pos, const Point3F& axis, const Point3F& vel,
+                                  const Point3F& axisx, const U32 age_offset)
 {
 {
    n_parts++;
    n_parts++;
    if (n_parts > n_part_capacity || n_parts > mDataBlock->partListInitSize)
    if (n_parts > n_part_capacity || n_parts > mDataBlock->partListInitSize)
@@ -1269,6 +1555,16 @@ void ParticleEmitter::addParticle(const Point3F& pos,
    pNew->next = part_list_head.next;
    pNew->next = part_list_head.next;
    part_list_head.next = pNew;
    part_list_head.next = pNew;
 
 
+   // for earlier access to constrain_pos, the ParticleData datablock is chosen here instead
+   // of later in the method.
+   U32 dBlockIndex = gRandGen.randI() % mDataBlock->particleDataBlocks.size();
+   ParticleData* part_db = mDataBlock->particleDataBlocks[dBlockIndex];
+   // set start position to world or local space
+   Point3F pos_start; 
+   if (part_db->constrain_pos)
+     pos_start.set(0,0,0);
+   else
+     pos_start = pos;
    Point3F ejectionAxis = axis;
    Point3F ejectionAxis = axis;
    F32 theta = (mDataBlock->thetaMax - mDataBlock->thetaMin) * gRandGen.randF() +
    F32 theta = (mDataBlock->thetaMax - mDataBlock->thetaMin) * gRandGen.randF() +
                mDataBlock->thetaMin;
                mDataBlock->thetaMin;
@@ -1290,14 +1586,17 @@ void ParticleEmitter::addParticle(const Point3F& pos,
    F32 initialVel = mDataBlock->ejectionVelocity;
    F32 initialVel = mDataBlock->ejectionVelocity;
    initialVel    += (mDataBlock->velocityVariance * 2.0f * gRandGen.randF()) - mDataBlock->velocityVariance;
    initialVel    += (mDataBlock->velocityVariance * 2.0f * gRandGen.randF()) - mDataBlock->velocityVariance;
 
 
-   pNew->pos = pos + (ejectionAxis * (mDataBlock->ejectionOffset + mDataBlock->ejectionOffsetVariance* gRandGen.randF()) );
-   pNew->vel = ejectionAxis * initialVel;
-   pNew->orientDir = ejectionAxis;
+   pNew->pos = pos_start + (ejectionAxis * (mDataBlock->ejectionOffset + mDataBlock->ejectionOffsetVariance* gRandGen.randF()) );
+   pNew->pos_local = pNew->pos;
+   pNew->vel = mDataBlock->ejectionInvert ? ejectionAxis * -initialVel : ejectionAxis * initialVel;
+   if (mDataBlock->orientParticles)
+     pNew->orientDir = ejectionAxis;
+   else
+     // note -- for non-oriented particles, we use orientDir.x to store the billboard start angle.
+     pNew->orientDir.x = mDegToRad(part_db->start_angle + part_db->angle_variance*2.0f*gRandGen.randF() - part_db->angle_variance);
    pNew->acc.set(0, 0, 0);
    pNew->acc.set(0, 0, 0);
-   pNew->currentAge = 0;
-
-   // Choose a new particle datablack randomly from the list
-   U32 dBlockIndex = gRandGen.randI() % mDataBlock->particleDataBlocks.size();
+   pNew->currentAge = age_offset;
+   pNew->t_last = 0.0f;
    mDataBlock->particleDataBlocks[dBlockIndex]->initializeParticle(pNew, vel);
    mDataBlock->particleDataBlocks[dBlockIndex]->initializeParticle(pNew, vel);
    updateKeyData( pNew );
    updateKeyData( pNew );
 
 
@@ -1379,8 +1678,10 @@ void ParticleEmitter::updateKeyData( Particle *part )
 	if( part->totalLifetime < 1 )
 	if( part->totalLifetime < 1 )
 		part->totalLifetime = 1;
 		part->totalLifetime = 1;
 
 
-   F32 t = F32(part->currentAge) / F32(part->totalLifetime);
-   AssertFatal(t <= 1.0f, "Out out bounds filter function for particle.");
+   if (part->currentAge > part->totalLifetime)
+      part->currentAge = part->totalLifetime;
+   F32 t = (F32)part->currentAge / (F32)part->totalLifetime;
+  
 
 
    for( U32 i = 1; i < ParticleData::PDC_NUM_KEYS; i++ )
    for( U32 i = 1; i < ParticleData::PDC_NUM_KEYS; i++ )
    {
    {
@@ -1412,7 +1713,25 @@ void ParticleEmitter::updateKeyData( Particle *part )
          {
          {
             part->size = (part->dataBlock->sizes[i-1] * (1.0 - firstPart)) +
             part->size = (part->dataBlock->sizes[i-1] * (1.0 - firstPart)) +
                          (part->dataBlock->sizes[i]   * firstPart);
                          (part->dataBlock->sizes[i]   * firstPart);
+            part->size *= part->dataBlock->sizeBias;
+         }
+		 
+         if (mDataBlock->fade_color) 
+         {
+           if (mDataBlock->fade_alpha) 
+             part->color *= fade_amt; 
+           else 
+           {
+             part->color.red *= fade_amt; 
+             part->color.green *= fade_amt; 
+             part->color.blue *= fade_amt; 
+           }
          }
          }
+         else if (mDataBlock->fade_alpha) 
+           part->color.alpha *= fade_amt;
+
+         if (mDataBlock->fade_size)
+           part->size *= fade_amt;
          break;
          break;
 
 
       }
       }
@@ -1424,19 +1743,25 @@ void ParticleEmitter::updateKeyData( Particle *part )
 //-----------------------------------------------------------------------------
 //-----------------------------------------------------------------------------
 void ParticleEmitter::update( U32 ms )
 void ParticleEmitter::update( U32 ms )
 {
 {
-   // TODO: Prefetch
+   F32 t = F32(ms)/1000.0f; // AFX -- moved outside loop, no need to recalculate this for every particle
 
 
    for (Particle* part = part_list_head.next; part != NULL; part = part->next)
    for (Particle* part = part_list_head.next; part != NULL; part = part->next)
    {
    {
-      F32 t = F32(ms) / 1000.0;
-
       Point3F a = part->acc;
       Point3F a = part->acc;
-      a -= part->vel        * part->dataBlock->dragCoefficient;
+      a -= part->vel * part->dataBlock->dragCoefficient;
       a -= mWindVelocity * part->dataBlock->windCoefficient;
       a -= mWindVelocity * part->dataBlock->windCoefficient;
-      a += Point3F(0.0f, 0.0f, -9.81f) * part->dataBlock->gravityCoefficient;
+      a.z += -9.81f*part->dataBlock->gravityCoefficient; // AFX -- as long as gravity is a constant, this is faster
 
 
       part->vel += a * t;
       part->vel += a * t;
-      part->pos += part->vel * t;
+      part->pos_local += part->vel * t; 
+
+      // AFX -- allow subclasses to adjust the particle params here
+      sub_particleUpdate(part);
+
+      if (part->dataBlock->constrain_pos)
+        part->pos = part->pos_local + this->pos_pe;
+      else
+        part->pos = part->pos_local;
 
 
       updateKeyData( part );
       updateKeyData( part );
    }
    }
@@ -1999,3 +2324,43 @@ DefineEngineMethod(ParticleEmitterData, reload, void,(),,
 {
 {
    object->reload();
    object->reload();
 }
 }
+void ParticleEmitter::emitParticlesExt(const MatrixF& xfm, const Point3F& point, 
+                                       const Point3F& velocity, const U32 numMilliseconds)
+{
+   if (mDataBlock->use_emitter_xfm)
+   {
+      Point3F zero_point(0.0f, 0.0f, 0.0f);
+      this->pos_pe = zero_point;
+      this->setTransform(xfm);
+      Point3F axis(0.0,0.0,1.0);    
+      xfm.mulV(axis);
+      emitParticles(zero_point, true, axis, velocity, numMilliseconds);
+   }
+   else
+   {
+      this->pos_pe = point;
+      Point3F axis(0.0,0.0,1.0);    
+      xfm.mulV(axis);
+      emitParticles(point, true, axis, velocity, numMilliseconds);
+   }
+}  
+
+void ParticleEmitter::setForcedObjBox(Box3F& box) 
+{
+  mObjBox = box;
+  forced_bbox = true;
+#if defined(AFX_CAP_PARTICLE_POOLS)
+  if (pool)
+    pool->updatePoolBBox(this);
+#endif
+}
+
+void ParticleEmitter::setSortPriority(S8 priority) 
+{
+  sort_priority = (priority == 0) ? 1 : priority;
+#if defined(AFX_CAP_PARTICLE_POOLS)
+  if (pool)
+    pool->setSortPriority(sort_priority);
+#endif
+}
+

+ 65 - 1
Engine/source/T3D/fx/particleEmitter.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 _H_PARTICLE_EMITTER
 #ifndef _H_PARTICLE_EMITTER
 #define _H_PARTICLE_EMITTER
 #define _H_PARTICLE_EMITTER
 
 
@@ -42,6 +47,12 @@
 class RenderPassManager;
 class RenderPassManager;
 class ParticleData;
 class ParticleData;
 
 
+#define AFX_CAP_PARTICLE_POOLS
+#if defined(AFX_CAP_PARTICLE_POOLS)
+class afxParticlePoolData;
+class afxParticlePool;
+#endif
+
 //*****************************************************************************
 //*****************************************************************************
 // Particle Emitter Data
 // Particle Emitter Data
 //*****************************************************************************
 //*****************************************************************************
@@ -113,6 +124,26 @@ class ParticleEmitterData : public GameBaseData
    bool glow;                                ///< Renders this emitter into the glow buffer.
    bool glow;                                ///< Renders this emitter into the glow buffer.
 
 
    bool reload();
    bool reload();
+public:
+   bool         fade_color;
+   bool         fade_size;
+   bool         fade_alpha;
+   bool         ejectionInvert;
+   U8           parts_per_eject;
+   bool         use_emitter_xfm;
+#if defined(AFX_CAP_PARTICLE_POOLS) 
+public:
+   afxParticlePoolData* pool_datablock;
+   U32          pool_index;
+   bool         pool_depth_fade;
+   bool         pool_radial_fade;
+   bool         do_pool_id_convert;
+#endif
+public:
+   /*C*/ ParticleEmitterData(const ParticleEmitterData&, bool = false);
+   /*D*/ ~ParticleEmitterData();
+   virtual ParticleEmitterData* cloneAndPerformSubstitutions(const SimObject*, S32 index=0);
+   virtual bool allowSubstitutions() const { return true; }
 };
 };
 
 
 //*****************************************************************************
 //*****************************************************************************
@@ -121,6 +152,9 @@ class ParticleEmitterData : public GameBaseData
 class ParticleEmitter : public GameBase
 class ParticleEmitter : public GameBase
 {
 {
    typedef GameBase Parent;
    typedef GameBase Parent;
+#if defined(AFX_CAP_PARTICLE_POOLS) 
+   friend class afxParticlePool;
+#endif 
 
 
   public:
   public:
 
 
@@ -190,7 +224,7 @@ class ParticleEmitter : public GameBase
    /// @param   axis
    /// @param   axis
    /// @param   vel   Initial velocity
    /// @param   vel   Initial velocity
    /// @param   axisx
    /// @param   axisx
-   void addParticle(const Point3F &pos, const Point3F &axis, const Point3F &vel, const Point3F &axisx);
+   void addParticle(const Point3F &pos, const Point3F &axis, const Point3F &vel, const Point3F &axisx, const U32 age_offset);
 
 
 
 
    inline void setupBillboard( Particle *part,
    inline void setupBillboard( Particle *part,
@@ -227,7 +261,12 @@ class ParticleEmitter : public GameBase
    // PEngine interface
    // PEngine interface
   private:
   private:
 
 
+   // AFX subclasses to ParticleEmitter require access to some members and methods of
+   // ParticleEmitter which are normally declared with private scope. In this section,
+   // protected and private scope statements have been inserted inline with the original
+   // code to expose the necessary members and methods.
    void update( U32 ms );
    void update( U32 ms );
+protected:
    inline void updateKeyData( Particle *part );
    inline void updateKeyData( Particle *part );
  
  
 
 
@@ -239,25 +278,30 @@ class ParticleEmitter : public GameBase
 
 
    ParticleEmitterData* mDataBlock;
    ParticleEmitterData* mDataBlock;
 
 
+protected: 
    U32       mInternalClock;
    U32       mInternalClock;
 
 
    U32       mNextParticleTime;
    U32       mNextParticleTime;
 
 
    Point3F   mLastPosition;
    Point3F   mLastPosition;
    bool      mHasLastPosition;
    bool      mHasLastPosition;
+private:   
    MatrixF   mBBObjToWorld;
    MatrixF   mBBObjToWorld;
 
 
    bool      mDeleteWhenEmpty;
    bool      mDeleteWhenEmpty;
    bool      mDeleteOnTick;
    bool      mDeleteOnTick;
 
 
+protected: 
    S32       mLifetimeMS;
    S32       mLifetimeMS;
    S32       mElapsedTimeMS;
    S32       mElapsedTimeMS;
 
 
+private:  
    F32       sizes[ ParticleData::PDC_NUM_KEYS ];
    F32       sizes[ ParticleData::PDC_NUM_KEYS ];
    LinearColorF    colors[ ParticleData::PDC_NUM_KEYS ];
    LinearColorF    colors[ ParticleData::PDC_NUM_KEYS ];
 
 
    GFXVertexBufferHandle<ParticleVertexType> mVertBuff;
    GFXVertexBufferHandle<ParticleVertexType> mVertBuff;
 
 
+protected:
    //   These members are for implementing a link-list of the active emitter 
    //   These members are for implementing a link-list of the active emitter 
    //   particles. Member part_store contains blocks of particles that can be
    //   particles. Member part_store contains blocks of particles that can be
    //   chained in a link-list. Usually the first part_store block is large
    //   chained in a link-list. Usually the first part_store block is large
@@ -268,8 +312,28 @@ class ParticleEmitter : public GameBase
    Particle   part_list_head;
    Particle   part_list_head;
    S32        n_part_capacity;
    S32        n_part_capacity;
    S32        n_parts;
    S32        n_parts;
+private:    
    S32       mCurBuffSize;
    S32       mCurBuffSize;
 
 
+  protected:
+   F32 fade_amt;
+   bool forced_bbox;
+   bool db_temp_clone;
+   Point3F pos_pe;
+   S8 sort_priority;
+   virtual void sub_particleUpdate(Particle*) { }
+  public:
+   virtual void emitParticlesExt(const MatrixF& xfm, const Point3F& point, const Point3F& velocity, const U32 numMilliseconds);
+   void setFadeAmount(F32 amt) { fade_amt = amt; }  
+   void setForcedObjBox(Box3F& box);
+   void setSortPriority(S8 priority);
+#if defined(AFX_CAP_PARTICLE_POOLS)
+  protected:
+   afxParticlePool* pool;
+  public:
+   void clearPool() { pool = 0; }
+   void setPool(afxParticlePool* p) { pool = p; }
+#endif
 };
 };
 
 
 #endif // _H_PARTICLE_EMITTER
 #endif // _H_PARTICLE_EMITTER

+ 39 - 2
Engine/source/T3D/gameBase/gameBase.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/gameBase/gameBase.h"
 #include "T3D/gameBase/gameBase.h"
 #include "console/consoleTypes.h"
 #include "console/consoleTypes.h"
@@ -36,6 +41,7 @@
 #include "T3D/aiConnection.h"
 #include "T3D/aiConnection.h"
 #endif
 #endif
 
 
+#include "afx/arcaneFX.h"
 //----------------------------------------------------------------------------
 //----------------------------------------------------------------------------
 // Ghost update relative priority values
 // Ghost update relative priority values
 
 
@@ -122,6 +128,12 @@ GameBaseData::GameBaseData()
    category = "";
    category = "";
    packed = false;
    packed = false;
 }
 }
+GameBaseData::GameBaseData(const GameBaseData& other, bool temp_clone) : SimDataBlock(other, temp_clone)
+{
+   packed = other.packed;
+   category = other.category;
+   //mReloadSignal = other.mReloadSignal; // DO NOT copy the mReloadSignal member. 
+}
 
 
 void GameBaseData::inspectPostApply()
 void GameBaseData::inspectPostApply()
 {
 {
@@ -244,6 +256,8 @@ GameBase::GameBase()
 
 
 GameBase::~GameBase()
 GameBase::~GameBase()
 {
 {
+   if (scope_registered)
+      arcaneFX::unregisterScopedObject(this);
 }
 }
 
 
 
 
@@ -256,8 +270,16 @@ bool GameBase::onAdd()
 
 
    // Datablock must be initialized on the server.
    // Datablock must be initialized on the server.
    // Client datablock are initialized by the initial update.
    // Client datablock are initialized by the initial update.
-   if ( isServerObject() && mDataBlock && !onNewDataBlock( mDataBlock, false ) )
-      return false;
+   if (isClientObject())
+   {
+      if (scope_id > 0 && !scope_registered)
+         arcaneFX::registerScopedObject(this);
+   }
+   else
+   {
+      if ( mDataBlock && !onNewDataBlock( mDataBlock, false ) )
+         return false;
+   }
 
 
    setProcessTick( true );
    setProcessTick( true );
 
 
@@ -266,6 +288,8 @@ bool GameBase::onAdd()
 
 
 void GameBase::onRemove()
 void GameBase::onRemove()
 {
 {
+   if (scope_registered)
+      arcaneFX::unregisterScopedObject(this);
    // EDITOR FEATURE: Remove us from the reload signal of our datablock.
    // EDITOR FEATURE: Remove us from the reload signal of our datablock.
    if ( mDataBlock )
    if ( mDataBlock )
       mDataBlock->mReloadSignal.remove( this, &GameBase::_onDatablockModified );
       mDataBlock->mReloadSignal.remove( this, &GameBase::_onDatablockModified );
@@ -290,6 +314,9 @@ bool GameBase::onNewDataBlock( GameBaseData *dptr, bool reload )
 
 
    if ( !mDataBlock )
    if ( !mDataBlock )
       return false;
       return false;
+   // Don't set mask when new datablock is a temp-clone.
+   if (mDataBlock->isTempClone())
+      return true;
 
 
    setMaskBits(DataBlockMask);
    setMaskBits(DataBlockMask);
    return true;
    return true;
@@ -543,6 +570,11 @@ U32 GameBase::packUpdate( NetConnection *connection, U32 mask, BitStream *stream
    stream->writeFlag(mIsAiControlled);
    stream->writeFlag(mIsAiControlled);
 #endif
 #endif
 
 
+   if (stream->writeFlag(mask & ScopeIdMask))
+   {
+      if (stream->writeFlag(scope_refs > 0))
+         stream->writeInt(scope_id, SCOPE_ID_BITS);
+   }
    return retMask;
    return retMask;
 }
 }
 
 
@@ -581,6 +613,11 @@ void GameBase::unpackUpdate(NetConnection *con, BitStream *stream)
    mTicksSinceLastMove = 0;
    mTicksSinceLastMove = 0;
    mIsAiControlled = stream->readFlag();
    mIsAiControlled = stream->readFlag();
 #endif
 #endif
+   if (stream->readFlag())
+   {
+      scope_id = (stream->readFlag()) ? (U16) stream->readInt(SCOPE_ID_BITS) : 0;
+      scope_refs = 0;
+   }
 }
 }
 
 
 void GameBase::onMount( SceneObject *obj, S32 node )
 void GameBase::onMount( SceneObject *obj, S32 node )

+ 11 - 1
Engine/source/T3D/gameBase/gameBase.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 _GAMEBASE_H_
 #ifndef _GAMEBASE_H_
 #define _GAMEBASE_H_
 #define _GAMEBASE_H_
 
 
@@ -113,6 +118,8 @@ public:
    DECLARE_CALLBACK( void, onMount, ( SceneObject* obj, SceneObject* mountObj, S32 node ) );
    DECLARE_CALLBACK( void, onMount, ( SceneObject* obj, SceneObject* mountObj, S32 node ) );
    DECLARE_CALLBACK( void, onUnmount, ( SceneObject* obj, SceneObject* mountObj, S32 node ) );
    DECLARE_CALLBACK( void, onUnmount, ( SceneObject* obj, SceneObject* mountObj, S32 node ) );
    /// @}
    /// @}
+public:
+   GameBaseData(const GameBaseData&, bool = false);
 };
 };
 
 
 //----------------------------------------------------------------------------
 //----------------------------------------------------------------------------
@@ -229,7 +236,8 @@ public:
    enum GameBaseMasks {      
    enum GameBaseMasks {      
       DataBlockMask     = Parent::NextFreeMask << 0,
       DataBlockMask     = Parent::NextFreeMask << 0,
       ExtendedInfoMask  = Parent::NextFreeMask << 1,
       ExtendedInfoMask  = Parent::NextFreeMask << 1,
-      NextFreeMask      = Parent::NextFreeMask << 2
+      ScopeIdMask       = Parent::NextFreeMask << 2,
+      NextFreeMask      = Parent::NextFreeMask << 3,
    };
    };
 
 
    // net flags added by game base
    // net flags added by game base
@@ -453,6 +461,8 @@ private:
    /// within this callback.
    /// within this callback.
    ///   
    ///   
    void _onDatablockModified();
    void _onDatablockModified();
+protected:
+   void    onScopeIdChange() { setMaskBits(ScopeIdMask); }
 };
 };
 
 
 
 

+ 609 - 1
Engine/source/T3D/gameBase/gameConnection.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/gameBase/gameConnection.h"
 #include "T3D/gameBase/gameConnection.h"
 
 
@@ -52,6 +57,11 @@
    #include "T3D/gameBase/std/stdMoveList.h"
    #include "T3D/gameBase/std/stdMoveList.h"
 #endif
 #endif
 
 
+#ifdef AFX_CAP_DATABLOCK_CACHE 
+#include "core/stream/fileStream.h"
+#endif 
+
+#include "afx/arcaneFX.h"
 //----------------------------------------------------------------------------
 //----------------------------------------------------------------------------
 #define MAX_MOVE_PACKET_SENDS 4
 #define MAX_MOVE_PACKET_SENDS 4
 
 
@@ -173,9 +183,26 @@ IMPLEMENT_CALLBACK( GameConnection, onFlash, void, (bool state), (state),
    "either is on or both are off.  Typically this is used to enable the flash postFx.\n\n"
    "either is on or both are off.  Typically this is used to enable the flash postFx.\n\n"
    "@param state Set to true if either the damage flash or white out conditions are active.\n\n");
    "@param state Set to true if either the damage flash or white out conditions are active.\n\n");
 
 
+#ifdef AFX_CAP_DATABLOCK_CACHE 
+StringTableEntry GameConnection::server_cache_filename = "";
+StringTableEntry GameConnection::client_cache_filename = "";
+bool GameConnection::server_cache_on = false;
+bool GameConnection::client_cache_on = false;
+#endif 
 //----------------------------------------------------------------------------
 //----------------------------------------------------------------------------
 GameConnection::GameConnection()
 GameConnection::GameConnection()
 {
 {
+   mRolloverObj = NULL;
+   mPreSelectedObj = NULL;
+   mSelectedObj = NULL;
+   mChangedSelectedObj = false;
+   mPreSelectTimestamp = 0;
+   zoned_in = false;
+   
+#ifdef AFX_CAP_DATABLOCK_CACHE 
+   client_db_stream = new InfiniteBitStream;
+   server_cache_CRC = 0xffffffff;
+#endif 
    mLagging = false;
    mLagging = false;
    mControlObject = NULL;
    mControlObject = NULL;
    mCameraObject = NULL;
    mCameraObject = NULL;
@@ -246,6 +273,10 @@ GameConnection::~GameConnection()
       dFree(mConnectArgv[i]);
       dFree(mConnectArgv[i]);
    dFree(mJoinPassword);
    dFree(mJoinPassword);
    delete mMoveList;
    delete mMoveList;
+
+#ifdef AFX_CAP_DATABLOCK_CACHE
+   delete client_db_stream;
+#endif 
 }
 }
 
 
 //----------------------------------------------------------------------------
 //----------------------------------------------------------------------------
@@ -1144,6 +1175,17 @@ void GameConnection::readPacket(BitStream *bstream)
    {
    {
       mMoveList->clientReadMovePacket(bstream);
       mMoveList->clientReadMovePacket(bstream);
 
 
+      // selected object - do we have a change in status?
+      if (bstream->readFlag()) 
+      { 
+         if (bstream->readFlag()) 
+         { 
+            S32 gIndex = bstream->readInt(NetConnection::GhostIdBitSize);
+            setSelectedObj(static_cast<SceneObject*>(resolveGhost(gIndex)));
+         }
+         else
+            setSelectedObj(NULL);
+      }
       bool hadFlash = mDamageFlash > 0 || mWhiteOut > 0;
       bool hadFlash = mDamageFlash > 0 || mWhiteOut > 0;
       mDamageFlash = 0;
       mDamageFlash = 0;
       mWhiteOut = 0;
       mWhiteOut = 0;
@@ -1387,6 +1429,35 @@ void GameConnection::writePacket(BitStream *bstream, PacketNotify *note)
       // all the damage flash & white out
       // all the damage flash & white out
 
 
       S32 gIndex = -1;
       S32 gIndex = -1;
+      if (mChangedSelectedObj)
+      {
+         S32 gidx;
+         // send NULL player
+         if ((mSelectedObj == NULL) || mSelectedObj.isNull())
+         {
+            bstream->writeFlag(true);
+            bstream->writeFlag(false);
+            mChangedSelectedObj = false;
+         }
+         // send ghost-idx
+         else if ((gidx = getGhostIndex(mSelectedObj)) != -1)
+         {
+            Con::printf("SEND OBJECT SELECTION");
+            bstream->writeFlag(true);
+            bstream->writeFlag(true);
+            bstream->writeInt(gidx, NetConnection::GhostIdBitSize);
+            mChangedSelectedObj = false;
+         }
+         // not fully changed yet
+         else
+         {
+            bstream->writeFlag(false);
+            mChangedSelectedObj = true;
+         }
+      }
+      else
+         bstream->writeFlag(false);
+		 
       if (!mControlObject.isNull())
       if (!mControlObject.isNull())
       {
       {
          gIndex = getGhostIndex(mControlObject);
          gIndex = getGhostIndex(mControlObject);
@@ -1608,6 +1679,14 @@ void GameConnection::preloadNextDataBlock(bool hadNewFiles)
          sendConnectionMessage(DataBlocksDownloadDone, mDataBlockSequence);
          sendConnectionMessage(DataBlocksDownloadDone, mDataBlockSequence);
 
 
 //          gResourceManager->setMissingFileLogging(false);
 //          gResourceManager->setMissingFileLogging(false);
+
+#ifdef AFX_CAP_DATABLOCK_CACHE 
+         // This should be the last of the datablocks. An argument of false
+         // indicates that this is a client save.
+         if (clientCacheEnabled())
+            saveDatablockCache(false);
+#endif 
+
          return;
          return;
       }
       }
       mFilesWereDownloaded = hadNewFiles;
       mFilesWereDownloaded = hadNewFiles;
@@ -1771,7 +1850,11 @@ DefineEngineMethod( GameConnection, transmitDataBlocks, void, (S32 sequence),,
     const U32 iCount = pGroup->size();
     const U32 iCount = pGroup->size();
 
 
     // If this is the local client...
     // If this is the local client...
+#ifdef AFX_CAP_DATABLOCK_CACHE 
+    if (GameConnection::getLocalClientConnection() == object && !GameConnection::serverCacheEnabled())
+#else
     if (GameConnection::getLocalClientConnection() == object)
     if (GameConnection::getLocalClientConnection() == object)
+#endif 
     {
     {
         // Set up a pointer to the datablock.
         // Set up a pointer to the datablock.
         SimDataBlock* pDataBlock = 0;
         SimDataBlock* pDataBlock = 0;
@@ -2166,6 +2249,13 @@ void GameConnection::consoleInit()
       "@ingroup Networking\n");
       "@ingroup Networking\n");
 
 
    // Con::addVariable("specialFog", TypeBool, &SceneGraph::useSpecial);
    // Con::addVariable("specialFog", TypeBool, &SceneGraph::useSpecial);
+
+#ifdef AFX_CAP_DATABLOCK_CACHE 
+   Con::addVariable("$Pref::Server::DatablockCacheFilename",  TypeString,   &server_cache_filename);
+   Con::addVariable("$pref::Client::DatablockCacheFilename",  TypeString,   &client_cache_filename);
+   Con::addVariable("$Pref::Server::EnableDatablockCache",    TypeBool,     &server_cache_on);
+   Con::addVariable("$pref::Client::EnableDatablockCache",    TypeBool,     &client_cache_on);
+#endif 
 }
 }
 
 
 DefineEngineMethod( GameConnection, startRecording, void, (const char* fileName),,
 DefineEngineMethod( GameConnection, startRecording, void, (const char* fileName),,
@@ -2360,4 +2450,522 @@ DefineEngineMethod( GameConnection, getVisibleGhostDistance, F32, (),,
    )
    )
 {
 {
    return object->getVisibleGhostDistance();
    return object->getVisibleGhostDistance();
-}
+}
+
+// The object selection code here is, in part, based, on functionality described
+// in the following resource:
+// Object Selection in Torque by Dave Myers 
+//   http://www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=7335
+
+ConsoleMethod(GameConnection, setSelectedObj, bool, 3, 4, "(object, [propagate_to_client])")
+{
+   SceneObject* pending_selection;
+   if (!Sim::findObject(argv[2], pending_selection))
+      return false;
+
+   bool propagate_to_client = (argc > 3) ? dAtob(argv[3]) : false;
+   object->setSelectedObj(pending_selection, propagate_to_client);
+
+   return true;
+}
+
+ConsoleMethod(GameConnection, getSelectedObj, S32, 2, 2, "()")
+{
+   SimObject* selected = object->getSelectedObj();
+   return (selected) ? selected->getId(): -1;
+}
+
+ConsoleMethod(GameConnection, clearSelectedObj, void, 2, 3, "([propagate_to_client])")
+{
+   bool propagate_to_client = (argc > 2) ? dAtob(argv[2]) : false;
+   object->setSelectedObj(NULL, propagate_to_client);
+}
+
+ConsoleMethod(GameConnection, setPreSelectedObjFromRollover, void, 2, 2, "()")
+{
+   object->setPreSelectedObjFromRollover();
+}
+
+ConsoleMethod(GameConnection, clearPreSelectedObj, void, 2, 2, "()")
+{
+   object->clearPreSelectedObj();
+}
+
+ConsoleMethod(GameConnection, setSelectedObjFromPreSelected, void, 2, 2, "()")
+{
+   object->setSelectedObjFromPreSelected();
+}
+
+void GameConnection::setSelectedObj(SceneObject* so, bool propagate_to_client) 
+{ 
+   if (!isConnectionToServer())
+   {
+      // clear previously selected object
+      if (mSelectedObj)
+         clearNotify(mSelectedObj);
+
+      // save new selection
+      mSelectedObj = so; 
+
+      // mark selected object
+      if (mSelectedObj)
+         deleteNotify(mSelectedObj);
+
+      // mark selection dirty
+      if (propagate_to_client)
+         mChangedSelectedObj = true; 
+
+      return;
+   }
+
+   // clear previously selected object
+   if (mSelectedObj)
+   {
+      mSelectedObj->setSelectionFlags(mSelectedObj->getSelectionFlags() & ~SceneObject::SELECTED);
+      clearNotify(mSelectedObj);
+      Con::executef(this, "onObjectDeselected", mSelectedObj->getIdString());
+   }
+
+   // save new selection
+   mSelectedObj = so; 
+
+   // mark selected object
+   if (mSelectedObj)
+   {
+      mSelectedObj->setSelectionFlags(mSelectedObj->getSelectionFlags() | SceneObject::SELECTED);
+      deleteNotify(mSelectedObj);
+   }
+
+   // mark selection dirty
+   //mChangedSelectedObj = true; 
+
+   // notify appropriate script of the change
+   if (mSelectedObj)
+      Con::executef(this, "onObjectSelected", mSelectedObj->getIdString());
+}
+
+void GameConnection::setRolloverObj(SceneObject* so) 
+{ 
+   // save new selection
+   mRolloverObj = so;  
+
+   // notify appropriate script of the change
+   Con::executef(this, "onObjectRollover", (mRolloverObj) ? mRolloverObj->getIdString() : "");
+}
+
+void GameConnection::setPreSelectedObjFromRollover()
+{
+   mPreSelectedObj = mRolloverObj;
+   mPreSelectTimestamp = Platform::getRealMilliseconds();
+}
+
+void GameConnection::clearPreSelectedObj()
+{
+   mPreSelectedObj = 0;
+   mPreSelectTimestamp = 0;
+}
+
+void GameConnection::setSelectedObjFromPreSelected()
+{
+   U32 now = Platform::getRealMilliseconds();
+   if (now - mPreSelectTimestamp < arcaneFX::sTargetSelectionTimeoutMS)
+      setSelectedObj(mPreSelectedObj);
+   mPreSelectedObj = 0;
+}
+
+void GameConnection::onDeleteNotify(SimObject* obj)
+{
+   if (obj == mSelectedObj)
+      setSelectedObj(NULL);
+
+   Parent::onDeleteNotify(obj);
+}
+
+#ifdef AFX_CAP_DATABLOCK_CACHE 
+
+void GameConnection::tempDisableStringBuffering(BitStream* bs) const 
+{ 
+   bs->setStringBuffer(0); 
+}
+
+void GameConnection::restoreStringBuffering(BitStream* bs) const 
+{ 
+   bs->clearStringBuffer();
+}              
+
+// rewind to stream postion and then move raw bytes into client_db_stream
+// for caching purposes.
+void GameConnection::repackClientDatablock(BitStream* bstream, S32 start_pos)
+{
+   static U8 bit_buffer[Net::MaxPacketDataSize];
+
+   if (!clientCacheEnabled() || !client_db_stream)
+      return;
+
+   S32 cur_pos = bstream->getCurPos();
+   S32 n_bits = cur_pos - start_pos;
+   if (n_bits <= 0)
+      return;
+
+   bstream->setCurPos(start_pos);
+   bstream->readBits(n_bits, bit_buffer);
+   bstream->setCurPos(cur_pos);
+
+   //S32 start_pos2 = client_db_stream->getCurPos();
+   client_db_stream->writeBits(n_bits, bit_buffer);
+}
+
+#define CLIENT_CACHE_VERSION_CODE 47241113
+
+void GameConnection::saveDatablockCache(bool on_server)
+{
+   InfiniteBitStream bit_stream;
+   BitStream* bstream = 0;
+
+   if (on_server)
+   {
+      SimDataBlockGroup *g = Sim::getDataBlockGroup();
+
+      // find the first one we haven't sent:
+      U32 i, groupCount = g->size();
+      S32 key = this->getDataBlockModifiedKey();
+      for (i = 0; i < groupCount; i++)
+         if (((SimDataBlock*)(*g)[i])->getModifiedKey() > key)
+            break;
+
+      // nothing to save
+      if (i == groupCount) 
+         return;
+
+      bstream = &bit_stream;
+
+      for (;i < groupCount; i++) 
+      {
+         SimDataBlock* obj = (SimDataBlock*)(*g)[i];
+         GameConnection* gc = this;
+         NetConnection* conn = this;
+         SimObjectId id = obj->getId();
+
+         if (bstream->writeFlag(gc->getDataBlockModifiedKey() < obj->getModifiedKey()))        // A - flag
+         {
+            if (obj->getModifiedKey() > gc->getMaxDataBlockModifiedKey())
+               gc->setMaxDataBlockModifiedKey(obj->getModifiedKey());
+
+            bstream->writeInt(id - DataBlockObjectIdFirst,DataBlockObjectIdBitSize);            // B - int
+
+            S32 classId = obj->getClassId(conn->getNetClassGroup());
+            bstream->writeClassId(classId, NetClassTypeDataBlock, conn->getNetClassGroup());    // C - id
+            bstream->writeInt(i, DataBlockObjectIdBitSize);                                     // D - int
+            bstream->writeInt(groupCount, DataBlockObjectIdBitSize + 1);                        // E - int
+            obj->packData(bstream);
+         }
+      }
+   }
+   else
+   {
+      bstream = client_db_stream;
+   }
+
+   if (bstream->getPosition() <= 0)
+      return;
+
+   // zero out any leftover bits short of an even byte count
+   U32 n_leftover_bits = (bstream->getPosition()*8) - bstream->getCurPos();
+   if (n_leftover_bits >= 0 && n_leftover_bits <= 8)
+   {
+      // note - an unusual problem regarding setCurPos() results when there 
+      // are no leftover bytes. Adding a buffer byte in this case avoids the problem.
+      if (n_leftover_bits == 0)
+         n_leftover_bits = 8;
+      U8 bzero = 0;
+      bstream->writeBits(n_leftover_bits, &bzero);
+   }
+
+   // this is where we actually save the file
+   const char* filename = (on_server) ? server_cache_filename : client_cache_filename;
+   if (filename && filename[0] != '\0')
+   {
+      FileStream* f_stream;
+      if((f_stream = FileStream::createAndOpen(filename, Torque::FS::File::Write )) == NULL)
+      {
+         Con::printf("Failed to open file '%s'.", filename);
+         return;
+      }
+
+      U32 save_sz = bstream->getPosition();
+
+      if (!on_server)
+      {
+         f_stream->write((U32)CLIENT_CACHE_VERSION_CODE);
+         f_stream->write(save_sz);
+         f_stream->write(server_cache_CRC);
+         f_stream->write((U32)CLIENT_CACHE_VERSION_CODE);
+      }
+
+      f_stream->write(save_sz, bstream->getBuffer());
+
+      // zero out any leftover bytes short of a 4-byte multiple
+      while ((save_sz % 4) != 0)
+      {
+         f_stream->write((U8)0);
+         save_sz++;
+      }
+
+      delete f_stream;
+   }
+
+   if (!on_server)
+      client_db_stream->clear();
+}
+
+static bool afx_saved_db_cache = false;
+static U32 afx_saved_db_cache_CRC = 0xffffffff;
+
+void GameConnection::resetDatablockCache()
+{
+   afx_saved_db_cache = false;
+   afx_saved_db_cache_CRC = 0xffffffff;
+}
+
+ConsoleFunction(resetDatablockCache, void, 1, 1, "resetDatablockCache()")
+{
+   GameConnection::resetDatablockCache();
+}
+
+ConsoleFunction(isDatablockCacheSaved, bool, 1, 1, "resetDatablockCache()")
+{
+   return afx_saved_db_cache;
+}
+
+ConsoleFunction(getDatablockCacheCRC, S32, 1, 1, "getDatablockCacheCRC()")
+{
+   return (S32)afx_saved_db_cache_CRC;
+}
+
+ConsoleFunction(extractDatablockCacheCRC, S32, 2, 2, "extractDatablockCacheCRC(filename)")
+{
+   FileStream f_stream;
+   const char* fileName = argv[1];
+   if(!f_stream.open(fileName, Torque::FS::File::Read))
+   {
+      Con::errorf("Failed to open file '%s'.", fileName);
+      return -1;
+   }
+
+   U32 stream_sz = f_stream.getStreamSize();
+   if (stream_sz < 4*32)
+   {
+      Con::errorf("File '%s' is not a valid datablock cache.", fileName);
+      f_stream.close();
+      return -1;
+   }
+
+   U32 pre_code; f_stream.read(&pre_code);
+   U32 save_sz; f_stream.read(&save_sz);
+   U32 crc_code; f_stream.read(&crc_code);
+   U32 post_code; f_stream.read(&post_code);
+
+   f_stream.close();
+
+   if (pre_code != post_code)
+   {
+      Con::errorf("File '%s' is not a valid datablock cache.", fileName);
+      return -1;
+   }
+
+   if (pre_code != (U32)CLIENT_CACHE_VERSION_CODE)
+   {
+      Con::errorf("Version of datablock cache file '%s' does not match version of running software.", fileName);
+      return -1;
+   }
+
+   return (S32)crc_code;
+}
+
+ConsoleFunction(setDatablockCacheCRC, void, 2, 2, "setDatablockCacheCRC(crc)")
+{
+   GameConnection *conn = GameConnection::getConnectionToServer();
+   if(!conn)
+      return;
+
+   U32 crc_u = (U32)dAtoi(argv[1]);
+   conn->setServerCacheCRC(crc_u);
+}
+
+ConsoleMethod( GameConnection, saveDatablockCache, void, 2, 2, "saveDatablockCache()")
+{
+   if (GameConnection::serverCacheEnabled() && !afx_saved_db_cache)
+   {
+      // Save the datablocks to a cache file. An argument
+      // of true indicates that this is a server save.
+      object->saveDatablockCache(true);
+      afx_saved_db_cache = true;
+      afx_saved_db_cache_CRC = 0xffffffff;
+
+      static char filename_buffer[1024];
+      String filename(Torque::Path::CleanSeparators(object->serverCacheFilename()));
+      Con::expandScriptFilename(filename_buffer, sizeof(filename_buffer), filename.c_str());
+      Torque::Path givenPath(Torque::Path::CompressPath(filename_buffer));
+      Torque::FS::FileNodeRef fileRef = Torque::FS::GetFileNode(givenPath);
+      if ( fileRef == NULL )
+         Con::errorf("saveDatablockCache() failed to get CRC for file '%s'.", filename.c_str());
+      else
+         afx_saved_db_cache_CRC = (S32)fileRef->getChecksum();
+   }
+}
+
+ConsoleMethod( GameConnection, loadDatablockCache, void, 2, 2, "loadDatablockCache()")
+{
+   if (GameConnection::clientCacheEnabled())
+   {
+      object->loadDatablockCache();
+   }
+}
+
+ConsoleMethod( GameConnection, loadDatablockCache_Begin, bool, 2, 2, "loadDatablockCache_Begin()")
+{
+   if (GameConnection::clientCacheEnabled())
+   {
+      return object->loadDatablockCache_Begin();
+   }
+
+   return false;
+}
+
+ConsoleMethod( GameConnection, loadDatablockCache_Continue, bool, 2, 2, "loadDatablockCache_Continue()")
+{
+   if (GameConnection::clientCacheEnabled())
+   {
+      return object->loadDatablockCache_Continue();
+   }
+
+   return false;
+}
+
+static char*        afx_db_load_buf = 0;
+static U32          afx_db_load_buf_sz = 0;
+static BitStream*   afx_db_load_bstream = 0;
+
+void GameConnection::loadDatablockCache()
+{
+   if (!loadDatablockCache_Begin())
+      return;
+
+   while (loadDatablockCache_Continue())
+      ;
+}
+
+bool GameConnection::loadDatablockCache_Begin()
+{
+   if (!client_cache_filename || client_cache_filename[0] == '\0')
+   {
+      Con::errorf("No filename was specified for the client datablock cache.");
+      return false;
+   }
+
+   // open cache file
+   FileStream f_stream;
+   if(!f_stream.open(client_cache_filename, Torque::FS::File::Read))
+   {
+      Con::errorf("Failed to open file '%s'.", client_cache_filename);
+      return false;
+   }
+
+   // get file size
+   U32 stream_sz = f_stream.getStreamSize();
+   if (stream_sz <= 4*4)
+   {
+      Con::errorf("File '%s' is too small to be a valid datablock cache.", client_cache_filename);
+      f_stream.close();
+      return false;
+   }
+
+   // load header data
+   U32 pre_code; f_stream.read(&pre_code);
+   U32 save_sz; f_stream.read(&save_sz);
+   U32 crc_code; f_stream.read(&crc_code);
+   U32 post_code; f_stream.read(&post_code);
+
+   // validate header info 
+   if (pre_code != post_code)
+   {
+      Con::errorf("File '%s' is not a valid datablock cache.", client_cache_filename);
+      f_stream.close();
+      return false;
+   }
+   if (pre_code != (U32)CLIENT_CACHE_VERSION_CODE)
+   {
+      Con::errorf("Version of datablock cache file '%s' does not match version of running software.", client_cache_filename);
+      f_stream.close();
+      return false;
+   }
+
+   // allocated the in-memory buffer
+   afx_db_load_buf_sz = stream_sz - (4*4);
+   afx_db_load_buf = new char[afx_db_load_buf_sz];
+
+   // load data from file into memory
+   if (!f_stream.read(stream_sz, afx_db_load_buf))
+   {
+      Con::errorf("Failed to read data from file '%s'.", client_cache_filename);
+      f_stream.close();
+      delete [] afx_db_load_buf;
+      afx_db_load_buf = 0;
+      afx_db_load_buf_sz = 0;
+      return false;
+   }
+
+   // close file
+   f_stream.close();
+
+   // At this point we have the whole cache in memory
+
+   // create a bitstream from the in-memory buffer
+   afx_db_load_bstream = new BitStream(afx_db_load_buf, afx_db_load_buf_sz);
+
+   return true;
+}
+
+bool GameConnection::loadDatablockCache_Continue()
+{
+   if (!afx_db_load_bstream)
+      return false;
+
+   // prevent repacking of datablocks during load
+   BitStream* save_client_db_stream = client_db_stream;
+   client_db_stream = 0;
+
+   bool all_finished = false;
+
+   // loop through at most 16 datablocks
+   BitStream *bstream = afx_db_load_bstream;
+   for (S32 i = 0; i < 16; i++)
+   {
+      S32 save_pos = bstream->getCurPos();
+      if (!bstream->readFlag())
+      {
+         all_finished = true;
+         break;
+      }
+      bstream->setCurPos(save_pos);
+      SimDataBlockEvent evt;
+      evt.unpack(this, bstream);
+      evt.process(this);
+   }
+
+   client_db_stream = save_client_db_stream;
+
+   if (all_finished)
+   {
+      delete afx_db_load_bstream;
+      afx_db_load_bstream = 0;
+      delete [] afx_db_load_buf;
+      afx_db_load_buf = 0;
+      afx_db_load_buf_sz = 0;
+      return false;
+   }
+
+   return true;
+}
+
+#endif 

+ 68 - 0
Engine/source/T3D/gameBase/gameConnection.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 _GAMECONNECTION_H_
 #ifndef _GAMECONNECTION_H_
 #define _GAMECONNECTION_H_
 #define _GAMECONNECTION_H_
 
 
@@ -55,6 +60,14 @@ class MoveList;
 struct Move;
 struct Move;
 struct AuthInfo;
 struct AuthInfo;
 
 
+// To disable datablock caching, remove or comment out the AFX_CAP_DATABLOCK_CACHE define below.
+// Also, at a minimum, the following script preferences should be set to false:
+//   $pref::Client::EnableDatablockCache = false; (in arcane.fx/client/defaults.cs)
+//   $Pref::Server::EnableDatablockCache = false; (in arcane.fx/server/defaults.cs)
+// Alternatively, all script code marked with "DATABLOCK CACHE CODE" can be removed or
+// commented out.
+//
+#define AFX_CAP_DATABLOCK_CACHE
 const F32 MinCameraFov              = 1.f;      ///< min camera FOV
 const F32 MinCameraFov              = 1.f;      ///< min camera FOV
 const F32 MaxCameraFov              = 179.f;    ///< max camera FOV
 const F32 MaxCameraFov              = 179.f;    ///< max camera FOV
 
 
@@ -372,6 +385,61 @@ protected:
    DECLARE_CALLBACK( void, setLagIcon, (bool state) );
    DECLARE_CALLBACK( void, setLagIcon, (bool state) );
    DECLARE_CALLBACK( void, onDataBlocksDone, (U32 sequence) );
    DECLARE_CALLBACK( void, onDataBlocksDone, (U32 sequence) );
    DECLARE_CALLBACK( void, onFlash, (bool state) );
    DECLARE_CALLBACK( void, onFlash, (bool state) );
+   
+   // GameConnection is modified to keep track of object selections which are used in
+   // spell targeting. This code stores the current object selection as well as the
+   // current rollover object beneath the cursor. The rollover object is treated as a
+   // pending object selection and actual object selection is usually made by promoting
+   // the rollover object to the current object selection.
+private:   
+   SimObjectPtr<SceneObject> mRolloverObj;  
+   SimObjectPtr<SceneObject> mPreSelectedObj;  
+   SimObjectPtr<SceneObject> mSelectedObj;  
+   bool          mChangedSelectedObj;
+   U32           mPreSelectTimestamp;
+protected:
+   virtual void  onDeleteNotify(SimObject*);
+public:   
+   void          setRolloverObj(SceneObject*);   
+   SceneObject*  getRolloverObj() { return  mRolloverObj; }   
+   void          setSelectedObj(SceneObject*, bool propagate_to_client=false);   
+   SceneObject*  getSelectedObj() { return  mSelectedObj; }  
+   void          setPreSelectedObjFromRollover();
+   void          clearPreSelectedObj();
+   void          setSelectedObjFromPreSelected();
+   // Flag is added to indicate when a client is fully connected or "zoned-in". 
+   // This information determines when AFX will startup active effects on a newly
+   // added client. 
+private:
+   bool          zoned_in;
+public:
+   bool          isZonedIn() const { return zoned_in; }
+   void          setZonedIn() { zoned_in = true; }
+   
+#ifdef AFX_CAP_DATABLOCK_CACHE
+private:
+   static StringTableEntry  server_cache_filename;
+   static StringTableEntry  client_cache_filename;
+   static bool   server_cache_on;
+   static bool   client_cache_on;
+   BitStream*    client_db_stream;
+   U32           server_cache_CRC;
+public:
+   void          repackClientDatablock(BitStream*, S32 start_pos);
+   void          saveDatablockCache(bool on_server);
+   void          loadDatablockCache();
+   bool          loadDatablockCache_Begin();
+   bool          loadDatablockCache_Continue();
+   void          tempDisableStringBuffering(BitStream* bs) const;
+   void          restoreStringBuffering(BitStream* bs) const;
+   void          setServerCacheCRC(U32 crc) { server_cache_CRC = crc; }
+
+   static void   resetDatablockCache();
+   static bool   serverCacheEnabled() { return server_cache_on; }
+   static bool   clientCacheEnabled() { return client_cache_on; }
+   static const char* serverCacheFilename() { return server_cache_filename; }
+   static const char* clientCacheFilename() { return client_cache_filename; }
+#endif
 };
 };
 
 
 #endif
 #endif

+ 21 - 0
Engine/source/T3D/gameBase/gameConnectionEvents.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 "core/dnet.h"
 #include "core/dnet.h"
 #include "core/stream/bitStream.h"
 #include "core/stream/bitStream.h"
@@ -136,6 +141,9 @@ void SimDataBlockEvent::notifyDelivered(NetConnection *conn, bool )
 
 
 void SimDataBlockEvent::pack(NetConnection *conn, BitStream *bstream)
 void SimDataBlockEvent::pack(NetConnection *conn, BitStream *bstream)
 {
 {
+#ifdef AFX_CAP_DATABLOCK_CACHE 
+   ((GameConnection *)conn)->tempDisableStringBuffering(bstream);
+#endif 
    SimDataBlock* obj;
    SimDataBlock* obj;
    Sim::findObject(id,obj);
    Sim::findObject(id,obj);
    GameConnection *gc = (GameConnection *) conn;
    GameConnection *gc = (GameConnection *) conn;
@@ -157,10 +165,18 @@ void SimDataBlockEvent::pack(NetConnection *conn, BitStream *bstream)
       bstream->writeInt(classId ^ DebugChecksum, 32);
       bstream->writeInt(classId ^ DebugChecksum, 32);
 #endif
 #endif
    }
    }
+#ifdef AFX_CAP_DATABLOCK_CACHE 
+   ((GameConnection *)conn)->restoreStringBuffering(bstream);
+#endif 
 }
 }
 
 
 void SimDataBlockEvent::unpack(NetConnection *cptr, BitStream *bstream)
 void SimDataBlockEvent::unpack(NetConnection *cptr, BitStream *bstream)
 {
 {
+#ifdef AFX_CAP_DATABLOCK_CACHE 
+   // stash the stream position prior to unpacking
+   S32 start_pos = bstream->getCurPos();
+   ((GameConnection *)cptr)->tempDisableStringBuffering(bstream);
+#endif
    if(bstream->readFlag())
    if(bstream->readFlag())
    {
    {
       mProcess = true;
       mProcess = true;
@@ -215,6 +231,11 @@ void SimDataBlockEvent::unpack(NetConnection *cptr, BitStream *bstream)
 #endif
 #endif
 
 
    }
    }
+#ifdef AFX_CAP_DATABLOCK_CACHE
+   // rewind to stream position and then process raw bytes for caching
+   ((GameConnection *)cptr)->repackClientDatablock(bstream, start_pos);
+   ((GameConnection *)cptr)->restoreStringBuffering(bstream);
+#endif
 }
 }
 
 
 void SimDataBlockEvent::write(NetConnection *cptr, BitStream *bstream)
 void SimDataBlockEvent::write(NetConnection *cptr, BitStream *bstream)

+ 20 - 0
Engine/source/T3D/gameBase/processList.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/gameBase/processList.h"
 #include "T3D/gameBase/processList.h"
 
 
@@ -284,5 +289,20 @@ void ProcessList::advanceObjects()
    PROFILE_END();
    PROFILE_END();
 }
 }
 
 
+ProcessObject* ProcessList::findNearestToEnd(Vector<ProcessObject*>& objs) const
+{
+   if (objs.empty())
+      return 0;
+
+   for (ProcessObject* obj = mHead.mProcessLink.prev; obj != &mHead; obj = obj->mProcessLink.prev)
+   {
+      for (S32 i = 0; i < objs.size(); i++)
+      {
+         if (obj == objs[i])
+            return obj;
+      }
+   }
 
 
+   return 0;
+}
 
 

+ 8 - 0
Engine/source/T3D/gameBase/processList.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 _PROCESSLIST_H_
 #ifndef _PROCESSLIST_H_
 #define _PROCESSLIST_H_
 #define _PROCESSLIST_H_
 
 
@@ -188,6 +193,9 @@ protected:
 
 
    PreTickSignal mPreTick;
    PreTickSignal mPreTick;
    PostTickSignal mPostTick;
    PostTickSignal mPostTick;
+   // JTF: still needed?
+public:
+   ProcessObject* findNearestToEnd(Vector<ProcessObject*>& objs) const;
 };
 };
 
 
 #endif // _PROCESSLIST_H_
 #endif // _PROCESSLIST_H_

+ 8 - 0
Engine/source/T3D/groundPlane.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/groundPlane.h"
 #include "T3D/groundPlane.h"
 
 
@@ -40,6 +45,7 @@
 #include "T3D/physics/physicsBody.h"
 #include "T3D/physics/physicsBody.h"
 #include "T3D/physics/physicsCollision.h"
 #include "T3D/physics/physicsCollision.h"
 
 
+#include "afx/ce/afxZodiacMgr.h"
 
 
 /// Minimum square size allowed.  This is a cheap way to limit the amount
 /// Minimum square size allowed.  This is a cheap way to limit the amount
 /// of geometry possibly generated by the GroundPlane (vertex buffers have a
 /// of geometry possibly generated by the GroundPlane (vertex buffers have a
@@ -77,6 +83,7 @@ GroundPlane::GroundPlane()
    mNetFlags.set( Ghostable | ScopeAlways );
    mNetFlags.set( Ghostable | ScopeAlways );
 
 
    mConvexList = new Convex;
    mConvexList = new Convex;
+   mTypeMask |= TerrainLikeObjectType;
 }
 }
 
 
 GroundPlane::~GroundPlane()
 GroundPlane::~GroundPlane()
@@ -356,6 +363,7 @@ void GroundPlane::prepRenderImage( SceneRenderState* state )
    if( mVertexBuffer.isNull() )
    if( mVertexBuffer.isNull() )
       return;
       return;
 
 
+   afxZodiacMgr::renderGroundPlaneZodiacs(state, this);
    // Add a render instance.
    // Add a render instance.
 
 
    RenderPassManager*   pass  = state->getRenderPass();
    RenderPassManager*   pass  = state->getRenderPass();

+ 7 - 1
Engine/source/T3D/lightBase.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/lightBase.h"
 #include "T3D/lightBase.h"
 
 
@@ -73,6 +78,7 @@ LightBase::LightBase()
    mLight = LightManager::createLightInfo();
    mLight = LightManager::createLightInfo();
 
 
    mFlareState.clear();
    mFlareState.clear();
+   mLocalRenderViz = false;
 }
 }
 
 
 LightBase::~LightBase()
 LightBase::~LightBase()
@@ -206,7 +212,7 @@ void LightBase::prepRenderImage( SceneRenderState *state )
 
 
    // If the light is selected or light visualization
    // If the light is selected or light visualization
    // is enabled then register the callback.
    // is enabled then register the callback.
-   if ( smRenderViz || isSelectedInEditor )
+   if ( mLocalRenderViz || smRenderViz || isSelectedInEditor )
    {
    {
       ObjectRenderInst *ri = state->getRenderPass()->allocInst<ObjectRenderInst>();
       ObjectRenderInst *ri = state->getRenderPass()->allocInst<ObjectRenderInst>();
       ri->renderDelegate.bind( this, &LightBase::_onRenderViz );
       ri->renderDelegate.bind( this, &LightBase::_onRenderViz );

+ 7 - 0
Engine/source/T3D/lightBase.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 _LIGHTBASE_H_
 #ifndef _LIGHTBASE_H_
 #define _LIGHTBASE_H_
 #define _LIGHTBASE_H_
 
 
@@ -132,6 +137,8 @@ public:
    virtual void pauseAnimation( void );
    virtual void pauseAnimation( void );
    virtual void playAnimation( void );
    virtual void playAnimation( void );
    virtual void playAnimation( LightAnimData *animData );
    virtual void playAnimation( LightAnimData *animData );
+protected:
+   bool mLocalRenderViz;
 };
 };
 
 
 #endif // _LIGHTBASE_H_
 #endif // _LIGHTBASE_H_

+ 13 - 0
Engine/source/T3D/objectTypes.h

@@ -20,11 +20,19 @@
 // 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 _OBJECTTYPES_H_
 #ifndef _OBJECTTYPES_H_
 #define _OBJECTTYPES_H_
 #define _OBJECTTYPES_H_
 
 
 #include "platform/types.h"
 #include "platform/types.h"
 
 
+// Uncomment the AFX_CAP_AFXMODEL_TYPE define below to enable a type flag
+// for afxModel objects.
+//#define AFX_CAP_AFXMODEL_TYPE
 /// Types used for SceneObject type masks (SceneObject::mTypeMask)
 /// Types used for SceneObject type masks (SceneObject::mTypeMask)
 ///
 ///
 /// @note If a new object type is added, don't forget to add it to
 /// @note If a new object type is added, don't forget to add it to
@@ -149,6 +157,11 @@ enum SceneObjectTypes
 
 
    EntityObjectType = BIT(23),
    EntityObjectType = BIT(23),
    /// @}
    /// @}
+   InteriorLikeObjectType =  BIT(24),
+   TerrainLikeObjectType = BIT(25),
+#if defined(AFX_CAP_AFXMODEL_TYPE) 
+   afxModelObjectType = BIT(26)
+#endif 
 };
 };
 
 
 enum SceneObjectTypeMasks : U32
 enum SceneObjectTypeMasks : U32

+ 200 - 24
Engine/source/T3D/physicalZone.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 "T3D/physicalZone.h"
 #include "T3D/physicalZone.h"
 #include "core/stream/bitStream.h"
 #include "core/stream/bitStream.h"
 #include "collision/boxConvex.h"
 #include "collision/boxConvex.h"
@@ -33,6 +38,8 @@
 #include "gfx/gfxDrawUtil.h"
 #include "gfx/gfxDrawUtil.h"
 #include "console/engineAPI.h"
 #include "console/engineAPI.h"
 
 
+//#include "console/engineTypes.h"
+#include "sim/netConnection.h"
 IMPLEMENT_CO_NETOBJECT_V1(PhysicalZone);
 IMPLEMENT_CO_NETOBJECT_V1(PhysicalZone);
 
 
 ConsoleDocClass( PhysicalZone,
 ConsoleDocClass( PhysicalZone,
@@ -103,6 +110,10 @@ PhysicalZone::PhysicalZone()
 
 
    mConvexList = new Convex;
    mConvexList = new Convex;
    mActive = true;
    mActive = true;
+   force_type = VECTOR;
+   force_mag = 0.0f;
+   orient_force = false;
+   fade_amt = 1.0f;
 }
 }
 
 
 PhysicalZone::~PhysicalZone()
 PhysicalZone::~PhysicalZone()
@@ -111,6 +122,16 @@ PhysicalZone::~PhysicalZone()
    mConvexList = NULL;
    mConvexList = NULL;
 }
 }
 
 
+
+ImplementEnumType( PhysicalZone_ForceType, "Possible physical zone force types.\n" "@ingroup PhysicalZone\n\n" )
+   { PhysicalZone::VECTOR,          "vector",        "..." },
+   { PhysicalZone::SPHERICAL,       "spherical",     "..." },
+   { PhysicalZone::CYLINDRICAL,     "cylindrical",   "..." },
+   // aliases
+   { PhysicalZone::SPHERICAL,       "sphere",        "..." },
+   { PhysicalZone::CYLINDRICAL,     "cylinder",      "..." },
+EndImplementEnumType;
+
 //--------------------------------------------------------------------------
 //--------------------------------------------------------------------------
 void PhysicalZone::consoleInit()
 void PhysicalZone::consoleInit()
 {
 {
@@ -129,6 +150,10 @@ void PhysicalZone::initPersistFields()
       "point followed by three vectors representing the edges extending from the corner." );
       "point followed by three vectors representing the edges extending from the corner." );
    endGroup("Misc");
    endGroup("Misc");
 
 
+   addGroup("AFX");
+   addField("forceType", TYPEID<PhysicalZone::ForceType>(), Offset(force_type, PhysicalZone));
+   addField("orientForce", TypeBool, Offset(orient_force, PhysicalZone));
+   endGroup("AFX");
    Parent::initPersistFields();
    Parent::initPersistFields();
 }
 }
 
 
@@ -158,6 +183,19 @@ bool PhysicalZone::onAdd()
    Polyhedron temp = mPolyhedron;
    Polyhedron temp = mPolyhedron;
    setPolyhedron(temp);
    setPolyhedron(temp);
 
 
+   switch (force_type)
+   {
+   case SPHERICAL:
+      force_mag = mAppliedForce.magnitudeSafe();
+      break;
+   case CYLINDRICAL:
+      {
+         Point3F force_vec = mAppliedForce;
+         force_vec.z = 0.0;
+         force_mag = force_vec.magnitudeSafe();
+      }
+      break;
+   }
    addToScene();
    addToScene();
 
 
    return true;
    return true;
@@ -191,7 +229,7 @@ void PhysicalZone::setTransform(const MatrixF & mat)
       mClippedList.setBaseTransform(base);
       mClippedList.setBaseTransform(base);
 
 
    if (isServerObject())
    if (isServerObject())
-      setMaskBits(InitialUpdateMask);
+      setMaskBits(MoveMask);
 }
 }
 
 
 
 
@@ -242,12 +280,8 @@ U32 PhysicalZone::packUpdate(NetConnection* con, U32 mask, BitStream* stream)
    U32 i;
    U32 i;
    U32 retMask = Parent::packUpdate(con, mask, stream);
    U32 retMask = Parent::packUpdate(con, mask, stream);
 
 
-   if (stream->writeFlag((mask & InitialUpdateMask) != 0)) {
-      // Note that we don't really care about efficiency here, since this is an
-      //  edit-only ghost...
-      mathWrite(*stream, mObjToWorld);
-      mathWrite(*stream, mObjScale);
-
+   if (stream->writeFlag(mask & PolyhedronMask)) 
+   {
       // Write the polyhedron
       // Write the polyhedron
       stream->write(mPolyhedron.pointList.size());
       stream->write(mPolyhedron.pointList.size());
       for (i = 0; i < mPolyhedron.pointList.size(); i++)
       for (i = 0; i < mPolyhedron.pointList.size(); i++)
@@ -266,32 +300,44 @@ U32 PhysicalZone::packUpdate(NetConnection* con, U32 mask, BitStream* stream)
          stream->write(rEdge.vertex[0]);
          stream->write(rEdge.vertex[0]);
          stream->write(rEdge.vertex[1]);
          stream->write(rEdge.vertex[1]);
       }
       }
+   }
 
 
+   if (stream->writeFlag(mask & MoveMask))
+   {
+      stream->writeAffineTransform(mObjToWorld);
+      mathWrite(*stream, mObjScale);
+   }
+
+   if (stream->writeFlag(mask & SettingsMask))
+   {
       stream->write(mVelocityMod);
       stream->write(mVelocityMod);
       stream->write(mGravityMod);
       stream->write(mGravityMod);
       mathWrite(*stream, mAppliedForce);
       mathWrite(*stream, mAppliedForce);
-      stream->writeFlag(mActive);
-   } else {
-      stream->writeFlag(mActive);
+      stream->writeInt(force_type, FORCE_TYPE_BITS);
+      stream->writeFlag(orient_force);
+   }
+
+   if (stream->writeFlag(mask & FadeMask))
+   {
+      U8 fade_byte = (U8)(fade_amt*255.0f);
+      stream->write(fade_byte);
    }
    }
 
 
-   return retMask;
+   stream->writeFlag(mActive);
+
+     return retMask;
 }
 }
 
 
 void PhysicalZone::unpackUpdate(NetConnection* con, BitStream* stream)
 void PhysicalZone::unpackUpdate(NetConnection* con, BitStream* stream)
 {
 {
    Parent::unpackUpdate(con, stream);
    Parent::unpackUpdate(con, stream);
 
 
-   if (stream->readFlag()) {
+   bool new_ph = false;
+   if (stream->readFlag()) // PolyhedronMask
+   {
       U32 i, size;
       U32 i, size;
-      MatrixF temp;
-      Point3F tempScale;
       Polyhedron tempPH;
       Polyhedron tempPH;
 
 
-      // Transform
-      mathRead(*stream, &temp);
-      mathRead(*stream, &tempScale);
-
       // Read the polyhedron
       // Read the polyhedron
       stream->read(&size);
       stream->read(&size);
       tempPH.pointList.setSize(size);
       tempPH.pointList.setSize(size);
@@ -314,17 +360,46 @@ void PhysicalZone::unpackUpdate(NetConnection* con, BitStream* stream)
          stream->read(&rEdge.vertex[1]);
          stream->read(&rEdge.vertex[1]);
       }
       }
 
 
+      setPolyhedron(tempPH);
+      new_ph = true;
+   }
+
+   if (stream->readFlag()) // MoveMask
+   {
+      MatrixF temp;
+      stream->readAffineTransform(&temp);
+
+      Point3F tempScale;
+      mathRead(*stream, &tempScale);
+
+      //if (!new_ph)
+      //{
+      //  Polyhedron rPolyhedron = mPolyhedron;
+      //  setPolyhedron(rPolyhedron);
+      //}
+      setScale(tempScale);
+      setTransform(temp);
+   }
+
+   if (stream->readFlag()) //SettingsMask
+   {
       stream->read(&mVelocityMod);
       stream->read(&mVelocityMod);
       stream->read(&mGravityMod);
       stream->read(&mGravityMod);
       mathRead(*stream, &mAppliedForce);
       mathRead(*stream, &mAppliedForce);
+      force_type = stream->readInt(FORCE_TYPE_BITS); // AFX
+      orient_force = stream->readFlag(); // AFX
+   }
 
 
-      setPolyhedron(tempPH);
-      setScale(tempScale);
-      setTransform(temp);
-      mActive = stream->readFlag();
-   } else {
-      mActive = stream->readFlag();
+   if (stream->readFlag()) //FadeMask
+   {
+      U8 fade_byte;
+      stream->read(&fade_byte);
+      fade_amt = ((F32)fade_byte)/255.0f;
    }
    }
+   else
+      fade_amt = 1.0f;
+
+   mActive = stream->readFlag();
 }
 }
 
 
 
 
@@ -443,3 +518,104 @@ void PhysicalZone::deactivate()
    mActive = false;
    mActive = false;
 }
 }
 
 
+void PhysicalZone::onStaticModified(const char* slotName, const char*newValue)
+{
+   if (dStricmp(slotName, "appliedForce") == 0 || dStricmp(slotName, "forceType") == 0)
+   {
+      switch (force_type)
+      {
+      case SPHERICAL:
+         force_mag = mAppliedForce.magnitudeSafe();
+         break;
+      case CYLINDRICAL:
+         {
+            Point3F force_vec = mAppliedForce;
+            force_vec.z = 0.0;
+            force_mag = force_vec.magnitudeSafe();
+         }
+         break;
+      }
+   }
+}
+
+const Point3F& PhysicalZone::getForce(const Point3F* center) const 
+{ 
+   static Point3F force_vec;
+
+   if (force_type == VECTOR)
+   {
+      if (orient_force)
+      {
+         getTransform().mulV(mAppliedForce, &force_vec);
+         force_vec *= fade_amt;
+         return force_vec; 
+      }
+      force_vec = mAppliedForce;
+      force_vec *= fade_amt;
+      return force_vec;
+   }
+
+   if (!center)
+   {
+      force_vec.zero();
+      return force_vec; 
+   }
+
+   if (force_type == SPHERICAL)
+   {
+      force_vec = *center - getPosition();
+      force_vec.normalizeSafe();
+      force_vec *= force_mag*fade_amt;
+      return force_vec;
+   }
+
+   if (orient_force)
+   {
+      force_vec = *center - getPosition();
+      getWorldTransform().mulV(force_vec);
+      force_vec.z = 0.0f;
+      force_vec.normalizeSafe();
+      force_vec *= force_mag;
+      force_vec.z = mAppliedForce.z;
+      getTransform().mulV(force_vec);
+      force_vec *= fade_amt;
+      return force_vec;
+   }
+
+   force_vec = *center - getPosition();
+   force_vec.z = 0.0f;
+   force_vec.normalizeSafe();
+   force_vec *= force_mag;
+   force_vec *= fade_amt;
+   return force_vec;
+}
+
+bool PhysicalZone::isExcludedObject(SceneObject* obj) const
+{
+   for (S32 i = 0; i < excluded_objects.size(); i++)
+      if (excluded_objects[i] == obj)
+         return true;
+
+   return false;
+}
+
+void PhysicalZone::registerExcludedObject(SceneObject* obj)
+{
+   if (isExcludedObject(obj))
+      return;
+
+   excluded_objects.push_back(obj);
+   setMaskBits(FadeMask);
+}
+
+void PhysicalZone::unregisterExcludedObject(SceneObject* obj)
+{
+   for (S32 i = 0; i < excluded_objects.size(); i++)
+      if (excluded_objects[i] == obj)
+      {
+         excluded_objects.erase(i);
+         setMaskBits(FadeMask);
+         return;
+      }
+}
+

+ 34 - 3
Engine/source/T3D/physicalZone.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 _H_PHYSICALZONE
 #ifndef _H_PHYSICALZONE
 #define _H_PHYSICALZONE
 #define _H_PHYSICALZONE
 
 
@@ -40,9 +45,14 @@ class PhysicalZone : public SceneObject
 {
 {
    typedef SceneObject Parent;
    typedef SceneObject Parent;
 
 
-   enum UpdateMasks {      
+   enum UpdateMasks {
       ActiveMask        = Parent::NextFreeMask << 0,
       ActiveMask        = Parent::NextFreeMask << 0,
-      NextFreeMask      = Parent::NextFreeMask << 1
+      SettingsMask      = Parent::NextFreeMask << 1,
+      FadeMask          = Parent::NextFreeMask << 2,
+      PolyhedronMask    = Parent::NextFreeMask << 3,
+      MoveMask          = Parent::NextFreeMask << 4,
+      ExclusionMask     = Parent::NextFreeMask << 5,
+      NextFreeMask      = Parent::NextFreeMask << 6
    };
    };
 
 
   protected:
   protected:
@@ -83,7 +93,10 @@ class PhysicalZone : public SceneObject
 
 
    inline F32 getVelocityMod() const      { return mVelocityMod; }
    inline F32 getVelocityMod() const      { return mVelocityMod; }
    inline F32 getGravityMod()  const      { return mGravityMod;  }
    inline F32 getGravityMod()  const      { return mGravityMod;  }
-   inline const Point3F& getForce() const { return mAppliedForce; }
+   // the scene object is now passed in to getForce() where
+   // it is needed to calculate the applied force when the
+   // force is radial.
+   const Point3F& getForce(const Point3F* center=0) const;
 
 
    void setPolyhedron(const Polyhedron&);
    void setPolyhedron(const Polyhedron&);
    bool testObject(SceneObject*);
    bool testObject(SceneObject*);
@@ -96,7 +109,25 @@ class PhysicalZone : public SceneObject
    void deactivate();
    void deactivate();
    inline bool isActive() const { return mActive; }
    inline bool isActive() const { return mActive; }
 
 
+protected:
+   friend class afxPhysicalZoneData;
+   friend class afxEA_PhysicalZone;
+   Vector<SceneObject*>  excluded_objects;
+   S32    force_type;
+   F32    force_mag;
+   bool   orient_force;
+   F32    fade_amt;
+   void   setFadeAmount(F32 amt) { fade_amt = amt; if (fade_amt < 1.0f) setMaskBits(FadeMask); }
+public:
+   enum ForceType { VECTOR, SPHERICAL, CYLINDRICAL };
+   enum { FORCE_TYPE_BITS = 2 };
+   virtual void onStaticModified(const char* slotName, const char*newValue = NULL);
+   bool   isExcludedObject(SceneObject*) const;
+   void   registerExcludedObject(SceneObject*);
+   void   unregisterExcludedObject(SceneObject*);
 };
 };
 
 
+typedef PhysicalZone::ForceType PhysicalZone_ForceType;
+DefineEnumType( PhysicalZone_ForceType );
 #endif // _H_PHYSICALZONE
 #endif // _H_PHYSICALZONE
 
 

+ 476 - 13
Engine/source/T3D/player.cpp

@@ -20,6 +20,10 @@
 // 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/player.h"
 #include "T3D/player.h"
 
 
@@ -1656,6 +1660,8 @@ Player::Player()
    mLastAbsoluteYaw = 0.0f;
    mLastAbsoluteYaw = 0.0f;
    mLastAbsolutePitch = 0.0f;
    mLastAbsolutePitch = 0.0f;
    mLastAbsoluteRoll = 0.0f;
    mLastAbsoluteRoll = 0.0f;
+   
+   afx_init();
 }
 }
 
 
 Player::~Player()
 Player::~Player()
@@ -2070,6 +2076,32 @@ void Player::processTick(const Move* move)
    }
    }
 
 
    Parent::processTick(move);
    Parent::processTick(move);
+   // Check for state changes in the standard move triggers and
+   // set bits for any triggers that switched on this tick in
+   // the fx_s_triggers mask. Flag any changes to be packed to
+   // clients.
+   if (isServerObject())
+   {
+      fx_s_triggers = 0;
+      if (move)
+      {
+         U8 on_bits = 0;
+         for (S32 i = 0; i < MaxTriggerKeys; i++)
+            if (move->trigger[i])
+               on_bits |= BIT(i);
+
+         if (on_bits != move_trigger_states)
+         {
+            U8 switched_on_bits = (on_bits & ~move_trigger_states);
+            if (switched_on_bits)
+            {
+               fx_s_triggers |= (U32)switched_on_bits;
+               setMaskBits(TriggerMask);
+            }
+            move_trigger_states = on_bits;
+         }
+      }
+   }
    // Warp to catch up to server
    // Warp to catch up to server
    if (delta.warpTicks > 0) {
    if (delta.warpTicks > 0) {
       delta.warpTicks--;
       delta.warpTicks--;
@@ -2085,7 +2117,10 @@ void Player::processTick(const Move* move)
       else if (delta.rot.z > M_PI_F)
       else if (delta.rot.z > M_PI_F)
          delta.rot.z -= M_2PI_F;
          delta.rot.z -= M_2PI_F;
 
 
-      setPosition(delta.pos,delta.rot);
+      if (!ignore_updates)
+      {
+         setPosition(delta.pos,delta.rot);
+      }
       updateDeathOffsets();
       updateDeathOffsets();
       updateLookAnimation();
       updateLookAnimation();
 
 
@@ -2183,7 +2218,8 @@ void Player::interpolateTick(F32 dt)
    Point3F pos = delta.pos + delta.posVec * dt;
    Point3F pos = delta.pos + delta.posVec * dt;
    Point3F rot = delta.rot + delta.rotVec * dt;
    Point3F rot = delta.rot + delta.rotVec * dt;
 
 
-   setRenderPosition(pos,rot,dt);
+   if (!ignore_updates)
+      setRenderPosition(pos,rot,dt);
 
 
 /*
 /*
    // apply camera effects - is this the best place? - bramage
    // apply camera effects - is this the best place? - bramage
@@ -2208,6 +2244,9 @@ void Player::advanceTime(F32 dt)
 {
 {
    // Client side animations
    // Client side animations
    Parent::advanceTime(dt);
    Parent::advanceTime(dt);
+   // Increment timer for triggering idle events.
+   if (idle_timer >= 0.0f)
+      idle_timer += dt;
    updateActionThread();
    updateActionThread();
    updateAnimation(dt);
    updateAnimation(dt);
    updateSplash();
    updateSplash();
@@ -2498,6 +2537,30 @@ AngAxisF gPlayerMoveRot;
 
 
 void Player::updateMove(const Move* move)
 void Player::updateMove(const Move* move)
 {
 {
+   struct Move my_move;
+   if (override_movement && movement_op < 3)
+   {
+      my_move = *move;
+      switch (movement_op)
+      {
+      case 0: // add
+         my_move.x += movement_data.x;
+         my_move.y += movement_data.y;
+         my_move.z += movement_data.z;
+         break;
+      case 1: // mult
+         my_move.x *= movement_data.x;
+         my_move.y *= movement_data.y;
+         my_move.z *= movement_data.z;
+         break;
+      case 2: // replace
+         my_move.x = movement_data.x;
+         my_move.y = movement_data.y;
+         my_move.z = movement_data.z;
+         break;
+      }
+      move = &my_move;
+   }
    delta.move = *move;
    delta.move = *move;
 
 
 #ifdef TORQUE_OPENVR
 #ifdef TORQUE_OPENVR
@@ -2740,7 +2803,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));
@@ -2799,6 +2867,9 @@ void Player::updateMove(const Move* move)
       moveSpeed = 0.0f;
       moveSpeed = 0.0f;
    }
    }
 
 
+   // apply speed bias here.
+   speed_bias = speed_bias + (speed_bias_goal - speed_bias)*0.1f;
+   moveSpeed *= speed_bias;
    // Acceleration due to gravity
    // Acceleration due to gravity
    VectorF acc(0.0f, 0.0f, mGravity * mGravityMod * TickSec);
    VectorF acc(0.0f, 0.0f, mGravity * mGravityMod * TickSec);
 
 
@@ -3025,7 +3096,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;
@@ -3076,6 +3149,9 @@ void Player::updateMove(const Move* move)
          setActionThread( seq, true, false, true );
          setActionThread( seq, true, false, true );
 
 
          mJumpSurfaceLastContact = JumpSkipContactsMax;
          mJumpSurfaceLastContact = JumpSkipContactsMax;
+         // Flag the jump event trigger.
+         fx_s_triggers |= PLAYER_JUMP_S_TRIGGER;
+         setMaskBits(TriggerMask);
       }
       }
    }
    }
    else
    else
@@ -3493,6 +3569,19 @@ void Player::updateDamageState()
 
 
 void Player::updateLookAnimation(F32 dt)
 void Player::updateLookAnimation(F32 dt)
 {
 {
+   // If the preference setting overrideLookAnimation is true, the player's
+   // arm and head no longer animate according to the view direction. They
+   // are instead given fixed positions.
+   if (overrideLookAnimation)
+   {
+      if (mArmAnimation.thread) 
+         mShapeInstance->setPos(mArmAnimation.thread, armLookOverridePos);
+      if (mHeadVThread) 
+         mShapeInstance->setPos(mHeadVThread, headVLookOverridePos);
+      if (mHeadHThread) 
+         mShapeInstance->setPos(mHeadHThread, headHLookOverridePos);
+      return;
+   }
    // Calculate our interpolated head position.
    // Calculate our interpolated head position.
    Point3F renderHead = delta.head + delta.headVec * dt;
    Point3F renderHead = delta.head + delta.headVec * dt;
 
 
@@ -3533,6 +3622,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;
@@ -3742,6 +3833,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];
@@ -3766,6 +3859,11 @@ void Player::setActionThread(U32 action,bool forward,bool hold,bool wait,bool fs
       return;
       return;
    }
    }
 
 
+   if (isClientObject())
+   {
+      mark_idle = (action == PlayerData::RootAnim);
+      idle_timer = (mark_idle) ? 0.0f : -1.0f;
+   }
    PlayerData::ActionAnimation &anim = mDataBlock->actionList[action];
    PlayerData::ActionAnimation &anim = mDataBlock->actionList[action];
    if (anim.sequence != -1)
    if (anim.sequence != -1)
    {
    {
@@ -3858,7 +3956,8 @@ void Player::updateActionThread()
          offset = mDataBlock->decalOffset * getScale().x;
          offset = mDataBlock->decalOffset * getScale().x;
       }
       }
 
 
-      if( triggeredLeft || triggeredRight )
+      process_client_triggers(triggeredLeft, triggeredRight);
+      if ((triggeredLeft || triggeredRight) && !noFootfallFX)
       {
       {
          Point3F rot, pos;
          Point3F rot, pos;
          RayInfo rInfo;
          RayInfo rInfo;
@@ -3875,7 +3974,7 @@ void Player::updateActionThread()
             // Put footprints on surface, if appropriate for material.
             // Put footprints on surface, if appropriate for material.
 
 
             if( material && material->mShowFootprints
             if( material && material->mShowFootprints
-                && mDataBlock->decalData )
+                && mDataBlock->decalData && !footfallDecalOverride )
             {
             {
                Point3F normal;
                Point3F normal;
                Point3F tangent;
                Point3F tangent;
@@ -3886,8 +3985,8 @@ void Player::updateActionThread()
             
             
             // Emit footpuffs.
             // Emit footpuffs.
 
 
-            if( rInfo.t <= 0.5 && mWaterCoverage == 0.0
-                && material && material->mShowDust )
+            if (!footfallDustOverride && rInfo.t <= 0.5f && mWaterCoverage == 0.0f
+                                         && material && material->mShowDust )
             {
             {
                // New emitter every time for visibility reasons
                // New emitter every time for visibility reasons
                ParticleEmitter * emitter = new ParticleEmitter;
                ParticleEmitter * emitter = new ParticleEmitter;
@@ -3920,6 +4019,7 @@ void Player::updateActionThread()
 
 
             // Play footstep sound.
             // Play footstep sound.
             
             
+            if (footfallSoundOverride <= 0)
             playFootstepSound( triggeredLeft, material, rInfo.object );
             playFootstepSound( triggeredLeft, material, rInfo.object );
          }
          }
       }
       }
@@ -3941,8 +4041,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 =
@@ -4560,6 +4662,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.
@@ -4573,6 +4679,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();
       }
       }
    }
    }
 }
 }
@@ -4878,6 +4989,11 @@ Point3F Player::_move( const F32 travelTime, Collision *outCol )
          // we can use it to do impacts
          // we can use it to do impacts
          // and query collision.
          // and query collision.
          *outCol = *collision;
          *outCol = *collision;
+         if (isServerObject() && bd > 6.8f && collision->normal.z > 0.7f)
+         {
+            fx_s_triggers |= PLAYER_LANDING_S_TRIGGER;
+            setMaskBits(TriggerMask);
+         }
 
 
          // Subtract out velocity
          // Subtract out velocity
          VectorF dv = collision->normal * (bd + sNormalElasticity);
          VectorF dv = collision->normal * (bd + sNormalElasticity);
@@ -5879,7 +5995,12 @@ void Player::applyImpulse(const Point3F&,const VectorF& vec)
 
 
 bool Player::castRay(const Point3F &start, const Point3F &end, RayInfo* info)
 bool Player::castRay(const Point3F &start, const Point3F &end, RayInfo* info)
 {
 {
-   if (getDamageState() != Enabled)
+   // In standard Torque there's a rather brute force culling of all
+   // non-enabled players (corpses) from the ray cast. But, to
+   // demonstrate a resurrection spell, we need corpses to be
+   // selectable, so this code change allows consideration of corpses
+   // in the ray cast if corpsesHiddenFromRayCast is set to false.
+   if (sCorpsesHiddenFromRayCast && getDamageState() != Enabled)
       return false;
       return false;
 
 
    // Collide against bounding box. Need at least this for the editor.
    // Collide against bounding box. Need at least this for the editor.
@@ -6146,7 +6267,8 @@ void Player::readPacketData(GameConnection *connection, BitStream *stream)
    stream->read(&mHead.z);
    stream->read(&mHead.z);
    stream->read(&rot.z);
    stream->read(&rot.z);
    rot.x = rot.y = 0;
    rot.x = rot.y = 0;
-   setPosition(pos,rot);
+   if (!ignore_updates)
+      setPosition(pos,rot);
    delta.head = mHead;
    delta.head = mHead;
    delta.rot = rot;
    delta.rot = rot;
 
 
@@ -6188,6 +6310,7 @@ U32 Player::packUpdate(NetConnection *con, U32 mask, BitStream *stream)
          mArmAnimation.action != mDataBlock->lookAction))) {
          mArmAnimation.action != mDataBlock->lookAction))) {
       stream->writeInt(mArmAnimation.action,PlayerData::ActionAnimBits);
       stream->writeInt(mArmAnimation.action,PlayerData::ActionAnimBits);
    }
    }
+   retMask = afx_packUpdate(con, mask, stream, retMask);
 
 
    // The rest of the data is part of the control object packet update.
    // The rest of the data is part of the control object packet update.
    // If we're controlled by this client, we don't need to send it.
    // If we're controlled by this client, we don't need to send it.
@@ -6292,6 +6415,7 @@ void Player::unpackUpdate(NetConnection *con, BitStream *stream)
          mArmAnimation.action = action;
          mArmAnimation.action = action;
    }
    }
 
 
+   afx_unpackUpdate(con, stream);
    // Done if controlled by client ( and not initial update )
    // Done if controlled by client ( and not initial update )
    if(stream->readFlag())
    if(stream->readFlag())
       return;
       return;
@@ -6391,7 +6515,8 @@ void Player::unpackUpdate(NetConnection *con, BitStream *stream)
             }
             }
             delta.pos = pos;
             delta.pos = pos;
             delta.rot = rot;
             delta.rot = rot;
-            setPosition(pos,rot);
+            if (!ignore_updates)
+               setPosition(pos,rot);
          }
          }
       }
       }
       else 
       else 
@@ -6403,7 +6528,8 @@ void Player::unpackUpdate(NetConnection *con, BitStream *stream)
          delta.rotVec.set(0.0f, 0.0f, 0.0f);
          delta.rotVec.set(0.0f, 0.0f, 0.0f);
          delta.warpTicks = 0;
          delta.warpTicks = 0;
          delta.dt = 0.0f;
          delta.dt = 0.0f;
-         setPosition(pos,rot);
+         if (!ignore_updates)
+            setPosition(pos,rot);
       }
       }
    }
    }
    F32 energy = stream->readFloat(EnergyLevelBits) * mDataBlock->maxEnergy;
    F32 energy = stream->readFloat(EnergyLevelBits) * mDataBlock->maxEnergy;
@@ -6819,6 +6945,7 @@ void Player::consoleInit()
    Con::addVariable("$player::extendedMoveHeadPosRotIndex", TypeS32, &smExtendedMoveHeadPosRotIndex, 
    Con::addVariable("$player::extendedMoveHeadPosRotIndex", TypeS32, &smExtendedMoveHeadPosRotIndex, 
       "@brief The ExtendedMove position/rotation index used for head movements.\n\n"
       "@brief The ExtendedMove position/rotation index used for head movements.\n\n"
       "@ingroup GameObjects\n");
       "@ingroup GameObjects\n");
+   afx_consoleInit();
 }
 }
 
 
 //--------------------------------------------------------------------------
 //--------------------------------------------------------------------------
@@ -6863,6 +6990,8 @@ void Player::calcClassRenderData()
 
 
 void Player::playFootstepSound( bool triggeredLeft, Material* contactMaterial, SceneObject* contactObject )
 void Player::playFootstepSound( bool triggeredLeft, Material* contactMaterial, SceneObject* contactObject )
 {
 {
+   if (footfallSoundOverride > 0)
+      return;
    MatrixF footMat = getTransform();
    MatrixF footMat = getTransform();
    if( mWaterCoverage > 0.0 )
    if( mWaterCoverage > 0.0 )
    {
    {
@@ -7172,6 +7301,340 @@ void Player::renderConvex( ObjectRenderInst *ri, SceneRenderState *state, BaseMa
    GFX->leaveDebugEvent();
    GFX->leaveDebugEvent();
 }
 }
 
 
+// static 
+bool Player::sCorpsesHiddenFromRayCast = true; // this default matches stock Torque behavior.
+
+// static 
+void Player::afx_consoleInit()
+{
+   Con::addVariable("pref::Player::corpsesHiddenFromRayCast", TypeBool, &sCorpsesHiddenFromRayCast);
+}
+
+void Player::afx_init()
+{
+   overrideLookAnimation = false;
+   armLookOverridePos = 0.5f;
+   headVLookOverridePos = 0.5f;
+   headHLookOverridePos = 0.5f;
+   ignore_updates = false;
+   fx_c_triggers = 0;
+   mark_fx_c_triggers = 0;
+   fx_s_triggers = 0;
+   move_trigger_states = 0;
+   z_velocity = 0.0f;
+   mark_idle = false;
+   idle_timer = 0.0f;
+   mark_s_landing = false;
+   speed_bias = 1.0f;
+   speed_bias_goal = 1.0f;
+   override_movement = 0;
+   movement_data.zero();
+   movement_op = 1;
+   last_movement_tag = 0;
+   footfallDecalOverride = 0;
+   footfallSoundOverride = 0;
+   footfallDustOverride = 0;
+   noFootfallFX = false;
+}
+
+U32 Player::afx_packUpdate(NetConnection* con, U32 mask, BitStream* stream, U32 retMask)
+{
+#if 0
+   if (stream->writeFlag(mask & LookOverrideMask))
+#else
+   if (stream->writeFlag(mask & ActionMask))
+#endif
+      stream->writeFlag(overrideLookAnimation);
+
+   if (stream->writeFlag(mask & TriggerMask))
+      stream->write(fx_s_triggers);
+
+   return retMask;
+}
+
+void Player::afx_unpackUpdate(NetConnection* con, BitStream* stream)
+{
+   if (stream->readFlag()) // LookOverrideMask
+      overrideLookAnimation = stream->readFlag();
+
+   if (stream->readFlag()) // TriggerMask
+   {
+      U32 mask;
+      stream->read(&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();
+}
+
+
+void Player::setLookAnimationOverride(bool flag) 
+{ 
+   overrideLookAnimation = flag; 
+#if 0
+   setMaskBits(LookOverrideMask);
+#else
+   setMaskBits(ActionMask);
+#endif
+}
+
+ConsoleMethod(Player, setLookAnimationOverride, void, 3, 3, "setLookAnimationOverride(flag)")
+{
+   object->setLookAnimationOverride(dAtob(argv[2]));
+}
+
+ConsoleMethod(Player, copyHeadRotation, void, 3, 3, "copyHeadRotation(other_player)")
+{
+   Player* other_player = dynamic_cast<Player*>(Sim::findObject(argv[2]));
+   if (other_player)
+      object->copyHeadRotation(other_player);
+}
+void Player::process_client_triggers(bool triggeredLeft, bool triggeredRight)
+{
+   bool mark_landing = false;
+   Point3F my_vel = getVelocity();
+   if (my_vel.z > 5.0f)
+      z_velocity = 1;
+   else if (my_vel.z < -5.0f)
+      z_velocity = -1;
+   else
+   {
+      if (z_velocity < 0)
+         mark_landing = true;
+      z_velocity = 0.0f;
+   }
+
+   fx_c_triggers = mark_fx_c_triggers;
+   if (triggeredLeft)
+      fx_c_triggers |= PLAYER_LF_FOOT_C_TRIGGER;
+   if (triggeredRight)
+      fx_c_triggers |= PLAYER_RT_FOOT_C_TRIGGER;
+   if (mark_landing)
+      fx_c_triggers |= PLAYER_LANDING_C_TRIGGER;
+   if (idle_timer > 10.0f)
+   {
+      fx_c_triggers |= PLAYER_IDLE_C_TRIGGER;
+      idle_timer = 0.0f;
+   }
+   if (fx_c_triggers & PLAYER_LANDING_S_TRIGGER)
+   {
+      fx_c_triggers &= ~(PLAYER_LANDING_S_TRIGGER);
+   }
+}
+U32 Player::unique_movement_tag_counter = 1;
+
+void Player::setMovementSpeedBias(F32 bias) 
+{ 
+   speed_bias_goal = bias; 
+}
+
+U32 Player::setMovementOverride(F32 bias, const Point3F* mov, U32 op) 
+{ 
+   if (mov)
+   {
+      movement_data = *mov;
+      override_movement = true;
+      movement_op = (U8)op;
+   }
+   else
+      override_movement = false;
+
+   speed_bias_goal = bias;
+
+   last_movement_tag = unique_movement_tag_counter++;
+   return last_movement_tag;
+}
+
+void Player::restoreMovement(U32 tag) 
+{ 
+   if (tag != 0 && tag == last_movement_tag)
+   {
+      speed_bias_goal = 1.0;
+      override_movement = false;
+   }
+}
+
+ConsoleMethod(Player, setMovementSpeedBias, void, 3, 3, "setMovementSpeedBias(F32 bias)")
+{
+   object->setMovementSpeedBias(dAtof(argv[2]));
+}
+
+void Player::overrideFootfallFX(bool decals, bool sounds, bool dust) 
+{ 
+   if (decals)
+      footfallDecalOverride++; 
+   if (sounds)
+      footfallSoundOverride++; 
+   if (dust)
+      footfallDustOverride++; 
+   noFootfallFX = (footfallDecalOverride > 0 && footfallSoundOverride > 0 && footfallDustOverride > 0);
+}
+
+void Player::restoreFootfallFX(bool decals, bool sounds, bool dust) 
+{ 
+   if (decals && footfallDecalOverride) 
+      footfallDecalOverride--; 
+   if (sounds && footfallSoundOverride) 
+      footfallSoundOverride--; 
+   if (dust && footfallDustOverride) 
+      footfallDustOverride--; 
+   noFootfallFX = (footfallDecalOverride > 0 && footfallSoundOverride > 0 && footfallDustOverride > 0);
+}
 #ifdef TORQUE_OPENVR
 #ifdef TORQUE_OPENVR
 void Player::setControllers(Vector<OpenVRTrackedObject*> controllerList)
 void Player::setControllers(Vector<OpenVRTrackedObject*> controllerList)
 {
 {

+ 90 - 1
Engine/source/T3D/player.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 _PLAYER_H_
 #ifndef _PLAYER_H_
 #define _PLAYER_H_
 #define _PLAYER_H_
 
 
@@ -401,7 +406,8 @@ protected:
       ActionMask   = Parent::NextFreeMask << 0,
       ActionMask   = Parent::NextFreeMask << 0,
       MoveMask     = Parent::NextFreeMask << 1,
       MoveMask     = Parent::NextFreeMask << 1,
       ImpactMask   = Parent::NextFreeMask << 2,
       ImpactMask   = Parent::NextFreeMask << 2,
-      NextFreeMask = Parent::NextFreeMask << 3
+      TriggerMask      = Parent::NextFreeMask << 3,
+      NextFreeMask     = Parent::NextFreeMask << 4
    };
    };
 
 
    SimObjectPtr<ParticleEmitter> mSplashEmitter[PlayerData::NUM_SPLASH_EMITTERS];
    SimObjectPtr<ParticleEmitter> mSplashEmitter[PlayerData::NUM_SPLASH_EMITTERS];
@@ -780,6 +786,89 @@ public:
    virtual void prepRenderImage( SceneRenderState* state );
    virtual void prepRenderImage( SceneRenderState* state );
    virtual void renderConvex( ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance *overrideMat );   
    virtual void renderConvex( ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance *overrideMat );   
    virtual void renderMountedImage( U32 imageSlot, TSRenderState &rstate, SceneRenderState *state );
    virtual void renderMountedImage( U32 imageSlot, TSRenderState &rstate, SceneRenderState *state );
+private:
+   static void  afx_consoleInit();
+   void         afx_init();
+   U32          afx_packUpdate(NetConnection*, U32 mask, BitStream*, U32 retMask);
+   void         afx_unpackUpdate(NetConnection*, BitStream*);
+private:
+   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); }
+   
+protected:
+   bool         overrideLookAnimation;
+   F32          armLookOverridePos;
+   F32          headVLookOverridePos;
+   F32          headHLookOverridePos;
+public:
+   void         setLookAnimationOverride(bool flag);
+   void         copyHeadRotation(const Player* p) { mHead = p->mHead; }
+public:
+   bool ignore_updates;
+   void resetContactTimer() { mContactTimer = 0; }
+private:
+   U8     move_trigger_states;
+   U32    fx_s_triggers;
+   U32    mark_fx_c_triggers;
+   U32    fx_c_triggers;
+   F32    z_velocity;
+   bool   mark_idle;
+   F32    idle_timer;
+   bool   mark_s_landing;
+   void   process_client_triggers(bool triggeredLeft, bool triggeredRight);
+public:
+   enum {
+     // server events
+     PLAYER_MOVE_TRIGGER_0        = BIT(0),
+     PLAYER_MOVE_TRIGGER_1        = BIT(1),
+     PLAYER_MOVE_TRIGGER_2        = BIT(2),
+     PLAYER_MOVE_TRIGGER_3        = BIT(3),
+     PLAYER_MOVE_TRIGGER_4        = BIT(4),
+     PLAYER_MOVE_TRIGGER_5        = BIT(5),
+     PLAYER_LANDING_S_TRIGGER     = BIT(6),
+
+     PLAYER_FIRE_S_TRIGGER        = PLAYER_MOVE_TRIGGER_0,
+     PLAYER_FIRE_ALT_S_TRIGGER    = PLAYER_MOVE_TRIGGER_1,
+     PLAYER_JUMP_S_TRIGGER        = BIT(7),
+
+     // client events
+     PLAYER_LF_FOOT_C_TRIGGER       = BIT(16),
+     PLAYER_RT_FOOT_C_TRIGGER       = BIT(17),
+     PLAYER_LANDING_C_TRIGGER       = BIT(18),
+     PLAYER_IDLE_C_TRIGGER          = BIT(19),
+   };
+   U32  getClientEventTriggers() const { return fx_c_triggers; }
+   U32  getServerEventTriggers() const { return fx_s_triggers; }
+private:
+   F32      speed_bias;
+   F32      speed_bias_goal;
+   bool     override_movement;
+   Point3F  movement_data;
+   U8       movement_op;
+   U32      last_movement_tag;
+   static U32   unique_movement_tag_counter;
+public:
+   void     setMovementSpeedBias(F32 bias);
+   U32      setMovementOverride(F32 bias, const Point3F* mov=0, U32 op=1);
+   void     restoreMovement(U32 tag);
+private:
+   S32      footfallDecalOverride;
+   S32      footfallSoundOverride;
+   S32      footfallDustOverride;
+   bool     noFootfallFX;
+public:
+   void     overrideFootfallFX(bool decals=true, bool sounds=true, bool dust=true);
+   void     restoreFootfallFX(bool decals=true, bool sounds=true, bool dust=true);
 };
 };
 
 
 typedef Player::Pose PlayerPose;
 typedef Player::Pose PlayerPose;

+ 62 - 3
Engine/source/T3D/projectile.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/projectile.h"
 #include "T3D/projectile.h"
 
 
@@ -190,6 +195,40 @@ ProjectileData::ProjectileData()
    lightDescId = 0;
    lightDescId = 0;
 }
 }
 
 
+ProjectileData::ProjectileData(const ProjectileData& other, bool temp_clone) : GameBaseData(other, temp_clone)
+{
+   projectileShapeName = other.projectileShapeName;
+   faceViewer = other.faceViewer; // -- always set to false
+   scale = other.scale;
+   velInheritFactor = other.velInheritFactor;
+   muzzleVelocity = other.muzzleVelocity;
+   impactForce = other.impactForce;
+   isBallistic = other.isBallistic;
+   bounceElasticity = other.bounceElasticity;
+   bounceFriction = other.bounceFriction;
+   gravityMod = other.gravityMod;
+   lifetime = other.lifetime;
+   armingDelay = other.armingDelay;
+   fadeDelay = other.fadeDelay;
+   explosion = other.explosion;
+   explosionId = other.explosionId; // -- for pack/unpack of explosion ptr
+   waterExplosion = other.waterExplosion;
+   waterExplosionId = other.waterExplosionId; // -- for pack/unpack of waterExplosion ptr
+   splash = other.splash;
+   splashId = other.splashId; // -- for pack/unpack of splash ptr
+   decal = other.decal;
+   decalId = other.decalId; // -- for pack/unpack of decal ptr
+   sound = other.sound;
+   lightDesc = other.lightDesc;
+   lightDescId = other.lightDescId; // -- for pack/unpack of lightDesc ptr
+   projectileShape = other.projectileShape; // -- TSShape loads using projectileShapeName
+   activateSeq = other.activateSeq; // -- from projectileShape sequence "activate"
+   maintainSeq = other.maintainSeq; // -- from projectileShape sequence "maintain"
+   particleEmitter = other.particleEmitter;
+   particleEmitterId = other.particleEmitterId; // -- for pack/unpack of particleEmitter ptr
+   particleWaterEmitter = other.particleWaterEmitter;
+   particleWaterEmitterId = other.particleWaterEmitterId; // -- for pack/unpack of particleWaterEmitter ptr
+}
 //--------------------------------------------------------------------------
 //--------------------------------------------------------------------------
 
 
 void ProjectileData::initPersistFields()
 void ProjectileData::initPersistFields()
@@ -274,6 +313,13 @@ void ProjectileData::initPersistFields()
       "A value of 1.0 will assume \"normal\" influence upon it.\n"
       "A value of 1.0 will assume \"normal\" influence upon it.\n"
       "The magnitude of gravity is assumed to be 9.81 m/s/s\n\n"
       "The magnitude of gravity is assumed to be 9.81 m/s/s\n\n"
       "@note ProjectileData::isBallistic must be true for this to have any affect.");
       "@note ProjectileData::isBallistic must be true for this to have any affect.");
+   // disallow some field substitutions
+   onlyKeepClearSubstitutions("explosion");
+   onlyKeepClearSubstitutions("particleEmitter");
+   onlyKeepClearSubstitutions("particleWaterEmitter");
+   onlyKeepClearSubstitutions("sound");
+   onlyKeepClearSubstitutions("splash");
+   onlyKeepClearSubstitutions("waterExplosion");
 
 
    Parent::initPersistFields();
    Parent::initPersistFields();
 }
 }
@@ -574,6 +620,11 @@ Projectile::Projectile()
 
 
    mLightState.clear();
    mLightState.clear();
    mLightState.setLightInfo( mLight );
    mLightState.setLightInfo( mLight );
+
+   mDataBlock = 0;
+   ignoreSourceTimeout = false;
+   dynamicCollisionMask = csmDynamicCollisionMask;
+   staticCollisionMask = csmStaticCollisionMask;
 }
 }
 
 
 Projectile::~Projectile()
 Projectile::~Projectile()
@@ -582,6 +633,11 @@ Projectile::~Projectile()
 
 
    delete mProjectileShape;
    delete mProjectileShape;
    mProjectileShape = NULL;
    mProjectileShape = NULL;
+   if (mDataBlock && mDataBlock->isTempClone())
+   {
+      delete mDataBlock;
+      mDataBlock = 0;
+   }
 }
 }
 
 
 //--------------------------------------------------------------------------
 //--------------------------------------------------------------------------
@@ -609,6 +665,7 @@ void Projectile::initPersistFields()
    addField("sourceSlot",       TypeS32,     Offset(mSourceObjectSlot, Projectile),
    addField("sourceSlot",       TypeS32,     Offset(mSourceObjectSlot, Projectile),
       "@brief The sourceObject's weapon slot that the projectile originates from.\n\n");
       "@brief The sourceObject's weapon slot that the projectile originates from.\n\n");
 
 
+   addField("ignoreSourceTimeout",  TypeBool,   Offset(ignoreSourceTimeout, Projectile));
    endGroup("Source");
    endGroup("Source");
 
 
 
 
@@ -1088,7 +1145,7 @@ void Projectile::simulate( F32 dt )
    // disable the source objects collision reponse for a short time while we
    // disable the source objects collision reponse for a short time while we
    // determine if the projectile is capable of moving from the old position
    // determine if the projectile is capable of moving from the old position
    // to the new position, otherwise we'll hit ourself
    // to the new position, otherwise we'll hit ourself
-   bool disableSourceObjCollision = (mSourceObject.isValid() && mCurrTick <= SourceIdTimeoutTicks);
+   bool disableSourceObjCollision = (mSourceObject.isValid() && (ignoreSourceTimeout || mCurrTick <= SourceIdTimeoutTicks));
    if ( disableSourceObjCollision )
    if ( disableSourceObjCollision )
       mSourceObject->disableCollision();
       mSourceObject->disableCollision();
    disableCollision();
    disableCollision();
@@ -1105,12 +1162,12 @@ void Projectile::simulate( F32 dt )
    if ( mPhysicsWorld )
    if ( mPhysicsWorld )
       hit = mPhysicsWorld->castRay( oldPosition, newPosition, &rInfo, Point3F( newPosition - oldPosition) * mDataBlock->impactForce );            
       hit = mPhysicsWorld->castRay( oldPosition, newPosition, &rInfo, Point3F( newPosition - oldPosition) * mDataBlock->impactForce );            
    else 
    else 
-      hit = getContainer()->castRay(oldPosition, newPosition, csmDynamicCollisionMask | csmStaticCollisionMask, &rInfo);
+      hit = getContainer()->castRay(oldPosition, newPosition, dynamicCollisionMask | staticCollisionMask, &rInfo);
 
 
    if ( hit )
    if ( hit )
    {
    {
       // make sure the client knows to bounce
       // make sure the client knows to bounce
-      if ( isServerObject() && ( rInfo.object->getTypeMask() & csmStaticCollisionMask ) == 0 )
+      if(isServerObject() && (rInfo.object->getTypeMask() & staticCollisionMask) == 0)
          setMaskBits( BounceMask );
          setMaskBits( BounceMask );
 
 
       MatrixF xform( true );
       MatrixF xform( true );
@@ -1301,6 +1358,7 @@ U32 Projectile::packUpdate( NetConnection *con, U32 mask, BitStream *stream )
             stream->writeRangedU32( U32(mSourceObjectSlot),
             stream->writeRangedU32( U32(mSourceObjectSlot),
                                     0, 
                                     0, 
                                     ShapeBase::MaxMountedImages - 1 );
                                     ShapeBase::MaxMountedImages - 1 );
+            stream->writeFlag(ignoreSourceTimeout);
          }
          }
          else 
          else 
             // have not recieved the ghost for the source object yet, try again later
             // have not recieved the ghost for the source object yet, try again later
@@ -1344,6 +1402,7 @@ void Projectile::unpackUpdate(NetConnection* con, BitStream* stream)
          mSourceObjectId   = stream->readRangedU32( 0, NetConnection::MaxGhostCount );
          mSourceObjectId   = stream->readRangedU32( 0, NetConnection::MaxGhostCount );
          mSourceObjectSlot = stream->readRangedU32( 0, ShapeBase::MaxMountedImages - 1 );
          mSourceObjectSlot = stream->readRangedU32( 0, ShapeBase::MaxMountedImages - 1 );
 
 
+         ignoreSourceTimeout = stream->readFlag();
          NetObject* pObject = con->resolveGhost( mSourceObjectId );
          NetObject* pObject = con->resolveGhost( mSourceObjectId );
          if ( pObject != NULL )
          if ( pObject != NULL )
             mSourceObject = dynamic_cast<ShapeBase*>( pObject );
             mSourceObject = dynamic_cast<ShapeBase*>( pObject );

+ 12 - 0
Engine/source/T3D/projectile.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 _PROJECTILE_H_
 #ifndef _PROJECTILE_H_
 #define _PROJECTILE_H_
 #define _PROJECTILE_H_
 
 
@@ -144,6 +149,9 @@ public:
    
    
    DECLARE_CALLBACK( void, onExplode, ( Projectile* proj, Point3F pos, F32 fade ) );
    DECLARE_CALLBACK( void, onExplode, ( Projectile* proj, Point3F pos, F32 fade ) );
    DECLARE_CALLBACK( void, onCollision, ( Projectile* proj, SceneObject* col, F32 fade, Point3F pos, Point3F normal ) );
    DECLARE_CALLBACK( void, onCollision, ( Projectile* proj, SceneObject* col, F32 fade, Point3F pos, Point3F normal ) );
+public:
+   ProjectileData(const ProjectileData&, bool = false);
+   virtual bool allowSubstitutions() const { return true; }
 };
 };
 
 
 
 
@@ -279,6 +287,10 @@ protected:
    Point3F mExplosionPosition;
    Point3F mExplosionPosition;
    Point3F mExplosionNormal;
    Point3F mExplosionNormal;
    U32     mCollideHitType;   
    U32     mCollideHitType;   
+public:
+   bool   ignoreSourceTimeout;
+   U32    dynamicCollisionMask;
+   U32    staticCollisionMask;
 };
 };
 
 
 #endif // _PROJECTILE_H_
 #endif // _PROJECTILE_H_

+ 397 - 0
Engine/source/T3D/shapeBase.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/shapeBase.h"
 #include "T3D/shapeBase.h"
 
 
@@ -194,6 +199,69 @@ ShapeBaseData::ShapeBaseData()
    inheritEnergyFromMount( false )
    inheritEnergyFromMount( false )
 {      
 {      
    dMemset( mountPointNode, -1, sizeof( S32 ) * SceneObject::NumMountPoints );
    dMemset( mountPointNode, -1, sizeof( S32 ) * SceneObject::NumMountPoints );
+   remap_txr_tags = NULL;
+   remap_buffer = NULL;
+   silent_bbox_check = false;
+}
+
+ShapeBaseData::ShapeBaseData(const ShapeBaseData& other, bool temp_clone) : GameBaseData(other, temp_clone)
+{
+   shadowEnable = other.shadowEnable;
+   shadowSize = other.shadowSize;
+   shadowMaxVisibleDistance = other.shadowMaxVisibleDistance;
+   shadowProjectionDistance = other.shadowProjectionDistance;
+   shadowSphereAdjust = other.shadowSphereAdjust;
+   shapeName = other.shapeName;
+   cloakTexName = other.cloakTexName;
+   cubeDescName = other.cubeDescName;
+   cubeDescId = other.cubeDescId;
+   reflectorDesc = other.reflectorDesc;
+   debris = other.debris;
+   debrisID = other.debrisID; // -- for pack/unpack of debris ptr
+   debrisShapeName = other.debrisShapeName;
+   debrisShape = other.debrisShape; // -- TSShape loaded using debrisShapeName
+   explosion = other.explosion;
+   explosionID = other.explosionID; // -- for pack/unpack of explosion ptr
+   underwaterExplosion = other.underwaterExplosion;
+   underwaterExplosionID = other.underwaterExplosionID; // -- for pack/unpack of underwaterExplosion ptr
+   mass = other.mass;
+   drag = other.drag;
+   density = other.density;
+   maxEnergy = other.maxEnergy;
+   maxDamage = other.maxDamage;
+   repairRate = other.repairRate;
+   disabledLevel = other.disabledLevel;
+   destroyedLevel = other.destroyedLevel;
+   cameraMaxDist = other.cameraMaxDist;
+   cameraMinDist = other.cameraMinDist;
+   cameraDefaultFov = other.cameraDefaultFov;
+   cameraMinFov = other.cameraMinFov;
+   cameraMaxFov = other.cameraMaxFov;
+   cameraCanBank = other.cameraCanBank;
+   mountedImagesBank = other.mountedImagesBank;
+   mShape = other.mShape; // -- TSShape loaded using shapeName
+   mCRC = other.mCRC; // -- from shape, used to verify client shape 
+   computeCRC = other.computeCRC;
+   eyeNode = other.eyeNode; // -- from shape node "eye"
+   earNode = other.earNode; // -- from shape node "ear"
+   cameraNode = other.cameraNode; // -- from shape node "cam"
+   dMemcpy(mountPointNode, other.mountPointNode, sizeof(mountPointNode)); // -- from shape nodes "mount#" 0-31
+   debrisDetail = other.debrisDetail; // -- from shape detail "Debris-17"
+   damageSequence = other.damageSequence; // -- from shape sequence "Damage"
+   hulkSequence = other.hulkSequence; // -- from shape sequence "Visibility"
+   observeThroughObject = other.observeThroughObject;
+   collisionDetails = other.collisionDetails; // -- calc from shape (this is a Vector copy)
+   collisionBounds = other.collisionBounds; // -- calc from shape (this is a Vector copy)
+   LOSDetails = other.LOSDetails; // -- calc from shape (this is a Vector copy)
+   firstPersonOnly = other.firstPersonOnly;
+   useEyePoint = other.useEyePoint;
+   isInvincible = other.isInvincible;
+   renderWhenDestroyed = other.renderWhenDestroyed;
+   inheritEnergyFromMount = other.inheritEnergyFromMount;
+   remap_txr_tags = other.remap_txr_tags;
+   remap_buffer = other.remap_buffer;
+   txr_tag_remappings = other.txr_tag_remappings;
+   silent_bbox_check = other.silent_bbox_check;
 }
 }
 
 
 struct ShapeBaseDataProto
 struct ShapeBaseDataProto
@@ -228,6 +296,8 @@ static ShapeBaseDataProto gShapeBaseDataProto;
 ShapeBaseData::~ShapeBaseData()
 ShapeBaseData::~ShapeBaseData()
 {
 {
 
 
+   if (remap_buffer && !isTempClone())
+      dFree(remap_buffer);
 }
 }
 
 
 bool ShapeBaseData::preload(bool server, String &errorStr)
 bool ShapeBaseData::preload(bool server, String &errorStr)
@@ -337,11 +407,13 @@ bool ShapeBaseData::preload(bool server, String &errorStr)
 
 
             if (!mShape->bounds.isContained(collisionBounds.last()))
             if (!mShape->bounds.isContained(collisionBounds.last()))
             {
             {
+               if (!silent_bbox_check)
                Con::warnf("Warning: shape %s collision detail %d (Collision-%d) bounds exceed that of shape.", shapeName, collisionDetails.size() - 1, collisionDetails.last());
                Con::warnf("Warning: shape %s collision detail %d (Collision-%d) bounds exceed that of shape.", shapeName, collisionDetails.size() - 1, collisionDetails.last());
                collisionBounds.last() = mShape->bounds;
                collisionBounds.last() = mShape->bounds;
             }
             }
             else if (collisionBounds.last().isValidBox() == false)
             else if (collisionBounds.last().isValidBox() == false)
             {
             {
+               if (!silent_bbox_check)
                Con::errorf("Error: shape %s-collision detail %d (Collision-%d) bounds box invalid!", shapeName, collisionDetails.size() - 1, collisionDetails.last());
                Con::errorf("Error: shape %s-collision detail %d (Collision-%d) bounds box invalid!", shapeName, collisionDetails.size() - 1, collisionDetails.last());
                collisionBounds.last() = mShape->bounds;
                collisionBounds.last() = mShape->bounds;
             }
             }
@@ -413,6 +485,29 @@ bool ShapeBaseData::preload(bool server, String &errorStr)
       F32 w = mShape->bounds.len_y() / 2;
       F32 w = mShape->bounds.len_y() / 2;
       if (cameraMaxDist < w)
       if (cameraMaxDist < w)
          cameraMaxDist = w;
          cameraMaxDist = w;
+      // just parse up the string and collect the remappings in txr_tag_remappings.
+      if (!server && remap_txr_tags != NULL && remap_txr_tags != StringTable->insert(""))
+      {
+         txr_tag_remappings.clear();
+         if (remap_buffer)
+            dFree(remap_buffer);
+
+         remap_buffer = dStrdup(remap_txr_tags);
+
+         char* remap_token = dStrtok(remap_buffer, " \t");
+         while (remap_token != NULL)
+         {
+            char* colon = dStrchr(remap_token, ':');
+            if (colon)
+            {
+               *colon = '\0';
+               txr_tag_remappings.increment();
+               txr_tag_remappings.last().old_tag = remap_token;
+               txr_tag_remappings.last().new_tag = colon+1;
+            }
+            remap_token = dStrtok(NULL, " \t");
+         }
+      }
    }
    }
 
 
    if(!server)
    if(!server)
@@ -586,6 +681,12 @@ void ShapeBaseData::initPersistFields()
 
 
    endGroup( "Reflection" );
    endGroup( "Reflection" );
 
 
+   addField("remapTextureTags",      TypeString,   Offset(remap_txr_tags, ShapeBaseData));
+   addField("silentBBoxValidation",  TypeBool,     Offset(silent_bbox_check, ShapeBaseData));
+   // disallow some field substitutions
+   onlyKeepClearSubstitutions("debris"); // subs resolving to "~~", or "~0" are OK
+   onlyKeepClearSubstitutions("explosion");
+   onlyKeepClearSubstitutions("underwaterExplosion");
    Parent::initPersistFields();
    Parent::initPersistFields();
 }
 }
 
 
@@ -738,6 +839,8 @@ void ShapeBaseData::packData(BitStream* stream)
    //stream->write(reflectMinDist);
    //stream->write(reflectMinDist);
    //stream->write(reflectMaxDist);
    //stream->write(reflectMaxDist);
    //stream->write(reflectDetailAdjust);
    //stream->write(reflectDetailAdjust);
+   stream->writeString(remap_txr_tags);
+   stream->writeFlag(silent_bbox_check);
 }
 }
 
 
 void ShapeBaseData::unpackData(BitStream* stream)
 void ShapeBaseData::unpackData(BitStream* stream)
@@ -839,6 +942,8 @@ void ShapeBaseData::unpackData(BitStream* stream)
    //stream->read(&reflectMinDist);
    //stream->read(&reflectMinDist);
    //stream->read(&reflectMaxDist);
    //stream->read(&reflectMaxDist);
    //stream->read(&reflectDetailAdjust);
    //stream->read(&reflectDetailAdjust);
+   remap_txr_tags = stream->readSTString();
+   silent_bbox_check = stream->readFlag();
 }
 }
 
 
 
 
@@ -941,6 +1046,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;
 }
 }
 
 
 
 
@@ -1079,6 +1191,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 );
@@ -1098,10 +1220,61 @@ bool ShapeBase::onNewDataBlock( GameBaseData *dptr, bool reload )
    // a shape assigned to this object.
    // a shape assigned to this object.
    if (bool(mDataBlock->mShape)) {
    if (bool(mDataBlock->mShape)) {
       delete mShapeInstance;
       delete mShapeInstance;
+      if (isClientObject() && mDataBlock->txr_tag_remappings.size() > 0)
+      {
+         // temporarily substitute material tags with alternates
+         TSMaterialList* mat_list = mDataBlock->mShape->materialList;
+         if (mat_list)
+         {
+            for (S32 i = 0; i < mDataBlock->txr_tag_remappings.size(); i++)
+            {
+               ShapeBaseData::TextureTagRemapping* remap = &mDataBlock->txr_tag_remappings[i];
+               Vector<String> & mat_names = (Vector<String>&) mat_list->getMaterialNameList();
+               for (S32 j = 0; j < mat_names.size(); j++) 
+               {
+                  if (mat_names[j].compare(remap->old_tag, dStrlen(remap->old_tag), String::NoCase) == 0)
+                  {
+                     mat_names[j] = String(remap->new_tag);
+                     mat_names[j].insert(0,'#');
+                     break;
+                  }
+               }
+            }
+         }
+      }
       mShapeInstance = new TSShapeInstance(mDataBlock->mShape, isClientObject());
       mShapeInstance = new TSShapeInstance(mDataBlock->mShape, isClientObject());
       if (isClientObject())
       if (isClientObject())
+      {
          mShapeInstance->cloneMaterialList();
          mShapeInstance->cloneMaterialList();
 
 
+         // restore the material tags to original form
+         if (mDataBlock->txr_tag_remappings.size() > 0)
+         {
+            TSMaterialList* mat_list = mDataBlock->mShape->materialList;
+            if (mat_list)
+            {
+               for (S32 i = 0; i < mDataBlock->txr_tag_remappings.size(); i++)
+               {
+                  ShapeBaseData::TextureTagRemapping* remap = &mDataBlock->txr_tag_remappings[i];
+                  Vector<String> & mat_names = (Vector<String>&) mat_list->getMaterialNameList();
+                  for (S32 j = 0; j < mat_names.size(); j++) 
+                  {
+                     String::SizeType len = mat_names[j].length();
+                     if (len > 1)
+                     {
+                        String temp_name = mat_names[j].substr(1,len-1);
+                        if (temp_name.compare(remap->new_tag, dStrlen(remap->new_tag)) == 0)
+                        {
+                           mat_names[j] = String(remap->old_tag);
+                           break;
+                        }
+                     }
+                  }
+               }
+            }
+         }
+      }
+
       mObjBox = mDataBlock->mShape->bounds;
       mObjBox = mDataBlock->mShape->bounds;
       resetWorldBox();
       resetWorldBox();
 
 
@@ -3550,6 +3723,31 @@ void ShapeBase::setCurrentWaterObject( WaterObject *obj )
    mCurrentWaterObject = obj;
    mCurrentWaterObject = obj;
 }
 }
 
 
+void ShapeBase::notifyCollisionCallbacks(SceneObject* obj, const VectorF& vel)
+{
+   for (S32 i = 0; i < collision_callbacks.size(); i++)
+      if (collision_callbacks[i])
+         collision_callbacks[i]->collisionNotify(this, obj, vel);
+}
+
+void ShapeBase::registerCollisionCallback(CollisionEventCallback* ce_cb)
+{
+   for (S32 i = 0; i < collision_callbacks.size(); i++)
+      if (collision_callbacks[i] == ce_cb)
+         return;
+
+   collision_callbacks.push_back(ce_cb);
+}
+
+void ShapeBase::unregisterCollisionCallback(CollisionEventCallback* ce_cb)
+{
+   for (S32 i = 0; i < collision_callbacks.size(); i++)
+      if (collision_callbacks[i] == ce_cb)
+      {
+         collision_callbacks.erase(i);
+         return;
+      }
+}
 //--------------------------------------------------------------------------
 //--------------------------------------------------------------------------
 //----------------------------------------------------------------------------
 //----------------------------------------------------------------------------
 DefineEngineMethod( ShapeBase, setHidden, void, ( bool show ),,
 DefineEngineMethod( ShapeBase, setHidden, void, ( bool show ),,
@@ -4945,3 +5143,202 @@ DefineEngineMethod( ShapeBase, getModelFile, const char *, (),,
    const char *fieldName = StringTable->insert( String("shapeFile") );
    const char *fieldName = StringTable->insert( String("shapeFile") );
    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)
+{
+   Parent::setSelectionFlags(flags);
+
+   if (!mShapeInstance || !isClientObject())  
+      return;  
+  
+   if (!mShapeInstance->ownMaterialList())  
+      return;  
+  
+   TSMaterialList* pMatList = mShapeInstance->getMaterialList();  
+   for (S32 j = 0; j < pMatList->size(); j++)   
+   {  
+      BaseMatInstance * bmi = pMatList->getMaterialInst(j);  
+      bmi->setSelectionHighlighting(needsSelectionHighlighting());  
+   }  
+}
+

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

@@ -20,6 +20,10 @@
 // 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 _SHAPEBASE_H_
 #ifndef _SHAPEBASE_H_
 #define _SHAPEBASE_H_
 #define _SHAPEBASE_H_
 
 
@@ -654,6 +658,17 @@ public:
    DECLARE_CALLBACK(void, onEndSequence, (ShapeBase* obj, S32 slot, const char* name));
    DECLARE_CALLBACK(void, onEndSequence, (ShapeBase* obj, S32 slot, const char* name));
    DECLARE_CALLBACK( void, onForceUncloak, ( ShapeBase* obj, const char* reason ) );
    DECLARE_CALLBACK( void, onForceUncloak, ( ShapeBase* obj, const char* reason ) );
    /// @}
    /// @}
+   struct TextureTagRemapping
+   {
+      char* old_tag;
+      char* new_tag;
+   };
+   StringTableEntry remap_txr_tags;
+   char* remap_buffer;
+   Vector<TextureTagRemapping> txr_tag_remappings;
+   bool silent_bbox_check;
+public:
+   ShapeBaseData(const ShapeBaseData&, bool = false);
 };
 };
 
 
 
 
@@ -1845,7 +1860,58 @@ public:
 
 
 protected:
 protected:
    DECLARE_CALLBACK( F32, validateCameraFov, (F32 fov) );
    DECLARE_CALLBACK( F32, validateCameraFov, (F32 fov) );
+public:
+   class CollisionEventCallback
+   {
+   public:
+      virtual void collisionNotify(SceneObject* shape0, SceneObject* shape1, const VectorF& vel)=0;
+   };
+private:
+   Vector<CollisionEventCallback*>  collision_callbacks;
+   void   notifyCollisionCallbacks(SceneObject*, const VectorF& vel);
+public:
+   void   registerCollisionCallback(CollisionEventCallback*);
+   void   unregisterCollisionCallback(CollisionEventCallback*);
 
 
+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);
 };
 };
 
 
 
 

+ 12 - 0
Engine/source/T3D/staticShape.cpp

@@ -20,6 +20,10 @@
 // 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 "core/dnet.h"
 #include "core/dnet.h"
 #include "core/stream/bitStream.h"
 #include "core/stream/bitStream.h"
@@ -97,6 +101,14 @@ StaticShapeData::StaticShapeData()
    noIndividualDamage = false;
    noIndividualDamage = false;
 }
 }
 
 
+StaticShapeData::StaticShapeData(const StaticShapeData& other, bool temp_clone) : ShapeBaseData(other, temp_clone)
+{
+   noIndividualDamage = other.noIndividualDamage;
+   dynamicTypeField = other.dynamicTypeField;
+   isShielded = other.isShielded; // -- uninitialized, unused
+   energyPerDamagePoint = other.energyPerDamagePoint; // -- uninitialized, unused
+}
+
 void StaticShapeData::initPersistFields()
 void StaticShapeData::initPersistFields()
 {
 {
    addField("noIndividualDamage",   TypeBool, Offset(noIndividualDamage,   StaticShapeData), "Deprecated\n\n @internal");
    addField("noIndividualDamage",   TypeBool, Offset(noIndividualDamage,   StaticShapeData), "Deprecated\n\n @internal");

+ 9 - 0
Engine/source/T3D/staticShape.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 _STATICSHAPE_H_
 #ifndef _STATICSHAPE_H_
 #define _STATICSHAPE_H_
 #define _STATICSHAPE_H_
 
 
@@ -38,12 +43,16 @@ struct StaticShapeData: public ShapeBaseData {
    bool  noIndividualDamage;
    bool  noIndividualDamage;
    S32   dynamicTypeField;
    S32   dynamicTypeField;
    bool  isShielded;
    bool  isShielded;
+   F32   energyPerDamagePoint;	// Re-added for AFX
 
 
    //
    //
    DECLARE_CONOBJECT(StaticShapeData);
    DECLARE_CONOBJECT(StaticShapeData);
    static void initPersistFields();
    static void initPersistFields();
    virtual void packData(BitStream* stream);
    virtual void packData(BitStream* stream);
    virtual void unpackData(BitStream* stream);
    virtual void unpackData(BitStream* stream);
+public:
+   StaticShapeData(const StaticShapeData&, bool = false);
+   virtual bool   allowSubstitutions() const { return true; }
 };
 };
 
 
 
 

+ 102 - 0
Engine/source/T3D/tsStatic.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/tsStatic.h"
 #include "T3D/tsStatic.h"
 
 
@@ -54,6 +59,8 @@ using namespace Torque;
 
 
 extern bool gEditingMission;
 extern bool gEditingMission;
 
 
+#include "afx/ce/afxZodiacMgr.h"
+
 IMPLEMENT_CO_NETOBJECT_V1(TSStatic);
 IMPLEMENT_CO_NETOBJECT_V1(TSStatic);
 
 
 ConsoleDocClass( TSStatic,
 ConsoleDocClass( TSStatic,
@@ -124,6 +131,12 @@ TSStatic::TSStatic()
 
 
    mCollisionType = CollisionMesh;
    mCollisionType = CollisionMesh;
    mDecalType = CollisionMesh;
    mDecalType = CollisionMesh;
+
+   mIgnoreZodiacs = false;
+   mHasGradients = false;
+   mInvertGradientRange = false;
+   mGradientRangeUser.set(0.0f, 180.0f);
+   afxZodiacData::convertGradientRangeFromDegrees(mGradientRange, mGradientRangeUser);
 }
 }
 
 
 TSStatic::~TSStatic()
 TSStatic::~TSStatic()
@@ -222,6 +235,12 @@ void TSStatic::initPersistFields()
 
 
    endGroup("Debug");
    endGroup("Debug");
 
 
+   addGroup("AFX");
+   addField("ignoreZodiacs",         TypeBool,       Offset(mIgnoreZodiacs,       TSStatic));
+   addField("useGradientRange",      TypeBool,       Offset(mHasGradients,        TSStatic));
+   addField("gradientRange",         TypePoint2F,    Offset(mGradientRangeUser,   TSStatic));
+   addField("invertGradientRange",   TypeBool,       Offset(mInvertGradientRange, TSStatic));
+   endGroup("AFX");
    Parent::initPersistFields();
    Parent::initPersistFields();
 }
 }
 
 
@@ -323,6 +342,8 @@ bool TSStatic::_createShape()
 {
 {
    // Cleanup before we create.
    // Cleanup before we create.
    mCollisionDetails.clear();
    mCollisionDetails.clear();
+   mDecalDetails.clear();
+   mDecalDetailsPtr = 0;
    mLOSDetails.clear();
    mLOSDetails.clear();
    SAFE_DELETE( mPhysicsRep );
    SAFE_DELETE( mPhysicsRep );
    SAFE_DELETE( mShapeInstance );
    SAFE_DELETE( mShapeInstance );
@@ -354,6 +375,8 @@ bool TSStatic::_createShape()
 
 
    mShapeInstance = new TSShapeInstance( mShape, isClientObject() );
    mShapeInstance = new TSShapeInstance( mShape, isClientObject() );
 
 
+   if (isClientObject())
+      mShapeInstance->cloneMaterialList();
    if( isGhost() )
    if( isGhost() )
    {
    {
       // Reapply the current skin
       // Reapply the current skin
@@ -396,11 +419,29 @@ void TSStatic::prepCollision()
 
 
    // Cleanup any old collision data
    // Cleanup any old collision data
    mCollisionDetails.clear();
    mCollisionDetails.clear();
+   mDecalDetails.clear();
+   mDecalDetailsPtr = 0;
    mLOSDetails.clear();
    mLOSDetails.clear();
    mConvexList->nukeList();
    mConvexList->nukeList();
 
 
    if ( mCollisionType == CollisionMesh || mCollisionType == VisibleMesh )
    if ( mCollisionType == CollisionMesh || mCollisionType == VisibleMesh )
+   {
       mShape->findColDetails( mCollisionType == VisibleMesh, &mCollisionDetails, &mLOSDetails );
       mShape->findColDetails( mCollisionType == VisibleMesh, &mCollisionDetails, &mLOSDetails );
+      if ( mDecalType == mCollisionType )
+      {
+         mDecalDetailsPtr = &mCollisionDetails;
+      }
+      else if ( mDecalType == CollisionMesh || mDecalType == VisibleMesh )
+      {
+         mShape->findColDetails( mDecalType == VisibleMesh, &mDecalDetails, 0 );
+         mDecalDetailsPtr = &mDecalDetails;
+      }
+   }
+   else if ( mDecalType == CollisionMesh || mDecalType == VisibleMesh )
+   {
+      mShape->findColDetails( mDecalType == VisibleMesh, &mDecalDetails, 0 );
+      mDecalDetailsPtr = &mDecalDetails;
+   }
 
 
    _updatePhysics();
    _updatePhysics();
 }
 }
@@ -681,6 +722,8 @@ void TSStatic::prepRenderImage( SceneRenderState* state )
    }
    }
    mShapeInstance->render( rdata );
    mShapeInstance->render( rdata );
 
 
+   if (!mIgnoreZodiacs && mDecalDetailsPtr != 0)
+      afxZodiacMgr::renderPolysoupZodiacs(state, this);
    if ( mRenderNormalScalar > 0 )
    if ( mRenderNormalScalar > 0 )
    {
    {
       ObjectRenderInst *ri = state->getRenderPass()->allocInst<ObjectRenderInst>();
       ObjectRenderInst *ri = state->getRenderPass()->allocInst<ObjectRenderInst>();
@@ -786,6 +829,13 @@ U32 TSStatic::packUpdate(NetConnection *con, U32 mask, BitStream *stream)
       stream->write(mInvertAlphaFade);  
       stream->write(mInvertAlphaFade);  
    } 
    } 
 
 
+   stream->writeFlag(mIgnoreZodiacs);
+   if (stream->writeFlag(mHasGradients))
+   {
+      stream->writeFlag(mInvertGradientRange);
+      stream->write(mGradientRange.x);
+      stream->write(mGradientRange.y);
+   }
    if ( mLightPlugin )
    if ( mLightPlugin )
       retMask |= mLightPlugin->packUpdate(this, AdvancedStaticOptionsMask, con, mask, stream);
       retMask |= mLightPlugin->packUpdate(this, AdvancedStaticOptionsMask, con, mask, stream);
 
 
@@ -870,6 +920,14 @@ void TSStatic::unpackUpdate(NetConnection *con, BitStream *stream)
       stream->read(&mInvertAlphaFade);  
       stream->read(&mInvertAlphaFade);  
    }
    }
 
 
+   mIgnoreZodiacs = stream->readFlag();
+   mHasGradients = stream->readFlag();
+   if (mHasGradients)
+   {
+      mInvertGradientRange = stream->readFlag();
+      stream->read(&mGradientRange.x);
+      stream->read(&mGradientRange.y);
+   }
    if ( mLightPlugin )
    if ( mLightPlugin )
    {
    {
       mLightPlugin->unpackUpdate(this, con, stream);
       mLightPlugin->unpackUpdate(this, con, stream);
@@ -882,6 +940,7 @@ void TSStatic::unpackUpdate(NetConnection *con, BitStream *stream)
 
 
    if ( isProperlyAdded() )
    if ( isProperlyAdded() )
       _updateShouldTick();
       _updateShouldTick();
+   set_special_typing();
 }
 }
 
 
 //----------------------------------------------------------------------------
 //----------------------------------------------------------------------------
@@ -992,6 +1051,11 @@ bool TSStatic::buildPolyList(PolyListContext context, AbstractPolyList* polyList
          polyList->addBox( mObjBox );
          polyList->addBox( mObjBox );
       else if ( meshType == VisibleMesh )
       else if ( meshType == VisibleMesh )
           mShapeInstance->buildPolyList( polyList, 0 );
           mShapeInstance->buildPolyList( polyList, 0 );
+      else if (context == PLC_Decal && mDecalDetailsPtr != 0)
+      {
+         for ( U32 i = 0; i < mDecalDetailsPtr->size(); i++ )
+            mShapeInstance->buildPolyListOpcode( (*mDecalDetailsPtr)[i], polyList, box );
+      }
       else
       else
       {
       {
          // Everything else is done from the collision meshes
          // Everything else is done from the collision meshes
@@ -1324,3 +1388,41 @@ DefineEngineMethod( TSStatic, getModelFile, const char *, (),,
 {
 {
    return object->getShapeFileName();
    return object->getShapeFileName();
 }
 }
+
+void TSStatic::set_special_typing()
+{
+   if (mCollisionType == VisibleMesh || mCollisionType == CollisionMesh)
+      mTypeMask |= InteriorLikeObjectType;
+   else
+      mTypeMask &= ~InteriorLikeObjectType;
+}
+
+void TSStatic::onStaticModified(const char* slotName, const char*newValue)
+{
+   if (slotName == afxZodiacData::GradientRangeSlot)
+   {
+      afxZodiacData::convertGradientRangeFromDegrees(mGradientRange, mGradientRangeUser);
+      return;
+   }
+
+   set_special_typing();
+}
+
+void TSStatic::setSelectionFlags(U8 flags)
+{
+   Parent::setSelectionFlags(flags);
+
+   if (!mShapeInstance || !isClientObject())  
+      return;  
+  
+   if (!mShapeInstance->ownMaterialList())  
+      return;  
+  
+   TSMaterialList* pMatList = mShapeInstance->getMaterialList();  
+   for (S32 j = 0; j < pMatList->size(); j++)   
+   {  
+      BaseMatInstance * bmi = pMatList->getMaterialInst(j);  
+      bmi->setSelectionHighlighting(needsSelectionHighlighting());  
+   }  
+}
+

+ 0 - 1330
Engine/source/T3D/tsStatic.cpp.orig

@@ -1,1330 +0,0 @@
-//-----------------------------------------------------------------------------
-// Copyright (c) 2012 GarageGames, LLC
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to
-// deal in the Software without restriction, including without limitation the
-// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
-// sell copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-// IN THE SOFTWARE.
-//-----------------------------------------------------------------------------
-
-#include "platform/platform.h"
-#include "T3D/tsStatic.h"
-
-#include "core/resourceManager.h"
-#include "core/stream/bitStream.h"
-#include "scene/sceneRenderState.h"
-#include "scene/sceneManager.h"
-#include "scene/sceneObjectLightingPlugin.h"
-#include "lighting/lightManager.h"
-#include "math/mathIO.h"
-#include "ts/tsShapeInstance.h"
-#include "ts/tsMaterialList.h"
-#include "console/consoleTypes.h"
-#include "T3D/shapeBase.h"
-#include "sim/netConnection.h"
-#include "gfx/gfxDevice.h"
-#include "gfx/gfxTransformSaver.h"
-#include "ts/tsRenderState.h"
-#include "collision/boxConvex.h"
-#include "T3D/physics/physicsPlugin.h"
-#include "T3D/physics/physicsBody.h"
-#include "T3D/physics/physicsCollision.h"
-#include "materials/materialDefinition.h"
-#include "materials/materialManager.h"
-#include "materials/matInstance.h"
-#include "materials/materialFeatureData.h"
-#include "materials/materialFeatureTypes.h"
-#include "console/engineAPI.h"
-#include "T3D/accumulationVolume.h"
-
-using namespace Torque;
-
-extern bool gEditingMission;
-
-IMPLEMENT_CO_NETOBJECT_V1(TSStatic);
-
-ConsoleDocClass( TSStatic,
-	"@brief A static object derived from a 3D model file and placed within the game world.\n\n"
-
-	"TSStatic is the most basic 3D shape in Torque.	 Unlike StaticShape it doesn't make use of "
-	"a datablock.	It derrives directly from SceneObject.	 This makes TSStatic extremely light "
-	"weight, which is why the Tools use this class when you want to drop in a DTS or DAE object.\n\n"
-
-	"While a TSStatic doesn't provide any motion -- it stays were you initally put it -- it does allow for "
-	"a single ambient animation sequence to play when the object is first added to the scene.\n\n"
-
-	"@tsexample\n"
-			"new TSStatic(Team1Base) {\n"
-			"	 shapeName = \"art/shapes/desertStructures/station01.dts\";\n"
-			"	 playAmbient = \"1\";\n"
-			"	 receiveSunLight = \"1\";\n"
-			"	 receiveLMLighting = \"1\";\n"
-			"	 useCustomAmbientLighting = \"0\";\n"
-			"	 customAmbientLighting = \"0 0 0 1\";\n"
-			"	 collisionType = \"Visible Mesh\";\n"
-			"	 decalType = \"Collision Mesh\";\n"
-			"	 allowPlayerStep = \"1\";\n"
-			"	 renderNormals = \"0\";\n"
-			"	 forceDetail = \"-1\";\n"
-			"	 position = \"315.18 -180.418 244.313\";\n"
-			"	 rotation = \"0 0 1 195.952\";\n"
-			"	 scale = \"1 1 1\";\n"
-			"	 isRenderEnabled = \"true\";\n"
-			"	 canSaveDynamicFields = \"1\";\n"
-			"};\n"
-	"@endtsexample\n"
-
-	"@ingroup gameObjects\n"
-);
-
-TSStatic::TSStatic()
-:
-	cubeDescId( 0 ),
-	reflectorDesc( NULL )
-{
-	mNetFlags.set(Ghostable | ScopeAlways);
-
-	mTypeMask |= StaticObjectType | StaticShapeObjectType;
-
-	mShapeName			= "";
-	mShapeInstance		= NULL;
-
-	mPlayAmbient		= true;
-	mAmbientThread		= NULL;
-
-	mAllowPlayerStep = false;
-
-	mConvexList = new Convex;
-
-	mRenderNormalScalar = 0;
-	mForceDetail = -1;
-
-	mMeshCulling = false;
-	mUseOriginSort = false;
-
-	mUseAlphaFade		= false;
-	mAlphaFadeStart	= 100.0f;
-	mAlphaFadeEnd		= 150.0f;
-	mInvertAlphaFade	= false;
-	mAlphaFade = 1.0f;
-	mPhysicsRep = NULL;
-
-	mCollisionType = CollisionMesh;
-	mDecalType = CollisionMesh;
-}
-
-TSStatic::~TSStatic()
-{
-	delete mConvexList;
-	mConvexList = NULL;
-}
-
-ImplementEnumType( TSMeshType,
-	"Type of mesh data available in a shape.\n"
-	"@ingroup gameObjects" )
-	{ TSStatic::None,				"None",				"No mesh data." },
-	{ TSStatic::Bounds,			"Bounds",			"Bounding box of the shape." },
-	{ TSStatic::CollisionMesh, "Collision Mesh", "Specifically desingated \"collision\" meshes." },
-	{ TSStatic::VisibleMesh,	"Visible Mesh",	"Rendered mesh polygons." },
-EndImplementEnumType;
-
-
-void TSStatic::initPersistFields()
-{
-	addGroup("Media");
-
-		addField("shapeName",	TypeShapeFilename,  Offset( mShapeName, TSStatic ),
-			"%Path and filename of the model file (.DTS, .DAE) to use for this TSStatic." );
-
-		addProtectedField( "skin", TypeRealString, Offset( mAppliedSkinName, TSStatic ), &_setFieldSkin, &_getFieldSkin,
-		"@brief The skin applied to the shape.\n\n"
-
-		"'Skinning' the shape effectively renames the material targets, allowing "
-		"different materials to be used on different instances of the same model.\n\n"
-
-		"Any material targets that start with the old skin name have that part "
-		"of the name replaced with the new skin name. The initial old skin name is "
-		"\"base\". For example, if a new skin of \"blue\" was applied to a model "
-		"that had material targets <i>base_body</i> and <i>face</i>, the new targets "
-		"would be <i>blue_body</i> and <i>face</i>. Note that <i>face</i> was not "
-		"renamed since it did not start with the old skin name of \"base\".\n\n"
-
-		"To support models that do not use the default \"base\" naming convention, "
-		"you can also specify the part of the name to replace in the skin field "
-		"itself. For example, if a model had a material target called <i>shapemat</i>, "
-		"we could apply a new skin \"shape=blue\", and the material target would be "
-		"renamed to <i>bluemat</i> (note \"shape\" has been replaced with \"blue\").\n\n"
-
-		"Multiple skin updates can also be applied at the same time by separating "
-		"them with a semicolon. For example: \"base=blue;face=happy_face\".\n\n"
-
-		"Material targets are only renamed if an existing Material maps to that "
-		"name, or if there is a diffuse texture in the model folder with the same "
-		"name as the new target.\n\n" );
-
-	endGroup("Media");
-
-	addGroup("Rendering");
-
-		addField( "playAmbient",	TypeBool,	Offset( mPlayAmbient, TSStatic ),
-			"Enables automatic playing of the animation sequence named \"ambient\" (if it exists) when the TSStatic is loaded.");
-		addField( "meshCulling",	TypeBool,	Offset( mMeshCulling, TSStatic ), 
-			"Enables detailed culling of meshes within the TSStatic. Should only be used "
-			"with large complex shapes like buildings which contain many submeshes." );
-		addField( "originSort",		TypeBool,	Offset( mUseOriginSort, TSStatic ), 
-			"Enables translucent sorting of the TSStatic by its origin instead of the bounds." );
-
-	endGroup("Rendering");
-
-	addGroup( "Reflection" );
-		addField( "cubeReflectorDesc", TypeRealString, Offset( cubeDescName, TSStatic ), 
-			"References a ReflectorDesc datablock that defines performance and quality properties for dynamic reflections.\n");
-	endGroup( "Reflection" );
-
-	addGroup("Collision");
-
-		addField( "collisionType",		TypeTSMeshType,	Offset( mCollisionType,	  TSStatic ),
-			"The type of mesh data to use for collision queries." );
-		addField( "decalType",			TypeTSMeshType,	Offset( mDecalType,	 TSStatic ),
-			"The type of mesh data used to clip decal polygons against." );
-		addField( "allowPlayerStep",	TypeBool,			Offset( mAllowPlayerStep, TSStatic ), 
-			"@brief Allow a Player to walk up sloping polygons in the TSStatic (based on the collisionType).\n\n"
-			"When set to false, the slightest bump will stop the player from walking on top of the object.\n");
-	
-	endGroup("Collision");
-
-	addGroup( "AlphaFade" );  
-		addField( "alphaFadeEnable",	 TypeBool,	 Offset(mUseAlphaFade,	  TSStatic), "Turn on/off Alpha Fade" );	
-		addField( "alphaFadeStart",	 TypeF32,	 Offset(mAlphaFadeStart,  TSStatic), "Distance of start Alpha Fade" );	
-		addField( "alphaFadeEnd",		 TypeF32,	 Offset(mAlphaFadeEnd,	  TSStatic), "Distance of end Alpha Fade" );	 
-		addField( "alphaFadeInverse", TypeBool,	 Offset(mInvertAlphaFade, TSStatic), "Invert Alpha Fade's Start & End Distance" );	
-	endGroup( "AlphaFade" );
-
-	addGroup("Debug");
-
-		addField( "renderNormals", TypeF32, Offset( mRenderNormalScalar, TSStatic ),
-			"Debug rendering mode shows the normals for each point in the TSStatic's mesh." );
-		addField( "forceDetail",	TypeS32, Offset( mForceDetail, TSStatic ),
-			"Forces rendering to a particular detail level." );
-
-	endGroup("Debug");
-
-	Parent::initPersistFields();
-}
-
-bool TSStatic::_setFieldSkin( void *object, const char *index, const char *data )
-{
-	TSStatic *ts = static_cast<TSStatic*>( object );
-	if ( ts )
-		ts->setSkinName( data );
-	return false;
-}
-
-const char *TSStatic::_getFieldSkin( void *object, const char *data )
-{
-	TSStatic *ts = static_cast<TSStatic*>( object );
-	return ts ? ts->mSkinNameHandle.getString() : "";
-}
-
-void TSStatic::inspectPostApply()
-{
-	// Apply any transformations set in the editor
-	Parent::inspectPostApply();
-
-	if(isServerObject()) 
-	{
-		setMaskBits(AdvancedStaticOptionsMask);
-		prepCollision();
-	}
-
-	_updateShouldTick();
-}
-
-bool TSStatic::onAdd()
-{
-	PROFILE_SCOPE(TSStatic_onAdd);
-
-	if ( isServerObject() )
-	{
-		// Handle the old "usePolysoup" field
-		SimFieldDictionary* fieldDict = getFieldDictionary();
-
-		if ( fieldDict )
-		{
-			StringTableEntry slotName = StringTable->insert( "usePolysoup" );
-
-			SimFieldDictionary::Entry * entry = fieldDict->findDynamicField( slotName );
-
-			if ( entry )
-			{
-				// Was "usePolysoup" set?
-				bool usePolysoup = dAtob( entry->value );
-
-				// "usePolysoup" maps to the new VisibleMesh type
-				if ( usePolysoup )
-					mCollisionType = VisibleMesh;
-
-				// Remove the field in favor on the new "collisionType" field
-				fieldDict->setFieldValue( slotName, "" );
-			}
-		}
-	}
-
-	if ( !Parent::onAdd() )
-		return false;
-
-	// Setup the shape.
-	if ( !_createShape() )
-	{
-		Con::errorf( "TSStatic::onAdd() - Shape creation failed!" );
-		return false;
-	}
-
-	setRenderTransform(mObjToWorld);
-
-	// Register for the resource change signal.
-	ResourceManager::get().getChangedSignal().notify( this, &TSStatic::_onResourceChanged );
-
-	addToScene();
-
-	if ( isClientObject() )
-	{		 
-		mCubeReflector.unregisterReflector();
-
-		if ( reflectorDesc )
-			mCubeReflector.registerReflector( this, reflectorDesc );		  
-	}
-
-	_updateShouldTick();
-
-	// Accumulation and environment mapping
-	if (isClientObject() && mShapeInstance)
-	{
-		AccumulationVolume::addObject(this);
-	}
-
-	return true;
-}
-
-bool TSStatic::_createShape()
-{
-	// Cleanup before we create.
-	mCollisionDetails.clear();
-	mLOSDetails.clear();
-	SAFE_DELETE( mPhysicsRep );
-	SAFE_DELETE( mShapeInstance );
-	mAmbientThread = NULL;
-	mShape = NULL;
-
-	if (!mShapeName || mShapeName[0] == '\0') 
-	{
-		Con::errorf( "TSStatic::_createShape() - No shape name!" );
-		return false;
-	}
-
-	mShapeHash = _StringTable::hashString(mShapeName);
-
-	mShape = ResourceManager::get().load(mShapeName);
-	if ( bool(mShape) == false )
-	{
-		Con::errorf( "TSStatic::_createShape() - Unable to load shape: %s", mShapeName );
-		return false;
-	}
-
-	if (	isClientObject() && 
-			!mShape->preloadMaterialList(mShape.getPath()) && 
-			NetConnection::filesWereDownloaded() )
-		return false;
-
-	mObjBox = mShape->bounds;
-	resetWorldBox();
-
-	mShapeInstance = new TSShapeInstance( mShape, isClientObject() );
-
-	if( isGhost() )
-	{
-		// Reapply the current skin
-		mAppliedSkinName = "";
-		reSkin();
-	}
-
-	prepCollision();
-
-	// Find the "ambient" animation if it exists
-	S32 ambientSeq = mShape->findSequence("ambient");
-
-	if ( ambientSeq > -1 && !mAmbientThread )
-		mAmbientThread = mShapeInstance->addThread();
-
-	if ( mAmbientThread )
-		mShapeInstance->setSequence( mAmbientThread, ambientSeq, 0);
-
-	// Resolve CubeReflectorDesc.
-	if ( cubeDescName.isNotEmpty() )
-	{
-		Sim::findObject( cubeDescName, reflectorDesc );
-	}
-	else if( cubeDescId > 0 )
-	{
-		Sim::findObject( cubeDescId, reflectorDesc );
-	}
-
-	return true;
-}
-
-void TSStatic::prepCollision()
-{
-	// Let the client know that the collision was updated
-	setMaskBits( UpdateCollisionMask );
-
-	// Allow the ShapeInstance to prep its collision if it hasn't already
-	if ( mShapeInstance )
-		mShapeInstance->prepCollision();
-
-	// Cleanup any old collision data
-	mCollisionDetails.clear();
-	mLOSDetails.clear();
-	mConvexList->nukeList();
-
-	if ( mCollisionType == CollisionMesh || mCollisionType == VisibleMesh )
-		mShape->findColDetails( mCollisionType == VisibleMesh, &mCollisionDetails, &mLOSDetails );
-
-	_updatePhysics();
-}
-
-void TSStatic::_updatePhysics()
-{
-	SAFE_DELETE( mPhysicsRep );
-
-	if ( !PHYSICSMGR || mCollisionType == None )
-		return;
-
-	PhysicsCollision *colShape = NULL;
-	if ( mCollisionType == Bounds )
-	{
-		MatrixF offset( true );
-		offset.setPosition( mShape->center );
-		colShape = PHYSICSMGR->createCollision();
-		colShape->addBox( getObjBox().getExtents() * 0.5f * mObjScale, offset );			
-	}
-	else
-		colShape = mShape->buildColShape( mCollisionType == VisibleMesh, getScale() );
-
-	if ( colShape )
-	{
-		PhysicsWorld *world = PHYSICSMGR->getWorld( isServerObject() ? "server" : "client" );
-		mPhysicsRep = PHYSICSMGR->createBody();
-		mPhysicsRep->init( colShape, 0, 0, this, world );
-		mPhysicsRep->setTransform( getTransform() );
-	}
-}
-
-void TSStatic::onRemove()
-{
-	SAFE_DELETE( mPhysicsRep );
-
-	// Accumulation
-	if ( isClientObject() && mShapeInstance )
-	{
-		if ( mShapeInstance->hasAccumulation() ) 
-			AccumulationVolume::removeObject(this);
-	}
-
-	mConvexList->nukeList();
-
-	removeFromScene();
-
-	// Remove the resource change signal.
-	ResourceManager::get().getChangedSignal().remove( this, &TSStatic::_onResourceChanged );
-
-	delete mShapeInstance;
-	mShapeInstance = NULL;
-
-	mAmbientThread = NULL;
-	if ( isClientObject() )
-		 mCubeReflector.unregisterReflector();
-
-	Parent::onRemove();
-}
-
-void TSStatic::_onResourceChanged( const Torque::Path &path )
-{
-	if ( path != Path( mShapeName ) )
-		return;
-	
-	_createShape();
-	_updateShouldTick();
-}
-
-void TSStatic::setSkinName( const char *name )
-{
-	if ( !isGhost() )
-	{
-		if ( name[0] != '\0' )
-		{
-			// Use tags for better network performance
-			// Should be a tag, but we'll convert to one if it isn't.
-			if ( name[0] == StringTagPrefixByte )
-				mSkinNameHandle = NetStringHandle( U32(dAtoi(name + 1)) );
-			else
-				mSkinNameHandle = NetStringHandle( name );
-		}
-		else
-			mSkinNameHandle = NetStringHandle();
-
-		setMaskBits( SkinMask );
-	}
-}
-
-void TSStatic::reSkin()
-{
-	if ( isGhost() && mShapeInstance && mSkinNameHandle.isValidString() )
-	{
-		Vector<String> skins;
-		String(mSkinNameHandle.getString()).split( ";", skins );
-
-		for (S32 i = 0; i < skins.size(); i++)
-		{
-			String oldSkin( mAppliedSkinName.c_str() );
-			String newSkin( skins[i] );
-
-			// Check if the skin handle contains an explicit "old" base string. This
-			// allows all models to support skinning, even if they don't follow the 
-			// "base_xxx" material naming convention.
-			S32 split = newSkin.find( '=' );		// "old=new" format skin?
-			if ( split != String::NPos )
-			{
-				oldSkin = newSkin.substr( 0, split );
-				newSkin = newSkin.erase( 0, split+1 );
-			}
-
-			mShapeInstance->reSkin( newSkin, oldSkin );
-			mAppliedSkinName = newSkin;
-		}
-	}
-}
-
-void TSStatic::processTick( const Move *move )
-{
-	if ( isServerObject() && mPlayAmbient && mAmbientThread )
-		mShapeInstance->advanceTime( TickSec, mAmbientThread );
-
-	if ( isMounted() )
-	{
-		MatrixF mat( true );
-		mMount.object->getMountTransform(mMount.node, mMount.xfm, &mat );
-		setTransform( mat );
-	}
-}
-
-void TSStatic::interpolateTick( F32 delta )
-{
-}
-
-void TSStatic::advanceTime( F32 dt )
-{
-	if ( mPlayAmbient && mAmbientThread )
-		mShapeInstance->advanceTime( dt, mAmbientThread );
-
-	if ( isMounted() )
-	{
-		MatrixF mat( true );
-		mMount.object->getRenderMountTransform( dt, mMount.node, mMount.xfm, &mat );
-		setRenderTransform( mat );
-	}
-}
-
-void TSStatic::_updateShouldTick()
-{
-	bool shouldTick = (mPlayAmbient && mAmbientThread) || isMounted();
-
-	if ( isTicking() != shouldTick )
-		setProcessTick( shouldTick );
-}
-
-void TSStatic::prepRenderImage( SceneRenderState* state )
-{
-	if( !mShapeInstance )
-		return;
-
-	Point3F cameraOffset;
-	getRenderTransform().getColumn(3,&cameraOffset);
-	cameraOffset -= state->getDiffuseCameraPosition();
-	F32 dist = cameraOffset.len();
-	if (dist < 0.01f)
-		dist = 0.01f;
-
-	if (mUseAlphaFade)
-	{
-		mAlphaFade = 1.0f;
-		if ((mAlphaFadeStart < mAlphaFadeEnd) && mAlphaFadeStart > 0.1f)
-		{
-			if (mInvertAlphaFade)
-			{
-				if (dist <= mAlphaFadeStart)
-				{
-					return;
-				}
-				if (dist < mAlphaFadeEnd)
-				{
-					mAlphaFade = ((dist - mAlphaFadeStart) / (mAlphaFadeEnd - mAlphaFadeStart));
-				}
-			}
-			else
-			{
-				if (dist >= mAlphaFadeEnd)
-				{
-					return;
-				}
-				if (dist > mAlphaFadeStart)
-				{
-					mAlphaFade -= ((dist - mAlphaFadeStart) / (mAlphaFadeEnd - mAlphaFadeStart));
-				}
-			}
-		}
-	}
-
-	F32 invScale = (1.0f/getMax(getMax(mObjScale.x,mObjScale.y),mObjScale.z));	  
-
-	// If we're currently rendering our own reflection we
-	// don't want to render ourselves into it.
-	if ( mCubeReflector.isRendering() )
-		return;
-
-
-	if ( mForceDetail == -1 )
-		mShapeInstance->setDetailFromDistance( state, dist * invScale );
-	else
-		mShapeInstance->setCurrentDetail( mForceDetail );
-
-	if ( mShapeInstance->getCurrentDetail() < 0 )
-		return;
-
-	GFXTransformSaver saver;
-	
-	// Set up our TS render state.
-	TSRenderState rdata;
-	rdata.setSceneState( state );
-	rdata.setFadeOverride( 1.0f );
-	rdata.setOriginSort( mUseOriginSort );
-
-	if ( mCubeReflector.isEnabled() )
-		rdata.setCubemap( mCubeReflector.getCubemap() );
-
-	// Acculumation
-	rdata.setAccuTex(mAccuTex);
-
-	// If we have submesh culling enabled then prepare
-	// the object space frustum to pass to the shape.
-	Frustum culler;
-	if ( mMeshCulling )
-	{
-		culler = state->getCullingFrustum();
-		MatrixF xfm( true );
-		xfm.scale( Point3F::One / getScale() );
-		xfm.mul( getRenderWorldTransform() );
-		xfm.mul( culler.getTransform() );
-		culler.setTransform( xfm );
-		rdata.setCuller( &culler );
-	}
-
-	// We might have some forward lit materials
-	// so pass down a query to gather lights.
-	LightQuery query;
-	query.init( getWorldSphere() );
-	rdata.setLightQuery( &query );
-
-	MatrixF mat = getRenderTransform();
-	mat.scale( mObjScale );
-	GFX->setWorldMatrix( mat );
-
-	if ( state->isDiffusePass() && mCubeReflector.isEnabled() && mCubeReflector.getOcclusionQuery() )
-	{
-		 RenderPassManager *pass = state->getRenderPass();
-		 OccluderRenderInst *ri = pass->allocInst<OccluderRenderInst>();	
-		 
-		 ri->type = RenderPassManager::RIT_Occluder;
-		 ri->query = mCubeReflector.getOcclusionQuery();
-		 mObjToWorld.mulP( mObjBox.getCenter(), &ri->position );
-		 ri->scale.set( mObjBox.getExtents() );
-		 ri->orientation = pass->allocUniqueXform( mObjToWorld ); 
-		 ri->isSphere = false;
-		 state->getRenderPass()->addInst( ri );
-	}
-
-	mShapeInstance->animate();
-	if(mShapeInstance)
-	{
-		if (mUseAlphaFade)
-		{
-			mShapeInstance->setAlphaAlways(mAlphaFade);
-			S32 s = mShapeInstance->mMeshObjects.size();
-			
-			for(S32 x = 0; x < s; x++)
-			{
-				mShapeInstance->mMeshObjects[x].visible = mAlphaFade;
-			}
-		}
-	}
-	mShapeInstance->render( rdata );
-
-	if ( mRenderNormalScalar > 0 )
-	{
-		ObjectRenderInst *ri = state->getRenderPass()->allocInst<ObjectRenderInst>();
-		ri->renderDelegate.bind( this, &TSStatic::_renderNormals );
-		ri->type = RenderPassManager::RIT_Editor;
-		state->getRenderPass()->addInst( ri );
-	}
-}
-
-void TSStatic::_renderNormals( ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance *overrideMat )
-{
-	PROFILE_SCOPE( TSStatic_RenderNormals );
-
-	GFXTransformSaver saver;
-
-	MatrixF mat = getRenderTransform();
-	mat.scale( mObjScale );
-	GFX->multWorld( mat );
-
-	S32 dl = mShapeInstance->getCurrentDetail();
-	mShapeInstance->renderDebugNormals( mRenderNormalScalar, dl );
-}
-
-void TSStatic::onScaleChanged()
-{
-	Parent::onScaleChanged();
-
-	if ( mPhysicsRep )
-	{
-		// If the editor is enabled delay the scale operation
-		// by a few milliseconds so that we're not rebuilding
-		// during an active scale drag operation.
-		if ( gEditingMission )
-			mPhysicsRep->queueCallback( 500, Delegate<void()>( this, &TSStatic::_updatePhysics ) );
-		else
-			_updatePhysics();
-	}
-
-	setMaskBits( ScaleMask );
-}
-
-void TSStatic::setTransform(const MatrixF & mat)
-{
-	Parent::setTransform(mat);
-	if ( !isMounted() )
-		setMaskBits( TransformMask );
-
-	if ( mPhysicsRep )
-		mPhysicsRep->setTransform( mat );
-
-	// Accumulation
-	if ( isClientObject() && mShapeInstance )
-	{
-		if ( mShapeInstance->hasAccumulation() ) 
-			AccumulationVolume::updateObject(this);
-	}
-
-	// Since this is a static it's render transform changes 1
-	// to 1 with it's collision transform... no interpolation.
-	setRenderTransform(mat);
-}
-
-U32 TSStatic::packUpdate(NetConnection *con, U32 mask, BitStream *stream)
-{
-	U32 retMask = Parent::packUpdate(con, mask, stream);
-
-	if ( stream->writeFlag( mask & TransformMask ) )  
-		mathWrite( *stream, getTransform() );
-
-	if ( stream->writeFlag( mask & ScaleMask ) )	 
-	{
-		// Only write one bit if the scale is one.
-		if ( stream->writeFlag( mObjScale != Point3F::One ) )
-			mathWrite( *stream, mObjScale );	  
-	}
-
-	if ( stream->writeFlag( mask & UpdateCollisionMask ) )
-		stream->write( (U32)mCollisionType );
-
-	if ( stream->writeFlag( mask & SkinMask ) )
-		con->packNetStringHandleU( stream, mSkinNameHandle );
-
-	if (stream->writeFlag(mask & AdvancedStaticOptionsMask))
-	{
-		stream->writeString(mShapeName);
-		stream->write((U32)mDecalType);
-
-		stream->writeFlag(mAllowPlayerStep);
-		stream->writeFlag(mMeshCulling);
-		stream->writeFlag(mUseOriginSort);
-
-		stream->write(mRenderNormalScalar);
-
-		stream->write(mForceDetail);
-
-		stream->writeFlag(mPlayAmbient);
-	}
-
-	if ( stream->writeFlag(mUseAlphaFade) )  
-	{	
-		stream->write(mAlphaFadeStart);	
-		stream->write(mAlphaFadeEnd);	 
-		stream->write(mInvertAlphaFade);	 
-	} 
-
-	if ( mLightPlugin )
-		retMask |= mLightPlugin->packUpdate(this, AdvancedStaticOptionsMask, con, mask, stream);
-
-	if( stream->writeFlag( reflectorDesc != NULL ) )
-	{
-		stream->writeRangedU32( reflectorDesc->getId(), DataBlockObjectIdFirst,	 DataBlockObjectIdLast );
-	}
-	return retMask;
-}
-
-void TSStatic::unpackUpdate(NetConnection *con, BitStream *stream)
-{
-	Parent::unpackUpdate(con, stream);
-
-	if ( stream->readFlag() ) // TransformMask
-	{
-		MatrixF mat;
-		mathRead( *stream, &mat );
-		setTransform(mat);
-		setRenderTransform(mat);
-	}
-
-	if ( stream->readFlag() ) // ScaleMask
-	{
-		if ( stream->readFlag() )
-		{
-			VectorF scale;
-			mathRead( *stream, &scale );
-			setScale( scale );
-		}
-		else
-			setScale( Point3F::One );
-	}
-
-	if ( stream->readFlag() ) // UpdateCollisionMask
-	{
-		U32 collisionType = CollisionMesh;
-
-		stream->read( &collisionType );
-
-		// Handle it if we have changed CollisionType's
-		if ( (MeshType)collisionType != mCollisionType )
-		{
-			mCollisionType = (MeshType)collisionType;
-
-			if ( isProperlyAdded() && mShapeInstance )
-				prepCollision();
-		}
-	}
-
-	if (stream->readFlag())		// SkinMask
-	{
-		NetStringHandle skinDesiredNameHandle = con->unpackNetStringHandleU(stream);;
-		if (mSkinNameHandle != skinDesiredNameHandle)
-		{
-			mSkinNameHandle = skinDesiredNameHandle;
-			reSkin();
-		}
-	}
-
-	if (stream->readFlag()) // AdvancedStaticOptionsMask
-	{
-		mShapeName = stream->readSTString();
-
-		stream->read((U32*)&mDecalType);
-
-		mAllowPlayerStep = stream->readFlag();
-		mMeshCulling = stream->readFlag();
-		mUseOriginSort = stream->readFlag();
-
-		stream->read(&mRenderNormalScalar);
-
-		stream->read(&mForceDetail);
-		mPlayAmbient = stream->readFlag();
-	}
-
-	mUseAlphaFade = stream->readFlag();	 
-	if (mUseAlphaFade)
-	{
-		stream->read(&mAlphaFadeStart);	
-		stream->read(&mAlphaFadeEnd);	 
-		stream->read(&mInvertAlphaFade);	 
-	}
-
-	if ( mLightPlugin )
-	{
-		mLightPlugin->unpackUpdate(this, con, stream);
-	}
-
-	if( stream->readFlag() )
-	{
-		cubeDescId = stream->readRangedU32( DataBlockObjectIdFirst, DataBlockObjectIdLast );
-	}
-
-	if ( isProperlyAdded() )
-		_updateShouldTick();
-}
-
-//----------------------------------------------------------------------------
-bool TSStatic::castRay(const Point3F &start, const Point3F &end, RayInfo* info)
-{
-	if ( mCollisionType == None )
-		return false;
-
-	if ( !mShapeInstance )
-		return false;
-
-	if ( mCollisionType == Bounds )
-	{
-		F32 fst;
-		if (!mObjBox.collideLine(start, end, &fst, &info->normal))
-			return false;
-
-		info->t = fst;
-		info->object = this;
-		info->point.interpolate( start, end, fst );
-		info->material = NULL;
-		return true;
-	}
-	else
-	{
-		RayInfo shortest = *info;
-		RayInfo localInfo;
-		shortest.t = 1e8f;
-		localInfo.generateTexCoord = info->generateTexCoord;
-
-		for ( U32 i = 0; i < mLOSDetails.size(); i++ )
-		{
-			mShapeInstance->animate( mLOSDetails[i] );
-
-			if ( mShapeInstance->castRayOpcode( mLOSDetails[i], start, end, &localInfo ) )
-			{
-				localInfo.object = this;
-
-				if (localInfo.t < shortest.t)
-					shortest = localInfo;
-			}
-		}
-
-		if (shortest.object == this)
-		{
-			// Copy out the shortest time...
-			*info = shortest;
-			return true;
-		}
-	}
-
-	return false;
-}
-
-bool TSStatic::castRayRendered(const Point3F &start, const Point3F &end, RayInfo *info)
-{
-	if ( !mShapeInstance )
-		return false;
-
-	// Cast the ray against the currently visible detail
-	RayInfo localInfo;
-	bool res = mShapeInstance->castRayOpcode( mShapeInstance->getCurrentDetail(), start, end, &localInfo );
-
-	if ( res )
-	{
-		*info = localInfo;
-		info->object = this;
-		return true;
-	}
-
-	return false;
-}
-
-bool TSStatic::buildPolyList(PolyListContext context, AbstractPolyList* polyList, const Box3F &box, const SphereF &)
-{
-	if ( !mShapeInstance )
-		return false;
-
-	// This is safe to set even if we're not outputing 
-	polyList->setTransform( &mObjToWorld, mObjScale );
-	polyList->setObject( this );
-
-	if ( context == PLC_Export )
-	{
-		// Use highest detail level
-		S32 dl = 0;
-
-		// Try to call on the client so we can export materials
-		if ( isServerObject() && getClientObject() )
-			dynamic_cast<TSStatic*>(getClientObject())->mShapeInstance->buildPolyList( polyList, dl );
-		else
-			 mShapeInstance->buildPolyList( polyList, dl );
-	}
-	else if ( context == PLC_Selection )
-	{
-		// Use the last rendered detail level
-		S32 dl = mShapeInstance->getCurrentDetail();
-		mShapeInstance->buildPolyListOpcode( dl, polyList, box );
-	}
-	else
-	{
-		// Figure out the mesh type we're looking for.
-		MeshType meshType = ( context == PLC_Decal ) ? mDecalType : mCollisionType;
-
-		if ( meshType == None )
-			return false;
-		else if ( meshType == Bounds )
-			polyList->addBox( mObjBox );
-		else if ( meshType == VisibleMesh )
-			 mShapeInstance->buildPolyList( polyList, 0 );
-		else
-		{
-			// Everything else is done from the collision meshes
-			// which may be built from either the visual mesh or
-			// special collision geometry.
-			for ( U32 i = 0; i < mCollisionDetails.size(); i++ )
-				mShapeInstance->buildPolyListOpcode( mCollisionDetails[i], polyList, box );
-		}
-	}
-
-	return true;
-}
-
-void TSStatic::buildConvex(const Box3F& box, Convex* convex)
-{
-	if ( mCollisionType == None )
-		return;
-
-	if ( mShapeInstance == NULL )
-		return;
-
-	// These should really come out of a pool
-	mConvexList->collectGarbage();
-
-	if ( mCollisionType == Bounds )
-	{
-		// Just return a box convex for the entire shape...
-		Convex* cc = 0;
-		CollisionWorkingList& wl = convex->getWorkingList();
-		for (CollisionWorkingList* itr = wl.wLink.mNext; itr != &wl; itr = itr->wLink.mNext)
-		{
-			if (itr->mConvex->getType() == BoxConvexType &&
-				 itr->mConvex->getObject() == this)
-			{
-				cc = itr->mConvex;
-				break;
-			}
-		}
-		if (cc)
-			return;
-
-		// Create a new convex.
-		BoxConvex* cp = new BoxConvex;
-		mConvexList->registerObject(cp);
-		convex->addToWorkingList(cp);
-		cp->init(this);
-
-		mObjBox.getCenter(&cp->mCenter);
-		cp->mSize.x = mObjBox.len_x() / 2.0f;
-		cp->mSize.y = mObjBox.len_y() / 2.0f;
-		cp->mSize.z = mObjBox.len_z() / 2.0f;
-	}
-	else	// CollisionMesh || VisibleMesh
-	{
-		TSStaticPolysoupConvex::smCurObject = this;
-
-		for (U32 i = 0; i < mCollisionDetails.size(); i++)
-			mShapeInstance->buildConvexOpcode( mObjToWorld, mObjScale, mCollisionDetails[i], box, convex, mConvexList );
-
-		TSStaticPolysoupConvex::smCurObject = NULL;
-	}
-}
-
-SceneObject* TSStaticPolysoupConvex::smCurObject = NULL;
-
-TSStaticPolysoupConvex::TSStaticPolysoupConvex()
-:	box( 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f ),
-	normal( 0.0f, 0.0f, 0.0f, 0.0f ),
-	idx( 0 ),
-	mesh( NULL )
-{
-	mType = TSPolysoupConvexType;
-
-	for ( U32 i = 0; i < 4; ++i )
-	{
-		verts[i].set( 0.0f, 0.0f, 0.0f );
-	}
-}
-
-Point3F TSStaticPolysoupConvex::support(const VectorF& vec) const
-{
-	F32 bestDot = mDot( verts[0], vec );
-
-	const Point3F *bestP = &verts[0];
-	for(S32 i=1; i<4; i++)
-	{
-		F32 newD = mDot(verts[i], vec);
-		if(newD > bestDot)
-		{
-			bestDot = newD;
-			bestP = &verts[i];
-		}
-	}
-
-	return *bestP;
-}
-
-Box3F TSStaticPolysoupConvex::getBoundingBox() const
-{
-	Box3F wbox = box;
-	wbox.minExtents.convolve( mObject->getScale() );
-	wbox.maxExtents.convolve( mObject->getScale() );
-	mObject->getTransform().mul(wbox);
-	return wbox;
-}
-
-Box3F TSStaticPolysoupConvex::getBoundingBox(const MatrixF& mat, const Point3F& scale) const
-{
-	AssertISV(false, "TSStaticPolysoupConvex::getBoundingBox(m,p) - Not implemented. -- XEA");
-	return box;
-}
-
-void TSStaticPolysoupConvex::getPolyList(AbstractPolyList *list)
-{
-	// Transform the list into object space and set the pointer to the object
-	MatrixF i( mObject->getTransform() );
-	Point3F iS( mObject->getScale() );
-	list->setTransform(&i, iS);
-	list->setObject(mObject);
-
-	// Add only the original collision triangle
-	S32 base =	list->addPoint(verts[0]);
-					list->addPoint(verts[2]);
-					list->addPoint(verts[1]);
-
-	list->begin(0, (U32)idx ^ (uintptr_t)mesh);
-	list->vertex(base + 2);
-	list->vertex(base + 1);
-	list->vertex(base + 0);
-	list->plane(base + 0, base + 1, base + 2);
-	list->end();
-}
-
-void TSStaticPolysoupConvex::getFeatures(const MatrixF& mat,const VectorF& n, ConvexFeature* cf)
-{
-	cf->material = 0;
-	cf->object = mObject;
-
-	// For a tetrahedron this is pretty easy... first
-	// convert everything into world space.
-	Point3F tverts[4];
-	mat.mulP(verts[0], &tverts[0]);
-	mat.mulP(verts[1], &tverts[1]);
-	mat.mulP(verts[2], &tverts[2]);
-	mat.mulP(verts[3], &tverts[3]);
-
-	// points...
-	S32 firstVert = cf->mVertexList.size();
-	cf->mVertexList.increment(); cf->mVertexList.last() = tverts[0];
-	cf->mVertexList.increment(); cf->mVertexList.last() = tverts[1];
-	cf->mVertexList.increment(); cf->mVertexList.last() = tverts[2];
-	cf->mVertexList.increment(); cf->mVertexList.last() = tverts[3];
-
-	//		edges...
-	cf->mEdgeList.increment();
-	cf->mEdgeList.last().vertex[0] = firstVert+0;
-	cf->mEdgeList.last().vertex[1] = firstVert+1;
-
-	cf->mEdgeList.increment();
-	cf->mEdgeList.last().vertex[0] = firstVert+1;
-	cf->mEdgeList.last().vertex[1] = firstVert+2;
-
-	cf->mEdgeList.increment();
-	cf->mEdgeList.last().vertex[0] = firstVert+2;
-	cf->mEdgeList.last().vertex[1] = firstVert+0;
-
-	cf->mEdgeList.increment();
-	cf->mEdgeList.last().vertex[0] = firstVert+3;
-	cf->mEdgeList.last().vertex[1] = firstVert+0;
-
-	cf->mEdgeList.increment();
-	cf->mEdgeList.last().vertex[0] = firstVert+3;
-	cf->mEdgeList.last().vertex[1] = firstVert+1;
-
-	cf->mEdgeList.increment();
-	cf->mEdgeList.last().vertex[0] = firstVert+3;
-	cf->mEdgeList.last().vertex[1] = firstVert+2;
-
-	//		triangles...
-	cf->mFaceList.increment();
-	cf->mFaceList.last().normal = PlaneF(tverts[2], tverts[1], tverts[0]);
-	cf->mFaceList.last().vertex[0] = firstVert+2;
-	cf->mFaceList.last().vertex[1] = firstVert+1;
-	cf->mFaceList.last().vertex[2] = firstVert+0;
-
-	cf->mFaceList.increment();
-	cf->mFaceList.last().normal = PlaneF(tverts[1], tverts[0], tverts[3]);
-	cf->mFaceList.last().vertex[0] = firstVert+1;
-	cf->mFaceList.last().vertex[1] = firstVert+0;
-	cf->mFaceList.last().vertex[2] = firstVert+3;
-
-	cf->mFaceList.increment();
-	cf->mFaceList.last().normal = PlaneF(tverts[2], tverts[1], tverts[3]);
-	cf->mFaceList.last().vertex[0] = firstVert+2;
-	cf->mFaceList.last().vertex[1] = firstVert+1;
-	cf->mFaceList.last().vertex[2] = firstVert+3;
-
-	cf->mFaceList.increment();
-	cf->mFaceList.last().normal = PlaneF(tverts[0], tverts[2], tverts[3]);
-	cf->mFaceList.last().vertex[0] = firstVert+0;
-	cf->mFaceList.last().vertex[1] = firstVert+2;
-	cf->mFaceList.last().vertex[2] = firstVert+3;
-
-	// All done!
-}
-
-void TSStatic::onMount( SceneObject *obj, S32 node )
-{
-	Parent::onMount(obj, node);
-	_updateShouldTick();
-}
-
-void TSStatic::onUnmount( SceneObject *obj, S32 node )
-{
-	Parent::onUnmount( obj, node );
-	setMaskBits( TransformMask );
-	_updateShouldTick();
-}
-
-//------------------------------------------------------------------------
-//These functions are duplicated in tsStatic and shapeBase.
-//They each function a little differently; but achieve the same purpose of gathering
-//target names/counts without polluting simObject.
-
-DefineEngineMethod( TSStatic, getTargetName, const char*, ( S32 index ),(0),
-	"Get the name of the indexed shape material.\n"
-	"@param index index of the material to get (valid range is 0 - getTargetCount()-1).\n"
-	"@return the name of the indexed material.\n"
-	"@see getTargetCount()\n")
-{
-	TSStatic *obj = dynamic_cast< TSStatic* > ( object );
-	if(obj)
-	{
-		// Try to use the client object (so we get the reskinned targets in the Material Editor)
-		if ((TSStatic*)obj->getClientObject())
-			obj = (TSStatic*)obj->getClientObject();
-
-		return obj->getShapeInstance()->getTargetName(index);
-	}
-
-	return "";
-}
-
-DefineEngineMethod( TSStatic, getTargetCount, S32,(),,
-	"Get the number of materials in the shape.\n"
-	"@return the number of materials in the shape.\n"
-	"@see getTargetName()\n")
-{
-	TSStatic *obj = dynamic_cast< TSStatic* > ( object );
-	if(obj)
-	{
-		// Try to use the client object (so we get the reskinned targets in the Material Editor)
-		if ((TSStatic*)obj->getClientObject())
-			obj = (TSStatic*)obj->getClientObject();
-
-		return obj->getShapeInstance()->getTargetCount();
-	}
-
-	return -1;
-}
-
-// This method is able to change materials per map to with others. The material that is being replaced is being mapped to
-// unmapped_mat as a part of this transition
-
-DefineEngineMethod( TSStatic, changeMaterial, void, ( const char* mapTo, Material* oldMat, Material* newMat ),("",nullAsType<Material*>(),nullAsType<Material*>()),
-	"@brief Change one of the materials on the shape.\n\n"
-
-	"This method changes materials per mapTo with others. The material that "
-	"is being replaced is mapped to unmapped_mat as a part of this transition.\n"
-
-	"@note Warning, right now this only sort of works. It doesn't do a live "
-	"update like it should.\n"
-
-	"@param mapTo the name of the material target to remap (from getTargetName)\n"
-	"@param oldMat the old Material that was mapped \n"
-	"@param newMat the new Material to map\n\n"
-
-	"@tsexample\n"
-		"// remap the first material in the shape\n"
-		"%mapTo = %obj.getTargetName( 0 );\n"
-		"%obj.changeMaterial( %mapTo, 0, MyMaterial );\n"
-	"@endtsexample\n" )
-{
-	// if no valid new material, theres no reason for doing this
-	if( !newMat )
-	{
-		Con::errorf("TSShape::changeMaterial failed: New material does not exist!");
-		return;
-	}
-
-	TSMaterialList* shapeMaterialList = object->getShape()->materialList;
-
-	// Check the mapTo name exists for this shape
-	S32 matIndex = shapeMaterialList->getMaterialNameList().find_next(String(mapTo));
-	if (matIndex < 0)
-	{
-		Con::errorf("TSShape::changeMaterial failed: Invalid mapTo name '%s'", mapTo);
-		return;
-	}
-
-	// Lets remap the old material off, so as to let room for our current material room to claim its spot
-	if( oldMat )
-		oldMat->mMapTo = String("unmapped_mat");
-
-	newMat->mMapTo = mapTo;
-
-	// Map the material by name in the matmgr
-	MATMGR->mapMaterial( mapTo, newMat->getName() );
-
-	// Replace instances with the new material being traded in. Lets make sure that we only
-	// target the specific targets per inst, this is actually doing more than we thought
-	delete shapeMaterialList->mMatInstList[matIndex];
-	shapeMaterialList->mMatInstList[matIndex] = newMat->createMatInstance();
-
-	// Finish up preparing the material instances for rendering
-	const GFXVertexFormat *flags = getGFXVertexFormat<GFXVertexPNTTB>();
-	FeatureSet features = MATMGR->getDefaultFeatures();
-	shapeMaterialList->getMaterialInst(matIndex)->init(features, flags);
-}
-
-DefineEngineMethod( TSStatic, getModelFile, const char *, (),,
-	"@brief Get the model filename used by this shape.\n\n"
-
-	"@return the shape filename\n\n"
-	"@tsexample\n"
-		"// Acquire the model filename used on this shape.\n"
-		"%modelFilename = %obj.getModelFile();\n"
-	"@endtsexample\n"
-	)
-{
-<<<<<<< HEAD
-	return object->getShapeFileName();
-=======
-	return object->getShapeFileName();
->>>>>>> garagegames/development
-}

+ 19 - 0
Engine/source/T3D/tsStatic.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 _TSSTATIC_H_
 #ifndef _TSSTATIC_H_
 #define _TSSTATIC_H_
 #define _TSSTATIC_H_
 
 
@@ -236,6 +241,20 @@ public:
 
 
    const Vector<S32>& getLOSDetails() const { return mLOSDetails; }
    const Vector<S32>& getLOSDetails() const { return mLOSDetails; }
 
 
+private:
+   virtual void   onStaticModified(const char* slotName, const char*newValue = NULL);
+protected:
+   Vector<S32>    mDecalDetails;
+   Vector<S32>*   mDecalDetailsPtr;
+public:
+   bool           mIgnoreZodiacs;
+   bool           mHasGradients;
+   bool           mInvertGradientRange;
+   Point2F        mGradientRangeUser;
+   Point2F        mGradientRange;
+private:
+   void           set_special_typing();
+   virtual void setSelectionFlags(U8 flags);
 };
 };
 
 
 typedef TSStatic::MeshType TSMeshType;
 typedef TSStatic::MeshType TSMeshType;

+ 20 - 0
Engine/source/T3D/turret/aiTurretShape.h

@@ -151,16 +151,36 @@ public:
 
 
 //----------------------------------------------------------------------------
 //----------------------------------------------------------------------------
 
 
+// As shipped, AITurretShape plus the chain of classes it inherits from, consumes
+// all 32 mask-bits. AFX uses one additional mask-bit in GameBase, which pushes
+// AITurretShape over the mask-bit limit which will cause runtime crashes. As 
+// a workaround, AFX modifies AITurretShape so that it reuses the TurretUpdateMask
+// defined by TurretShape rather than adding a unique TurretStateMask. This will
+// make AITurretShape's network updates slightly less efficient, but should be 
+// acceptable for most uses of AITurretShape. If you plan to populate your levels
+// with many AITurretShape objects, consider restoring it to use of a unique
+// bit-mask, but if you do that, you will have to eliminate at use of at least one
+// bit by one of it's parent classes. (FYI ShapeBase uses 20 bits.)
+//
+// Comment out this define if you want AITurretShape to define it's own bit-mask.
+#define AFX_REUSE_TURRETSHAPE_MASKBITS
 class AITurretShape: public TurretShape
 class AITurretShape: public TurretShape
 {
 {
    typedef TurretShape Parent;
    typedef TurretShape Parent;
 
 
 protected:
 protected:
 
 
+#ifdef AFX_REUSE_TURRETSHAPE_MASKBITS
+   enum MaskBits {
+      TurretStateMask   = Parent::TurretUpdateMask,
+      NextFreeMask      = Parent::NextFreeMask
+   };
+#else // ORIGINAL CODE
    enum MaskBits {
    enum MaskBits {
       TurretStateMask   = Parent::NextFreeMask,
       TurretStateMask   = Parent::NextFreeMask,
       NextFreeMask      = Parent::NextFreeMask << 1
       NextFreeMask      = Parent::NextFreeMask << 1
    };
    };
+#endif
 
 
    struct TargetInfo
    struct TargetInfo
    {
    {

+ 1189 - 0
Engine/source/afx/afxCamera.cpp

@@ -0,0 +1,1189 @@
+//-----------------------------------------------------------------------------
+// Copyright (c) 2012 GarageGames, LLC
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//-----------------------------------------------------------------------------
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
+// Copyright (C) 2015 Faust Logic, Inc.
+//
+// afxCamera implements a modified camera for demonstrating a third person camera style
+// which is more common to RPG games than the standard FPS style camera. For the most part,
+// it is a hybrid of the standard TGE camera and the third person mode of the Advanced Camera
+// resource, authored by Thomas "Man of Ice" Lund. This camera implements the bare minimum
+// required for demonstrating an RPG style camera and leaves tons of room for improvement. 
+// It should be replaced with a better camera if possible.
+//
+// Advanced Camera Resource by Thomas "Man of Ice" Lund:
+//   http://www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=5471
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#include "afx/arcaneFX.h"
+
+#include "math/mathUtils.h"
+#include "math/mathIO.h"
+#include "T3D/gameBase/gameConnection.h"
+#include "T3D/camera.h"
+#include "T3D/player.h"
+#include "T3D/sfx/sfx3DWorld.h"
+
+#include "afx/afxCamera.h"
+
+#define MaxPitch      1.3962f
+#define CameraRadius  0.05f;
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxCameraData
+
+IMPLEMENT_CO_DATABLOCK_V1(afxCameraData);
+
+ConsoleDocClass( afxCameraData,
+   "@brief A datablock that describes an afxCamera.\n\n"
+
+   "@ingroup afxMisc\n"
+   "@ingroup AFX\n"
+   "@ingroup Datablocks\n"
+);
+
+U32 afxCameraData::sCameraCollisionMask = TerrainObjectType | InteriorLikeObjectType | TerrainLikeObjectType;
+
+void afxCameraData::initPersistFields()
+{
+  Con::addVariable("pref::afxCamera::collisionMask", TypeS32, &sCameraCollisionMask);
+
+  Parent::initPersistFields();
+}
+
+void afxCameraData::packData(BitStream* stream)
+{
+  Parent::packData(stream);
+}
+
+void afxCameraData::unpackData(BitStream* stream)
+{
+  Parent::unpackData(stream);
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxCamera
+
+IMPLEMENT_CO_NETOBJECT_V1(afxCamera);
+
+ConsoleDocClass( afxCamera,
+   "@brief A 3rd person camera object.\n\n"
+
+   "@ingroup afxMisc\n"
+   "@ingroup AFX\n"
+);
+
+afxCamera::afxCamera()
+{
+  mNetFlags.clear(Ghostable);
+  mTypeMask |= CameraObjectType;
+  delta.pos = Point3F(0,0,100);
+  delta.rot = Point3F(0,0,0);
+  delta.posVec = delta.rotVec = VectorF(0,0,0);
+  mObjToWorld.setColumn(3,delta.pos);
+  mRot = delta.rot;
+  
+  mMinOrbitDist = 0;
+  mMaxOrbitDist = 0;
+  mCurOrbitDist = 0;
+  mOrbitObject = NULL;
+  mPosition.set(0.f, 0.f, 0.f);
+  mObservingClientObject = false;
+  mode = FlyMode;
+  
+  cam_subject = NULL;
+  coi_offset.set(0, 0, 2);
+  cam_offset.set(0, 0, 0);
+  cam_distance = 0.0f;
+  cam_angle = 0.0f;
+  cam_dirty = false;
+      
+  flymode_saved = false;
+  third_person_snap_s = 1;
+  third_person_snap_c = 1;
+  flymode_saved_pos.zero();
+
+  mDamageState = Disabled;
+}
+
+afxCamera::~afxCamera()
+{
+}
+
+//----------------------------------------------------------------------------
+
+void afxCamera::cam_update(F32 dt, bool on_server) 
+{
+  if (mode == ThirdPersonMode && cam_subject)
+    cam_update_3pov(dt, on_server);
+}
+
+void afxCamera::set_cam_pos(const Point3F& pos,const Point3F& rot)
+{
+   MatrixF xRot, zRot;
+   xRot.set(EulerF(rot.x, 0, 0));
+   zRot.set(EulerF(0, 0, rot.z));
+   MatrixF temp;
+   temp.mul(zRot, xRot);
+   temp.setColumn(3, pos);
+   Parent::setTransform(temp);
+   mRot = rot;
+}
+
+
+//----------------------------------------------------------------------------
+
+
+
+//----------------------------------------------------------------------------
+
+Point3F &afxCamera::getPosition()
+{
+   static Point3F position;
+   mObjToWorld.getColumn(3, &position);
+   return position;
+}
+
+//----------------------------------------------------------------------------
+
+
+//----------------------------------------------------------------------------
+//----------------------------------------------------------------------------
+//    NEW Observer Code
+//----------------------------------------------------------------------------
+//----------------------------------------------------------------------------
+void afxCamera::setFlyMode()
+{
+  mode = FlyMode;
+  if (flymode_saved)
+    snapToPosition(flymode_saved_pos);
+  
+  if (bool(mOrbitObject)) 
+  {
+    clearProcessAfter();
+    clearNotify(mOrbitObject);
+  }
+  mOrbitObject = NULL;
+}
+
+void afxCamera::setOrbitMode(GameBase *obj, Point3F &pos, AngAxisF &rot, F32 minDist, F32 maxDist, F32 curDist, bool ownClientObject)
+{
+   mObservingClientObject = ownClientObject;
+
+   if(bool(mOrbitObject)) {
+      clearProcessAfter();
+      clearNotify(mOrbitObject);
+   }
+   mOrbitObject = obj;
+   if(bool(mOrbitObject))
+   {
+      processAfter(mOrbitObject);
+      deleteNotify(mOrbitObject);
+      mOrbitObject->getWorldBox().getCenter(&mPosition);
+      mode = OrbitObjectMode;
+   }
+   else
+   {
+      mode = OrbitPointMode;
+      mPosition = pos;
+   }
+
+   QuatF q(rot);
+   MatrixF tempMat(true);
+   q.setMatrix(&tempMat);
+   Point3F dir;
+   tempMat.getColumn(1, &dir);
+
+   set_cam_pos(mPosition, dir);
+
+   mMinOrbitDist = minDist;
+   mMaxOrbitDist = maxDist;
+   mCurOrbitDist = curDist;
+}
+
+
+void afxCamera::validateEyePoint(F32 pos, MatrixF *mat)
+{
+   if (pos != 0) {
+      // Use the eye transform to orient the camera
+      Point3F dir;
+      mat->getColumn(1, &dir);
+      pos *= mMaxOrbitDist - mMinOrbitDist;
+      // Use the camera node's pos.
+      Point3F startPos;
+      Point3F endPos;
+      mObjToWorld.getColumn(3,&startPos);
+
+      // Make sure we don't extend the camera into anything solid
+      if(mOrbitObject)
+         mOrbitObject->disableCollision();
+      disableCollision();
+      RayInfo collision;
+
+      SceneContainer* pContainer = isServerObject() ? &gServerContainer : &gClientContainer;
+      if (!pContainer->castRay(startPos, startPos - dir * 2.5 * pos, afxCameraData::sCameraCollisionMask, &collision))
+         endPos = startPos - dir * pos;
+      else
+      {
+         float dot = mDot(dir, collision.normal);
+         if(dot > 0.01)
+         {
+            float colDist = mDot(startPos - collision.point, dir) - (1 / dot) * CameraRadius;
+            if(colDist > pos)
+               colDist = pos;
+            if(colDist < 0)
+               colDist = 0;
+            endPos = startPos - dir * colDist;
+         }
+         else
+            endPos = startPos - dir * pos;
+      }
+      mat->setColumn(3,endPos);
+      enableCollision();
+      if(mOrbitObject)
+         mOrbitObject->enableCollision();
+   }
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+
+
+
+
+// Sets the position and calculates rotation
+void afxCamera::snapToPosition(const Point3F& tPos) 
+{
+  MatrixF transMat;
+
+  if (cam_subject) 
+  {
+    // get the subject's transform
+    MatrixF objToWorld = cam_subject->getRenderTransform();
+
+    // transform the center-of-interest to world-space
+    Point3F objPos;
+    objToWorld.mulP(coi_offset, &objPos);
+
+    // find normalized direction vector looking from camera to coi
+    VectorF dirVec = objPos - tPos;
+    dirVec.normalize();
+
+    MathUtils::getAnglesFromVector(dirVec, mRot.z, mRot.x);
+    mRot.x = 0 - mRot.x;
+
+    transMat = MathUtils::createOrientFromDir(dirVec);
+  } 
+  else
+  {
+    transMat.identity();
+  }
+
+  transMat.setColumn(3, tPos);
+  Parent::setTransform(transMat);
+}
+
+void afxCamera::setCameraSubject(SceneObject* new_subject)
+{
+  // cleanup any existing chase subject
+  if (cam_subject) 
+  {
+    if (dynamic_cast<GameBase*>(cam_subject))
+      clearProcessAfter();
+    clearNotify(cam_subject);
+  }
+  
+  cam_subject = new_subject;
+  
+  // set associations with new chase subject 
+  if (cam_subject) 
+  {
+    if (dynamic_cast<GameBase*>(cam_subject))
+      processAfter((GameBase*)cam_subject);
+    deleteNotify(cam_subject);
+  }
+
+  mode = (cam_subject) ? ThirdPersonMode : FlyMode;
+  setMaskBits(SubjectMask);
+}
+
+void afxCamera::setThirdPersonOffset(const Point3F& offset) 
+{
+  // new method
+  if (cam_distance > 0.0f)
+  {
+    if (isClientObject())
+    {
+      GameConnection* conn = GameConnection::getConnectionToServer();
+      if (conn)
+      {
+        // this auto switches to/from first person 
+        if (conn->isFirstPerson())
+        {
+          if (cam_distance >= 1.0f)
+            conn->setFirstPerson(false);
+        }
+        else
+        {
+          if (cam_distance < 1.0f)
+            conn->setFirstPerson(true);
+        }
+      }
+    }
+
+    cam_offset = offset;
+    cam_dirty = true;
+
+    return;
+  }
+
+  // old backwards-compatible method
+  if (offset.y != cam_offset.y && isClientObject())
+  {
+    GameConnection* conn = GameConnection::getConnectionToServer();
+    if (conn)
+    {
+      // this auto switches to/from first person 
+      if (conn->isFirstPerson())
+      {
+        if (offset.y <= -1.0f)
+          conn->setFirstPerson(false);
+      }
+      else
+      {
+        if (offset.y > -1.0f)
+          conn->setFirstPerson(true);
+      }
+    }
+  }
+
+  cam_offset = offset;
+  cam_dirty = true;
+}
+
+void afxCamera::setThirdPersonOffset(const Point3F& offset, const Point3F& coi_offset) 
+{
+  this->coi_offset = coi_offset;
+  setThirdPersonOffset(offset);
+}
+
+void afxCamera::setThirdPersonDistance(F32 distance) 
+{
+  cam_distance = distance;
+  cam_dirty = true;
+}
+
+F32 afxCamera::getThirdPersonDistance() 
+{
+  return cam_distance;
+}
+
+void afxCamera::setThirdPersonAngle(F32 angle) 
+{
+  cam_angle = angle;
+  cam_dirty = true;
+}
+
+F32 afxCamera::getThirdPersonAngle() 
+{
+  return cam_angle;
+}
+
+void afxCamera::setThirdPersonMode()
+{
+  mode = ThirdPersonMode;
+  flymode_saved_pos = getPosition();
+  flymode_saved = true;
+  cam_dirty = true;
+  third_person_snap_s++;
+}
+
+void afxCamera::setThirdPersonSnap()
+{
+  if (mode == ThirdPersonMode)
+    third_person_snap_s += 2;
+}
+
+void afxCamera::setThirdPersonSnapClient()
+{
+  if (mode == ThirdPersonMode)
+    third_person_snap_c++;
+}
+
+const char* afxCamera::getMode()
+{
+  switch (mode)
+  {
+  case ThirdPersonMode:
+    return "ThirdPerson";
+  case FlyMode:
+    return "Fly";
+  case OrbitObjectMode:
+    return "Orbit";
+  }
+
+  return "Unknown";
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Console Methods
+
+static char buffer[100];
+
+ConsoleMethod(afxCamera, setOrbitMode, void, 7, 8, 
+  "(GameBase orbitObject, transform mat, float minDistance, float maxDistance, float curDistance, bool ownClientObject)"
+  "Set the camera to orbit around some given object.\n\n"
+  "@param   orbitObject  Object we want to orbit.\n"
+  "@param   mat          A set of fields: posX posY posZ aaX aaY aaZ aaTheta\n"
+  "@param   minDistance  Minimum distance to keep from object.\n"
+  "@param   maxDistance  Maximum distance to keep from object.\n"
+  "@param   curDistance  Distance to set initially from object.\n"
+  "@param   ownClientObj Are we observing an object owned by us?")
+{
+  Point3F pos;
+  AngAxisF aa;
+  F32 minDis, maxDis, curDis;
+  
+  GameBase *orbitObject = NULL;
+  if(Sim::findObject(argv[2],orbitObject) == false)
+  {
+    Con::warnf("Cannot orbit non-existing object.");
+    object->setFlyMode();
+    return;
+  }
+  
+  dSscanf(argv[3],"%f %f %f %f %f %f %f",
+    &pos.x,&pos.y,&pos.z,&aa.axis.x,&aa.axis.y,&aa.axis.z,&aa.angle);
+  minDis = dAtof(argv[4]);
+  maxDis = dAtof(argv[5]);
+  curDis = dAtof(argv[6]);
+  
+  object->setOrbitMode(orbitObject, pos, aa, minDis, maxDis, curDis, (argc == 8) ? dAtob(argv[7]) : false);
+}
+
+ConsoleMethod( afxCamera, setFlyMode, void, 2, 2, "()" "Set the camera to be able to fly freely.")
+{
+  object->setFlyMode();
+}
+
+ConsoleMethod( afxCamera, getPosition, const char *, 2, 2, "()"
+              "Get the position of the camera.\n\n"
+              "@returns A string of form \"x y z\".")
+{ 
+  Point3F& pos = object->getPosition();
+  dSprintf(buffer, sizeof(buffer),"%f %f %f",pos.x,pos.y,pos.z);
+  return buffer;
+}
+
+ConsoleMethod(afxCamera, setCameraSubject, bool, 3, 3, "") 
+{   
+  SceneObject* subject;
+  if (!Sim::findObject(argv[2], subject))
+  {
+    Con::errorf("Camera subject \"%s\" not found.", argv[2].getStringValue());
+    return false;
+  }
+  
+  object->setCameraSubject(subject);
+  
+  return true;
+}
+
+ConsoleMethod(afxCamera, setThirdPersonDistance, bool, 3, 3, "") 
+{   
+  F32 distance; 
+  dSscanf(argv[2], "%f", &distance);
+
+  object->setThirdPersonDistance(distance);
+  
+  return true;
+}
+
+ConsoleMethod(afxCamera, getThirdPersonDistance, F32, 2, 2, "")
+{
+   return object->getThirdPersonDistance();
+}
+
+ConsoleMethod(afxCamera, setThirdPersonAngle, bool, 3, 3, "") 
+{   
+  F32 angle; 
+  dSscanf(argv[2], "%f", &angle);
+
+  object->setThirdPersonAngle(angle);
+  
+  return true;
+}
+
+ConsoleMethod(afxCamera, getThirdPersonAngle, F32, 2, 2, "")
+{
+   return object->getThirdPersonAngle();
+}
+
+ConsoleMethod(afxCamera, setThirdPersonOffset, void, 3, 4, "(Point3F offset [, Point3f coi_offset])") 
+{
+  Point3F offset; 
+  dSscanf(argv[2], "%f %f %f", &offset.x, &offset.y, &offset.z);
+  if (argc > 3)
+  {
+    Point3F coi_offset; 
+    dSscanf(argv[3], "%f %f %f", &coi_offset.x, &coi_offset.y, &coi_offset.z);
+    object->setThirdPersonOffset(offset, coi_offset);
+  }
+  else
+    object->setThirdPersonOffset(offset);
+}
+
+ConsoleMethod(afxCamera, getThirdPersonOffset, const char *, 2, 2, "()")
+{
+  const Point3F& pos = object->getThirdPersonOffset();
+  dSprintf(buffer, sizeof(buffer),"%f %f %f",pos.x,pos.y,pos.z);
+  return buffer;
+}
+
+ConsoleMethod(afxCamera, getThirdPersonCOIOffset, const char *, 2, 2, "()")
+{
+  const Point3F& pos = object->getThirdPersonCOIOffset();
+  dSprintf(buffer, sizeof(buffer),"%f %f %f",pos.x,pos.y,pos.z);
+  return buffer;
+}
+
+ConsoleMethod(afxCamera, setThirdPersonMode, void, 2, 2, "()")
+{
+  object->setThirdPersonMode();
+}
+
+ConsoleMethod(afxCamera, setThirdPersonSnap, void, 2, 2, "()")
+{
+  object->setThirdPersonSnap();
+}
+
+ConsoleMethod(afxCamera, getMode, const char *, 2, 2, "()")
+{
+  return object->getMode();
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+// 3POV SECTION
+
+void afxCamera::cam_update_3pov(F32 dt, bool on_server) 
+{
+  Point3F	goal_pos;
+  Point3F curr_pos = getRenderPosition();
+  MatrixF	xfm = cam_subject->getRenderTransform();
+  Point3F coi = cam_subject->getRenderPosition() + coi_offset;
+
+  // for player subjects, pitch is adjusted
+  Player*	player_subj =	dynamic_cast<Player*>(cam_subject);
+  if (player_subj) 
+  {
+    if (cam_distance > 0.0f)
+    {
+      // rotate xfm by amount of cam_angle
+      F32	look_yaw = player_subj->getHeadRotation().z + mDegToRad(-cam_angle);
+      MatrixF	look_yaw_mtx(EulerF(0,0,look_yaw));
+      xfm.mul(look_yaw_mtx);
+
+      // rotate xfm by amount of head pitch in player
+      F32	head_pitch = player_subj->getHeadRotation().x;
+      MatrixF	head_pitch_mtx(EulerF(head_pitch,0,0));
+      xfm.mul(head_pitch_mtx);
+
+      VectorF	behind_vec(0, -cam_distance, 0);
+      xfm.mulP(behind_vec, &goal_pos);
+      goal_pos += cam_offset;
+    }
+    else // old backwards-compatible method
+    {
+      // rotate xfm by amount of head pitch in player
+      F32	head_pitch = player_subj->getHeadRotation().x;
+      MatrixF	head_pitch_mtx(EulerF(head_pitch,0,0));
+      xfm.mul(head_pitch_mtx);
+
+      VectorF	behind_vec(0, cam_offset.y, 0);
+      xfm.mulP(behind_vec, &goal_pos);
+      goal_pos.z += cam_offset.z;
+    }
+  }
+  // for non-player subjects, camera will follow, but pitch won't adjust.
+  else 
+  {
+    xfm.mulP(cam_offset, &goal_pos);
+  }
+
+  // avoid view occlusion
+  if (avoid_blocked_view(coi, goal_pos, goal_pos) && !on_server)
+  {
+    // snap to final position if path to goal is blocked
+    if (test_blocked_line(curr_pos, goal_pos))
+      third_person_snap_c++;
+  }
+
+  // place camera into its final position	
+
+  // speed factor values
+  //   15 -- tight
+  //   10 -- normal
+  //    5 -- loose
+  //    1 -- very loose
+  F32 speed_factor = 8.0f;
+  F32 time_inc = 1.0f/speed_factor;
+
+  // snap to final position
+  if (on_server || (third_person_snap_c > 0 || dt > time_inc))
+  {
+    snapToPosition(goal_pos);
+    if (!on_server && third_person_snap_c > 0)
+      third_person_snap_c--;
+    return;
+  }
+  // interpolate to final position
+  else
+  {
+    // interpretation: always move a proportion of the distance
+    // from current location to destination that would cover the
+    // entire distance in time_inc duration at constant velocity.
+    F32 t = (dt >= time_inc) ? 1.0f : dt*speed_factor;
+    snapToPosition(goal_pos*t + curr_pos*(1.0-t));
+  }
+}
+
+// See if the camera view is occluded by certain objects, 
+// and move the camera closer to the subject in that case
+bool afxCamera::avoid_blocked_view(const Point3F& startpos, const Point3F& endpos, Point3F& newpos) 
+{ 
+  // cast ray to check for intersection with potential blocker objects
+  RayInfo hit_info;
+  if (!getContainer()->castRay(startpos, endpos, afxCameraData::sCameraCollisionMask, &hit_info)) 
+  {
+    // no hit: just return original endpos
+    newpos = endpos;
+    return false;
+  }
+
+	// did hit: return the hit location nudged forward slightly
+  // to avoid seeing clipped portions of blocking object.
+	Point3F sight_line = startpos - hit_info.point;
+  sight_line.normalize();
+  newpos = hit_info.point + sight_line*0.4f;
+
+  return true;
+}
+
+bool afxCamera::test_blocked_line(const Point3F& startpos, const Point3F& endpos) 
+{ 
+  RayInfo hit_info;
+  return getContainer()->castRay(startpos, endpos, afxCameraData::sCameraCollisionMask, &hit_info);
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+// STD OVERRIDES SECTION
+
+bool afxCamera::onAdd()
+{
+  if(!Parent::onAdd())
+    return false;
+
+  mObjBox.maxExtents = mObjScale;
+  mObjBox.minExtents = mObjScale;
+  mObjBox.minExtents.neg();
+
+  resetWorldBox();
+
+  addToScene();
+
+  return true;
+}
+
+void afxCamera::onRemove()
+{
+  removeFromScene();
+  Parent::onRemove();
+}
+
+void afxCamera::onDeleteNotify(SimObject *obj)
+{
+  Parent::onDeleteNotify(obj);
+
+  if (obj == (SimObject*)mOrbitObject)
+  {
+    mOrbitObject = NULL;
+    if (mode == OrbitObjectMode)
+      mode = OrbitPointMode;
+  }
+
+  if (obj == cam_subject)
+  {
+    cam_subject = NULL;
+  }
+}
+
+void afxCamera::advanceTime(F32 dt) 
+{
+  Parent::advanceTime(dt);
+
+  if (gSFX3DWorld)
+  {
+     if (mode == ThirdPersonMode && cam_subject)
+     {
+        if (gSFX3DWorld->getListener() != cam_subject)
+           gSFX3DWorld->setListener(cam_subject);
+     }
+     else if (mode == FlyMode)
+     {
+        if (gSFX3DWorld->getListener() != this)
+           gSFX3DWorld->setListener(this);
+     }
+  }
+
+  cam_update(dt, false);
+}
+
+void afxCamera::processTick(const Move* move)
+{
+  Parent::processTick(move);
+  Point3F vec,pos;
+
+  // move will be NULL unless camera becomes the control object as in FlyMode
+  if (move) 
+  {
+    // UPDATE ORIENTATION //
+    delta.rotVec = mRot;
+    mObjToWorld.getColumn(3, &delta.posVec);
+    mRot.x = mClampF(mRot.x + move->pitch, -MaxPitch, MaxPitch);
+    mRot.z += move->yaw;
+
+    // ORBIT MODE // 
+    if (mode == OrbitObjectMode || mode == OrbitPointMode)
+    {
+      if(mode == OrbitObjectMode && bool(mOrbitObject)) 
+      {
+        // If this is a shapebase, use its render eye transform
+        // to avoid jittering.
+        GameBase *castObj = mOrbitObject;
+        ShapeBase* shape = dynamic_cast<ShapeBase*>(castObj);
+        if( shape != NULL ) {
+          MatrixF ret;
+          shape->getRenderEyeTransform( &ret );
+          mPosition = ret.getPosition();
+        } 
+        else 
+        {
+          // Hopefully this is a static object that doesn't move,
+          // because the worldbox doesn't get updated between ticks.
+          mOrbitObject->getWorldBox().getCenter(&mPosition);
+        }
+      }
+      set_cam_pos(mPosition, mRot);
+      validateEyePoint(1.0f, &mObjToWorld);
+      pos = mPosition;
+    }
+
+    // NON-ORBIT MODE (FLY MODE) //
+    else // if (mode == FlyMode)
+    {
+      // Update pos
+      bool faster = move->trigger[0] || move->trigger[1];
+      F32 scale = Camera::getMovementSpeed() * (faster + 1);
+
+      mObjToWorld.getColumn(3,&pos);
+      mObjToWorld.getColumn(0,&vec);
+      pos += vec * move->x * TickSec * scale;
+      mObjToWorld.getColumn(1,&vec);
+      pos += vec * move->y * TickSec * scale;
+      mObjToWorld.getColumn(2,&vec);
+      pos += vec * move->z * TickSec * scale;
+      set_cam_pos(pos,mRot);
+    }
+
+    // If on the client, calc delta for backstepping
+    if (isClientObject()) 
+    {
+      delta.pos = pos;
+      delta.rot = mRot;
+      delta.posVec = delta.posVec - delta.pos;
+      delta.rotVec = delta.rotVec - delta.rot;
+    }
+    else
+    {
+      setMaskBits(MoveMask);
+    }
+  }
+  else // if (!move)
+  {
+    if (isServerObject())
+      cam_update(1.0/32.0, true);
+  }
+
+  if (getControllingClient() && mContainer)
+    updateContainer();
+}
+
+void afxCamera::interpolateTick(F32 dt)
+{
+  Parent::interpolateTick(dt);
+
+  if (mode == ThirdPersonMode)
+    return;
+
+  Point3F rot = delta.rot + delta.rotVec * dt;
+
+  if(mode == OrbitObjectMode || mode == OrbitPointMode)
+  {
+    if(mode == OrbitObjectMode && bool(mOrbitObject))
+    {
+      // If this is a shapebase, use its render eye transform
+      // to avoid jittering.
+      GameBase *castObj = mOrbitObject;
+      ShapeBase* shape = dynamic_cast<ShapeBase*>(castObj);
+      if( shape != NULL ) 
+      {
+        MatrixF ret;
+        shape->getRenderEyeTransform( &ret );
+        mPosition = ret.getPosition();
+      } 
+      else 
+      {
+        // Hopefully this is a static object that doesn't move,
+        // because the worldbox doesn't get updated between ticks.
+        mOrbitObject->getWorldBox().getCenter(&mPosition);
+      }
+    }
+    set_cam_pos(mPosition, rot);
+    validateEyePoint(1.0f, &mObjToWorld);
+  }
+  else 
+  {
+    // NOTE - posVec is 0,0,0 unless cam is control-object and process tick is
+    // updating the delta
+    Point3F pos = delta.pos + delta.posVec * dt;
+    set_cam_pos(pos,rot);
+  }
+}
+
+void afxCamera::writePacketData(GameConnection *connection, BitStream *bstream)
+{
+  // Update client regardless of status flags.
+  Parent::writePacketData(connection, bstream);
+
+  Point3F pos; mObjToWorld.getColumn(3, &pos);
+  bstream->setCompressionPoint(pos);                                      // SET COMPRESSION POINT
+  mathWrite(*bstream, pos);                                               // SND POS
+  bstream->write(mRot.x);                                                 // SND X ROT
+  bstream->write(mRot.z);                                                 // SND Z ROT
+
+  if (bstream->writeFlag(cam_dirty))
+  {
+    mathWrite(*bstream, cam_offset);                                        // SND CAM_OFFSET
+    mathWrite(*bstream, coi_offset);                                        // SND COI_OFFSET
+    bstream->write(cam_distance); 
+    bstream->write(cam_angle);
+    cam_dirty = false;
+  }
+
+  U32 writeMode = mode;
+  Point3F writePos = mPosition;
+  S32 gIndex = -1;
+  if (mode == OrbitObjectMode)
+  {
+    gIndex = bool(mOrbitObject) ? connection->getGhostIndex(mOrbitObject): -1;
+    if(gIndex == -1)
+    {
+      writeMode = OrbitPointMode;
+      mOrbitObject->getWorldBox().getCenter(&writePos);
+    }
+  }
+
+  bstream->writeRangedU32(writeMode, CameraFirstMode, CameraLastMode);    // SND MODE
+  if (writeMode == ThirdPersonMode)
+  {
+    bstream->write(third_person_snap_s > 0);                              // SND SNAP
+    if (third_person_snap_s > 0)
+      third_person_snap_s--;
+  }
+
+  if (writeMode == OrbitObjectMode || writeMode == OrbitPointMode)
+  {
+    bstream->write(mMinOrbitDist);                                        // SND ORBIT MIN DIST
+    bstream->write(mMaxOrbitDist);                                        // SND ORBIT MAX DIST
+    bstream->write(mCurOrbitDist);                                        // SND ORBIT CURR DIST
+    if(writeMode == OrbitObjectMode)
+    {
+      bstream->writeFlag(mObservingClientObject);                         // SND OBSERVING CLIENT OBJ
+      bstream->writeInt(gIndex, NetConnection::GhostIdBitSize);           // SND ORBIT OBJ
+    }
+    if (writeMode == OrbitPointMode)
+      bstream->writeCompressedPoint(writePos);                            // WRITE COMPRESSION POINT
+  }
+}
+
+void afxCamera::readPacketData(GameConnection *connection, BitStream *bstream)
+{
+  Parent::readPacketData(connection, bstream);
+
+  Point3F pos,rot;
+  mathRead(*bstream, &pos);                                               // RCV POS
+  bstream->setCompressionPoint(pos);
+  bstream->read(&rot.x);                                                  // RCV X ROT
+  bstream->read(&rot.z);                                                  // RCV Z ROT
+
+  if (bstream->readFlag())
+  {
+    Point3F new_cam_offset, new_coi_offset;
+    mathRead(*bstream, &new_cam_offset);                                    // RCV CAM_OFFSET
+    mathRead(*bstream, &new_coi_offset);                                    // RCV COI_OFFSET
+    bstream->read(&cam_distance);
+    bstream->read(&cam_angle);
+    setThirdPersonOffset(new_cam_offset, new_coi_offset);
+  }
+
+  GameBase* obj = 0;
+  mode = bstream->readRangedU32(CameraFirstMode,                          // RCV MODE
+    CameraLastMode);
+  if (mode == ThirdPersonMode)
+  {
+    bool snap; bstream->read(&snap);
+    if (snap)
+      third_person_snap_c++;
+  }
+
+  mObservingClientObject = false;
+  if (mode == OrbitObjectMode || mode == OrbitPointMode) {
+    bstream->read(&mMinOrbitDist);
+    bstream->read(&mMaxOrbitDist);
+    bstream->read(&mCurOrbitDist);
+
+    if(mode == OrbitObjectMode)
+    {
+      mObservingClientObject = bstream->readFlag();
+      S32 gIndex = bstream->readInt(NetConnection::GhostIdBitSize);
+      obj = static_cast<GameBase*>(connection->resolveGhost(gIndex));
+    }
+    if (mode == OrbitPointMode)
+      bstream->readCompressedPoint(&mPosition);
+  }
+  if (obj != (GameBase*)mOrbitObject) {
+    if (mOrbitObject) {
+      clearProcessAfter();
+      clearNotify(mOrbitObject);
+    }
+    mOrbitObject = obj;
+    if (mOrbitObject) {
+      processAfter(mOrbitObject);
+      deleteNotify(mOrbitObject);
+    }
+  }
+
+  if (mode == ThirdPersonMode)
+    return;
+
+  set_cam_pos(pos,rot);
+  delta.pos = pos;
+  delta.rot = rot;
+  delta.rotVec.set(0,0,0);
+  delta.posVec.set(0,0,0);
+}
+
+U32 afxCamera::packUpdate(NetConnection* conn, U32 mask, BitStream *bstream)
+{
+  U32 retMask = Parent::packUpdate(conn,mask,bstream);
+
+  // The rest of the data is part of the control object packet update.
+  // If we're controlled by this client, we don't need to send it.
+  //if(bstream->writeFlag(getControllingClient() == conn && !(mask & InitialUpdateMask)))
+  //   return 0;
+
+  if (bstream->writeFlag(mask & MoveMask)) {
+    Point3F pos;
+    mObjToWorld.getColumn(3,&pos);
+    bstream->write(pos.x);
+    bstream->write(pos.y);
+    bstream->write(pos.z);
+    bstream->write(mRot.x);
+    bstream->write(mRot.z);
+  }
+
+  if (bstream->writeFlag(mask & SubjectMask)) 
+  {
+    S32 ghost_id = (cam_subject) ? conn->getGhostIndex(cam_subject) : -1;
+    if (bstream->writeFlag(ghost_id != -1))
+      bstream->writeRangedU32(U32(ghost_id), 0, NetConnection::MaxGhostCount);
+    else if (cam_subject)
+      retMask |= SubjectMask;
+  }
+
+  return retMask;
+}
+
+void afxCamera::unpackUpdate(NetConnection *conn, BitStream *bstream)
+{
+  Parent::unpackUpdate(conn,bstream);
+
+  // controlled by the client?
+  //if(bstream->readFlag())
+  //   return;
+
+  if (bstream->readFlag()) {
+    Point3F pos,rot;
+    bstream->read(&pos.x);
+    bstream->read(&pos.y);
+    bstream->read(&pos.z);
+    bstream->read(&rot.x);
+    bstream->read(&rot.z);
+    set_cam_pos(pos,rot);
+
+    // New delta for client side interpolation
+    delta.pos = pos;
+    delta.rot = rot;
+    delta.posVec = delta.rotVec = VectorF(0,0,0);
+  }
+
+  if (bstream->readFlag()) 
+  {
+    if (bstream->readFlag())
+    {
+      S32 ghost_id = bstream->readRangedU32(0, NetConnection::MaxGhostCount);
+      cam_subject = dynamic_cast<GameBase*>(conn->resolveGhost(ghost_id));
+    }
+    else
+      cam_subject = NULL;
+  }
+}
+
+// Override to ensure both are kept in scope
+void afxCamera::onCameraScopeQuery(NetConnection* conn, CameraScopeQuery* query) 
+{
+  if (cam_subject)
+    conn->objectInScope(cam_subject);
+  Parent::onCameraScopeQuery(conn, query);
+}
+
+//----------------------------------------------------------------------------
+// check if the object needs to be observed through its own camera...
+void afxCamera::getCameraTransform(F32* pos, MatrixF* mat)
+{
+  // The camera doesn't support a third person mode,
+  // so we want to override the default ShapeBase behavior.
+  ShapeBase * obj = dynamic_cast<ShapeBase*>(static_cast<SimObject*>(mOrbitObject));
+  if (obj && static_cast<ShapeBaseData*>(obj->getDataBlock())->observeThroughObject)
+    obj->getCameraTransform(pos, mat);
+  else
+    getEyeTransform(mat);
+}
+
+void afxCamera::setTransform(const MatrixF& mat)
+{
+  // This method should never be called on the client.
+
+  // This currently converts all rotation in the mat into
+  // rotations around the z and x axis.
+  Point3F pos,vec;
+  mat.getColumn(1,&vec);
+  mat.getColumn(3,&pos);
+  Point3F rot(-mAtan2(vec.z, mSqrt(vec.x*vec.x + vec.y*vec.y)),0,-mAtan2(-vec.x,vec.y));
+  set_cam_pos(pos,rot);
+}
+
+void afxCamera::onEditorEnable()
+{
+  mNetFlags.set(Ghostable);
+}
+
+void afxCamera::onEditorDisable()
+{
+  mNetFlags.clear(Ghostable);
+}
+
+F32 afxCamera::getCameraFov()
+{
+  ShapeBase * obj = dynamic_cast<ShapeBase*>(static_cast<SimObject*>(mOrbitObject));
+  if(obj && static_cast<ShapeBaseData*>(obj->getDataBlock())->observeThroughObject)
+    return(obj->getCameraFov());
+  else
+    return(Parent::getCameraFov());
+}
+
+F32 afxCamera::getDefaultCameraFov()
+{
+  ShapeBase * obj = dynamic_cast<ShapeBase*>(static_cast<SimObject*>(mOrbitObject));
+  if(obj && static_cast<ShapeBaseData*>(obj->getDataBlock())->observeThroughObject)
+    return(obj->getDefaultCameraFov());
+  else
+    return(Parent::getDefaultCameraFov());
+}
+
+bool afxCamera::isValidCameraFov(F32 fov)
+{
+  ShapeBase * obj = dynamic_cast<ShapeBase*>(static_cast<SimObject*>(mOrbitObject));
+  if(obj && static_cast<ShapeBaseData*>(obj->getDataBlock())->observeThroughObject)
+    return(obj->isValidCameraFov(fov));
+  else
+    return(Parent::isValidCameraFov(fov));
+}
+
+void afxCamera::setCameraFov(F32 fov)
+{
+  ShapeBase * obj = dynamic_cast<ShapeBase*>(static_cast<SimObject*>(mOrbitObject));
+  if(obj && static_cast<ShapeBaseData*>(obj->getDataBlock())->observeThroughObject)
+    obj->setCameraFov(fov);
+  else
+    Parent::setCameraFov(fov);
+}
+
+F32 afxCamera::getDamageFlash() const
+{
+  if (mode == OrbitObjectMode && isServerObject() && bool(mOrbitObject))
+  {
+    const GameBase *castObj = mOrbitObject;
+    const ShapeBase* psb = dynamic_cast<const ShapeBase*>(castObj);
+    if (psb)
+      return psb->getDamageFlash();
+  }
+
+  return mDamageFlash;
+}
+
+F32 afxCamera::getWhiteOut() const
+{
+  if (mode == OrbitObjectMode && isServerObject() && bool(mOrbitObject))
+  {
+    const GameBase *castObj = mOrbitObject;
+    const ShapeBase* psb = dynamic_cast<const ShapeBase*>(castObj);
+    if (psb)
+      return psb->getWhiteOut();
+  }
+
+  return mWhiteOut;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+void afxCamera::setControllingClient( GameConnection* client )
+{
+   GameBase::setControllingClient( client );
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//

+ 189 - 0
Engine/source/afx/afxCamera.h

@@ -0,0 +1,189 @@
+//-----------------------------------------------------------------------------
+// Copyright (c) 2012 GarageGames, LLC
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//-----------------------------------------------------------------------------
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
+// Copyright (C) 2015 Faust Logic, Inc.
+//
+// afxCamera implements a modified camera for demonstrating a third person camera style
+// which is more common to RPG games than the standard FPS style camera. For the most part,
+// it is a hybrid of the standard TGE camera and the third person mode of the Advanced Camera
+// resource, authored by Thomas "Man of Ice" Lund. This camera implements the bare minimum
+// required for demonstrating an RPG style camera and leaves tons of room for improvement. 
+// It should be replaced with a better camera if possible.
+//
+// Advanced Camera Resource by Thomas "Man of Ice" Lund:
+//   http://www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=5471
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _AFX_CAMERA_H_
+#define _AFX_CAMERA_H_
+
+#ifndef _SHAPEBASE_H_
+#include "game/shapeBase.h"
+#endif
+
+//----------------------------------------------------------------------------
+struct afxCameraData: public ShapeBaseData {
+  typedef ShapeBaseData Parent;
+
+  static U32    sCameraCollisionMask;
+
+  //
+  DECLARE_CONOBJECT(afxCameraData);
+  DECLARE_CATEGORY("AFX");
+  static void initPersistFields();
+  virtual void packData(BitStream* stream);
+  virtual void unpackData(BitStream* stream);
+};
+
+
+//----------------------------------------------------------------------------
+// Implements a basic camera object.
+class afxCamera: public ShapeBase
+{
+  typedef ShapeBase Parent;
+
+  enum MaskBits {
+    MoveMask     = Parent::NextFreeMask,
+    SubjectMask  = Parent::NextFreeMask << 1,
+    NextFreeMask = Parent::NextFreeMask << 2
+  };
+
+  struct StateDelta {
+    Point3F pos;
+    Point3F rot;
+    VectorF posVec;
+    VectorF rotVec;
+  };
+
+  enum 
+  {
+    ThirdPersonMode = 1,
+    FlyMode         = 2,
+    OrbitObjectMode = 3,
+    OrbitPointMode  = 4,
+    CameraFirstMode = 0,
+    CameraLastMode  = 4
+  };
+
+private:
+  int             mode;
+  Point3F         mRot;
+  StateDelta      delta;
+
+  SimObjectPtr<GameBase> mOrbitObject;
+  F32             mMinOrbitDist;
+  F32             mMaxOrbitDist;
+  F32             mCurOrbitDist;
+  Point3F         mPosition;
+  bool            mObservingClientObject;
+
+  SceneObject*    cam_subject;
+  Point3F         cam_offset;
+  Point3F         coi_offset;
+  F32             cam_distance;
+  F32             cam_angle;
+  bool            cam_dirty;
+
+  bool            flymode_saved;
+  Point3F         flymode_saved_pos;
+  S8              third_person_snap_c;
+  S8              third_person_snap_s;
+
+  void            set_cam_pos(const Point3F& pos, const Point3F& viewRot);
+  void            cam_update(F32 dt, bool on_server);
+
+public:
+  /*C*/           afxCamera();
+  /*D*/           ~afxCamera();
+
+  Point3F&        getPosition();
+  void            setFlyMode();
+  void            setOrbitMode(GameBase* obj, Point3F& pos, AngAxisF& rot, F32 minDist, F32 maxDist, F32 curDist, bool ownClientObject);
+  void            validateEyePoint(F32 pos, MatrixF *mat);
+
+  GameBase*       getOrbitObject() { return(mOrbitObject); }
+  bool            isObservingClientObject() { return(mObservingClientObject); }
+
+  void            snapToPosition(const Point3F& pos);
+  void            setCameraSubject(SceneObject* subject);
+  void            setThirdPersonOffset(const Point3F& offset);
+  void            setThirdPersonOffset(const Point3F& offset, const Point3F& coi_offset);
+  const Point3F&  getThirdPersonOffset() const { return cam_offset; }
+  const Point3F&  getThirdPersonCOIOffset() const { return coi_offset; }
+  void            setThirdPersonDistance(F32 distance);
+  F32             getThirdPersonDistance();
+  void            setThirdPersonAngle(F32 angle);
+  F32             getThirdPersonAngle();
+  void            setThirdPersonMode();
+  void            setThirdPersonSnap();
+  void            setThirdPersonSnapClient();
+  const char*     getMode();
+
+  bool            isCamera() const { return true; }
+
+  DECLARE_CONOBJECT(afxCamera);
+  DECLARE_CATEGORY("AFX");
+
+private:          // 3POV SECTION
+  U32             blockers_mask_3pov;
+
+  void            cam_update_3pov(F32 dt, bool on_server);
+  bool            avoid_blocked_view(const Point3F& start, const Point3F& end, Point3F& newpos);
+  bool            test_blocked_line(const Point3F& start, const Point3F& end);
+
+public:           // STD OVERRIDES SECTION
+  virtual bool    onAdd();
+  virtual void    onRemove();
+  virtual void    onDeleteNotify(SimObject *obj);
+
+  virtual void    advanceTime(F32 dt);
+  virtual void    processTick(const Move* move);
+  virtual void    interpolateTick(F32 delta);
+
+  virtual void    writePacketData(GameConnection *conn, BitStream *stream);
+  virtual void    readPacketData(GameConnection *conn, BitStream *stream);
+  virtual U32     packUpdate(NetConnection *conn, U32 mask, BitStream *stream);
+  virtual void    unpackUpdate(NetConnection *conn, BitStream *stream);
+
+  virtual void    onCameraScopeQuery(NetConnection* cr, CameraScopeQuery*);
+  virtual void    getCameraTransform(F32* pos,MatrixF* mat);
+  virtual void    setTransform(const MatrixF& mat);
+
+  virtual void    onEditorEnable();
+  virtual void    onEditorDisable();
+
+  virtual F32     getCameraFov();
+  virtual F32     getDefaultCameraFov();
+  virtual bool    isValidCameraFov(F32 fov);
+  virtual void    setCameraFov(F32 fov);
+
+  virtual F32     getDamageFlash() const;
+  virtual F32     getWhiteOut() const;
+
+  virtual void setControllingClient( GameConnection* connection );
+};
+
+
+#endif // _AFX_CAMERA_H_

+ 1084 - 0
Engine/source/afx/afxChoreographer.cpp

@@ -0,0 +1,1084 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
+// Copyright (C) 2015 Faust Logic, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#include "afx/arcaneFX.h"
+
+#include "console/engineAPI.h"
+#include "T3D/gameBase/gameConnection.h"
+#include "math/mathIO.h"
+#include "console/compiler.h"
+
+#include "afx/afxConstraint.h"
+#include "afx/afxChoreographer.h"
+#include "afx/afxEffectWrapper.h"
+#include "afx/util/afxParticlePool.h"
+#include "afx/forces/afxForceSet.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+IMPLEMENT_CO_DATABLOCK_V1(afxChoreographerData);
+
+ConsoleDocClass( afxChoreographerData,
+   "@brief Datablock base class used by choreographers.\n\n"
+
+   "@ingroup afxChoreographers\n"
+   "@ingroup AFX\n"
+   "@ingroup Datablocks\n"
+);
+
+afxChoreographerData::afxChoreographerData()
+{
+  exec_on_new_clients = false;
+  echo_packet_usage = 100;
+  client_script_file = ST_NULLSTRING;
+  client_init_func = ST_NULLSTRING;
+}
+
+afxChoreographerData::afxChoreographerData(const afxChoreographerData& other, bool temp_clone) : GameBaseData(other, temp_clone)
+{
+  exec_on_new_clients = other.exec_on_new_clients;
+  echo_packet_usage = other.echo_packet_usage;
+  client_script_file = other.client_script_file;
+  client_init_func = other.client_init_func;
+}
+
+#define myOffset(field) Offset(field, afxChoreographerData)
+
+void afxChoreographerData::initPersistFields()
+{
+  addField("execOnNewClients",    TypeBool,       myOffset(exec_on_new_clients),
+    "...");
+  addField("echoPacketUsage",     TypeS8,         myOffset(echo_packet_usage),
+    "...");
+  addField("clientScriptFile",    TypeFilename,   myOffset(client_script_file),
+    "...");
+  addField("clientInitFunction",  TypeString,     myOffset(client_init_func),
+    "...");
+
+  Parent::initPersistFields();
+}
+
+void afxChoreographerData::packData(BitStream* stream)
+{
+  Parent::packData(stream);
+
+  stream->write(exec_on_new_clients);
+  stream->write(echo_packet_usage);
+  stream->writeString(client_script_file);
+  stream->writeString(client_init_func);
+}
+
+void afxChoreographerData::unpackData(BitStream* stream)
+{
+  Parent::unpackData(stream);
+
+  stream->read(&exec_on_new_clients);
+  stream->read(&echo_packet_usage);
+  client_script_file = stream->readSTString();
+  client_init_func = stream->readSTString();
+}
+
+bool afxChoreographerData::preload(bool server, String &errorStr)
+{
+  if (!Parent::preload(server, errorStr))
+    return false;
+
+  if (!server && client_script_file != ST_NULLSTRING)
+  {
+    Compiler::gSyntaxError = false;
+    Con::evaluate(avar("exec(\"%s\");", client_script_file), false, 0);
+    if (Compiler::gSyntaxError)
+    {
+      Con::errorf("afxChoreographerData: failed to exec clientScriptFile \"%s\" -- syntax error", client_script_file);
+      Compiler::gSyntaxError = false;
+    }
+  }
+
+  return true;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+IMPLEMENT_CO_DATABLOCK_V1(afxChoreographer);
+
+ConsoleDocClass( afxChoreographer,
+   "@brief Base class used by choreographers.\n\n"
+
+   "@ingroup afxChoreographers\n"
+   "@ingroup AFX\n"
+);
+
+afxChoreographer::afxChoreographer()
+{
+  datablock = 0;  // datablock initializer (union these?)
+  exeblock = 0;   // object to use in executef callouts
+
+  // create the constraint manager
+  constraint_mgr = new afxConstraintMgr();
+
+  ranking = 0;
+  lod = 0;
+  exec_conds_mask = 0;
+  choreographer_id = 0;
+  extra = 0;
+  started_with_newop = false;
+  postpone_activation = false;
+  remapped_cons_sent = false; // CONSTRAINT REMAPPING
+
+  dyn_cons_defs = &dc_defs_a;
+  dyn_cons_defs2 = &dc_defs_b;
+
+  proc_after_obj = 0;
+  trigger_mask = 0;
+
+  force_set_mgr = 0;
+
+  mOrderGUID = 0x0FFFFFFF;
+}
+
+afxChoreographer::~afxChoreographer()
+{
+  explicit_clients.clear();
+
+  for (S32 i = 0; i < dyn_cons_defs->size(); i++)
+  {
+    if ((*dyn_cons_defs)[i].cons_type == POINT_CONSTRAINT && (*dyn_cons_defs)[i].cons_obj.point != NULL)
+      delete (*dyn_cons_defs)[i].cons_obj.point;
+    else if ((*dyn_cons_defs)[i].cons_type == TRANSFORM_CONSTRAINT && (*dyn_cons_defs)[i].cons_obj.xfm != NULL)
+      delete (*dyn_cons_defs)[i].cons_obj.xfm;
+  }
+
+  constraint_mgr->clearAllScopeableObjs();
+  delete constraint_mgr;
+
+  delete force_set_mgr;
+}
+
+void afxChoreographer::initPersistFields()
+{
+  // conditionals
+  addField("extra",              TYPEID<SimObject>(), Offset(extra, afxChoreographer),
+    "...");
+  addField("postponeActivation", TypeBool,            Offset(postpone_activation, afxChoreographer),
+    "...");
+
+  Parent::initPersistFields();
+}
+
+bool afxChoreographer::onAdd()
+{
+  if (!Parent::onAdd()) 
+    return(false);
+
+  if (isServerObject())
+    choreographer_id = arcaneFX::registerChoreographer(this);
+  else
+  {
+    if (proc_after_obj)
+    {
+      processAfter(proc_after_obj);
+      proc_after_obj = 0;
+    }
+
+    force_set_mgr = new afxForceSetMgr();
+
+    if (datablock && datablock->client_init_func != ST_NULLSTRING)
+      Con::executef(datablock->client_init_func, getIdString());
+  }
+
+  return(true);
+}
+
+void afxChoreographer::onRemove()
+{
+  for (S32 i = 0; i < particle_pools.size(); i++)
+    if (particle_pools[i])
+      particle_pools[i]->setChoreographer(0);
+  particle_pools.clear();
+
+  if (isServerObject())
+    arcaneFX::unregisterChoreographer(this);
+  else
+    arcaneFX::unregisterClientChoreographer(this);
+
+  choreographer_id = 0;
+  Parent::onRemove();
+}
+
+void afxChoreographer::onDeleteNotify(SimObject* obj)
+{
+  if (dynamic_cast<SceneObject*>(obj))
+  { 
+    SceneObject* scn_obj = (SceneObject*)(obj);
+    for (S32 i = 0; i < dyn_cons_defs->size(); i++)
+      if ((*dyn_cons_defs)[i].cons_type != OBJECT_CONSTRAINT || (*dyn_cons_defs)[i].cons_obj.object != scn_obj)
+        dyn_cons_defs2->push_back((*dyn_cons_defs)[i]);
+
+    Vector<dynConstraintDef>* tmp = dyn_cons_defs;
+    dyn_cons_defs = dyn_cons_defs2;
+    dyn_cons_defs2 = tmp;
+    dyn_cons_defs2->clear();
+
+    if (isServerObject() && scn_obj->isScopeable())
+        constraint_mgr->removeScopeableObject(scn_obj);
+  }
+  else if (dynamic_cast<NetConnection*>(obj))
+  {
+    removeExplicitClient((NetConnection*)obj);
+  }
+
+  Parent::onDeleteNotify(obj);
+}
+
+bool afxChoreographer::onNewDataBlock(GameBaseData* dptr, bool reload)
+{
+  datablock = dynamic_cast<afxChoreographerData*>(dptr);
+  if (!datablock || !Parent::onNewDataBlock(dptr, reload))
+    return false;
+
+  exeblock = datablock;
+
+  return true;
+}
+
+void afxChoreographer::pack_constraint_info(NetConnection* conn, BitStream* stream)
+{
+  stream->write(dyn_cons_defs->size());
+  for (S32 i = 0; i < dyn_cons_defs->size(); i++)
+  {
+    if ((*dyn_cons_defs)[i].cons_type == OBJECT_CONSTRAINT && (*dyn_cons_defs)[i].cons_obj.object != NULL)
+    {
+      stream->writeString((*dyn_cons_defs)[i].cons_name);
+      stream->write((*dyn_cons_defs)[i].cons_type);
+      SceneObject* object = (*dyn_cons_defs)[i].cons_obj.object;
+      S32 ghost_idx = conn->getGhostIndex(object); 
+      if (stream->writeFlag(ghost_idx != -1))
+      {
+        stream->writeRangedU32(U32(ghost_idx), 0, NetConnection::MaxGhostCount);
+      }
+      else
+      {
+        if (stream->writeFlag(object->getScopeId() > 0))
+        {
+          stream->writeInt(object->getScopeId(), NetObject::SCOPE_ID_BITS);
+          stream->writeFlag(dynamic_cast<ShapeBase*>(object) != NULL);
+        }
+      }
+    }
+    else if ((*dyn_cons_defs)[i].cons_type == POINT_CONSTRAINT && (*dyn_cons_defs)[i].cons_obj.point != NULL)
+    {
+      stream->writeString((*dyn_cons_defs)[i].cons_name);
+      stream->write((*dyn_cons_defs)[i].cons_type);
+      mathWrite(*stream, *(*dyn_cons_defs)[i].cons_obj.point);
+    }
+    else if ((*dyn_cons_defs)[i].cons_type == TRANSFORM_CONSTRAINT && (*dyn_cons_defs)[i].cons_obj.xfm != NULL)
+    {
+      stream->writeString((*dyn_cons_defs)[i].cons_name);
+      stream->write((*dyn_cons_defs)[i].cons_type);
+      mathWrite(*stream, *(*dyn_cons_defs)[i].cons_obj.xfm);
+    }
+  }
+      
+  constraint_mgr->packConstraintNames(conn, stream);
+}
+
+void afxChoreographer::unpack_constraint_info(NetConnection* conn, BitStream* stream)
+{
+  S32 n_defs;
+  stream->read(&n_defs);
+  dyn_cons_defs->clear();
+  for (S32 i = 0; i < n_defs; i++)
+  {
+    StringTableEntry cons_name = stream->readSTString();
+    U8 cons_type; stream->read(&cons_type);
+    if (cons_type == OBJECT_CONSTRAINT)
+    {
+      SceneObject* scn_obj = NULL;
+      if (stream->readFlag())
+      {
+        S32 ghost_idx = stream->readRangedU32(0, NetConnection::MaxGhostCount);
+        scn_obj = dynamic_cast<SceneObject*>(conn->resolveGhost(ghost_idx));
+        if (scn_obj)
+        {
+          addObjectConstraint(scn_obj, cons_name); 
+        }
+        else
+          Con::errorf("CANNOT RESOLVE GHOST %d %s", ghost_idx, cons_name);
+      }
+      else
+      {
+        if (stream->readFlag())
+        {
+          U16 scope_id = stream->readInt(NetObject::SCOPE_ID_BITS);
+          bool is_shape = stream->readFlag();
+          addObjectConstraint(scope_id, cons_name, is_shape);                                                                   
+        }
+      }
+    }
+    else if (cons_type == POINT_CONSTRAINT)
+    {
+      Point3F point;
+      mathRead(*stream, &point);
+      addPointConstraint(point, cons_name);
+    }    
+    else if (cons_type == TRANSFORM_CONSTRAINT)
+    {
+      MatrixF xfm;
+      mathRead(*stream, &xfm);
+      addTransformConstraint(xfm, cons_name);
+    }    
+  }
+  
+  constraint_mgr->unpackConstraintNames(stream);
+}
+
+void afxChoreographer::setup_dynamic_constraints()
+{
+  for (S32 i = 0; i < dyn_cons_defs->size(); i++)
+  {
+    switch ((*dyn_cons_defs)[i].cons_type)
+    {
+    case OBJECT_CONSTRAINT:
+      constraint_mgr->setReferenceObject((*dyn_cons_defs)[i].cons_name, (*dyn_cons_defs)[i].cons_obj.object);
+      break;
+    case POINT_CONSTRAINT:
+      constraint_mgr->setReferencePoint((*dyn_cons_defs)[i].cons_name, *(*dyn_cons_defs)[i].cons_obj.point);
+      break;
+    case TRANSFORM_CONSTRAINT:
+      constraint_mgr->setReferenceTransform((*dyn_cons_defs)[i].cons_name, *(*dyn_cons_defs)[i].cons_obj.xfm);
+      break;
+    case OBJECT_CONSTRAINT_SANS_OBJ:
+      constraint_mgr->setReferenceObjectByScopeId((*dyn_cons_defs)[i].cons_name, (*dyn_cons_defs)[i].cons_obj.scope_id, false);
+      break;
+    case OBJECT_CONSTRAINT_SANS_SHAPE:
+      constraint_mgr->setReferenceObjectByScopeId((*dyn_cons_defs)[i].cons_name, (*dyn_cons_defs)[i].cons_obj.scope_id, true);
+      break;
+    }
+  }
+}
+
+afxChoreographer::dynConstraintDef* afxChoreographer::find_cons_def_by_name(const char* cons_name)
+{
+  StringTableEntry cons_name_ste = StringTable->insert(cons_name);
+
+  for (S32 i = 0; i < dyn_cons_defs->size(); i++)
+  {
+    if ((*dyn_cons_defs)[i].cons_name == cons_name_ste)
+      return &((*dyn_cons_defs)[i]);
+  }
+
+  return 0;
+}
+
+void afxChoreographer::check_packet_usage(NetConnection* conn, BitStream* stream, S32 mark_stream_pos, const char* msg_tag)
+{
+
+  S32 packed_size = stream->getCurPos() - mark_stream_pos;
+  S32 current_headroom = stream->getMaxWriteBitNum()-(stream->getStreamSize()<<3);
+  S32 max_headroom = stream->getMaxWriteBitNum()-(conn->getMaxRatePacketSize()<<3);
+
+  if (packed_size > max_headroom)
+  {
+    Con::errorf("%s [%s] WARNING -- packed-bits (%d) > limit (%d) for max PacketSize settings. [%s]",
+                msg_tag, datablock->getName(), packed_size, max_headroom, "PACKET OVERRUN POSSIBLE");
+    Con::errorf("getMaxRatePacketSize()=%d getCurRatePacketSize()=%d", conn->getMaxRatePacketSize(), conn->getCurRatePacketSize());
+  }
+  // JTF Note: this current_headroom > 0 check is odd and occurs when object is created real early
+  // in the startup, such as when the dead orc gets fatal damage and we try to post a text effect.
+  else if (packed_size > current_headroom && current_headroom > 0)
+  {
+    Con::errorf("%s [%s] WARNING -- packed-bits (%d) > limit (%d) for current PacketSize settings. [%s]",
+                msg_tag, datablock->getName(), packed_size, current_headroom, "PACKET OVERRUN POSSIBLE");
+  }
+  else
+  {
+    F32 percentage = 100.0f*((F32)packed_size/(F32)max_headroom);
+    if (percentage >= datablock->echo_packet_usage)
+    {
+        Con::warnf("%s [%s] -- packed-bits (%d) < limit (%d). [%.1f%% full]", msg_tag, datablock->getName(),
+                  packed_size, max_headroom, percentage);
+    }
+  }
+}
+
+SceneObject* afxChoreographer::get_camera(Point3F* cam_pos) const
+{
+  GameConnection* conn = GameConnection::getConnectionToServer();
+  if (!conn)
+  {
+    if (cam_pos)
+      cam_pos->zero();
+    return 0;
+  }
+
+  SceneObject* cam_obj = conn->getCameraObject();
+  if (cam_pos)
+  {
+    if (cam_obj)
+    {
+      MatrixF cam_mtx;
+      conn->getControlCameraTransform(0, &cam_mtx);
+      cam_mtx.getColumn(3, cam_pos);
+    }
+    else
+      cam_pos->zero();
+  }
+
+  return cam_obj;
+}
+
+U32 afxChoreographer::packUpdate(NetConnection* conn, U32 mask, BitStream* stream)
+{
+  U32 retMask = Parent::packUpdate(conn, mask, stream);
+  
+  if (stream->writeFlag(mask & InitialUpdateMask))      //-- INITIAL UPDATE ?
+  {
+    stream->write(ranking);
+    stream->write(lod);
+    stream->write(exec_conds_mask);
+    stream->write(choreographer_id);
+
+    // write dynamic fields beginning with arcaneFX::sParameterFieldPrefix
+    SimFieldDictionaryIterator itr(getFieldDictionary());
+    SimFieldDictionary::Entry* entry;
+    U32 prefix_len = dStrlen(arcaneFX::sParameterFieldPrefix);
+    if (prefix_len > 0)
+    {
+      while ((entry = *itr) != NULL)
+      {
+        if (dStrncmp(entry->slotName, arcaneFX::sParameterFieldPrefix, prefix_len) == 0)
+        {
+          stream->writeFlag(true);
+          stream->writeString(entry->slotName);
+          stream->writeLongString(1023, entry->value);
+        }
+        ++itr;
+      }
+    }
+    stream->writeFlag(false);
+  }
+
+  if (stream->writeFlag(mask & TriggerMask))
+  {
+    stream->write(trigger_mask);
+  }
+
+  // CONSTRAINT REMAPPING <<
+  if (stream->writeFlag((mask & RemapConstraintMask) && !remapped_cons_defs.empty()))
+  {
+    remapped_cons_sent = true;
+    //Con::errorf("PACKING CONS REMAP %d conn:%d", remapped_cons_defs.size(), (U32) conn);
+
+    stream->write(remapped_cons_defs.size());
+    for (S32 i = 0; i < remapped_cons_defs.size(); i++)
+    {
+      if (remapped_cons_defs[i]->cons_type == OBJECT_CONSTRAINT && remapped_cons_defs[i]->cons_obj.object != NULL)
+      {
+        //Con::errorf("PACKING CONS REMAP: name %s", remapped_cons_defs[i]->cons_name);
+        stream->writeString(remapped_cons_defs[i]->cons_name);
+        //Con::errorf("PACKING CONS REMAP: type %d", remapped_cons_defs[i]->cons_type);
+        stream->write(remapped_cons_defs[i]->cons_type);
+        SceneObject* object = remapped_cons_defs[i]->cons_obj.object;
+        S32 ghost_idx = conn->getGhostIndex(object);
+        //Con::errorf("PACKING CONS REMAP: ghost %d", ghost_idx);
+        if (stream->writeFlag(ghost_idx != -1))
+        {
+          stream->writeRangedU32(U32(ghost_idx), 0, NetConnection::MaxGhostCount);
+        }
+        else
+        {
+          if (stream->writeFlag(object->getScopeId() > 0))
+          {
+            stream->writeInt(object->getScopeId(), NetObject::SCOPE_ID_BITS);
+            stream->writeFlag(dynamic_cast<ShapeBase*>(object) != NULL);
+          }
+        }
+      }
+      else if (remapped_cons_defs[i]->cons_type == POINT_CONSTRAINT && remapped_cons_defs[i]->cons_obj.point != NULL)
+      {
+        stream->writeString(remapped_cons_defs[i]->cons_name);
+        stream->write(remapped_cons_defs[i]->cons_type);
+        mathWrite(*stream, *remapped_cons_defs[i]->cons_obj.point);
+      }
+      else if (remapped_cons_defs[i]->cons_type == TRANSFORM_CONSTRAINT && remapped_cons_defs[i]->cons_obj.xfm != NULL)
+      {
+        stream->writeString(remapped_cons_defs[i]->cons_name);
+        stream->write(remapped_cons_defs[i]->cons_type);
+        mathWrite(*stream, *remapped_cons_defs[i]->cons_obj.xfm);
+      }
+    }
+  }
+  // CONSTRAINT REMAPPING >>
+
+  AssertISV(stream->isValid(), "afxChoreographer::packUpdate(): write failure occurred, possibly caused by packet-size overrun."); 
+
+  return retMask;
+}
+
+void afxChoreographer::unpackUpdate(NetConnection * conn, BitStream * stream)
+{
+  Parent::unpackUpdate(conn, stream);
+  
+  // InitialUpdate Only
+  if (stream->readFlag())
+  {
+    stream->read(&ranking);
+    stream->read(&lod);
+    stream->read(&exec_conds_mask);
+    stream->read(&choreographer_id);
+
+    // read dynamic fields
+    while(stream->readFlag())
+    {
+      char slotName[256];
+      char value[1024];
+      stream->readString(slotName);
+      stream->readLongString(1023, value);
+      setDataField(StringTable->insert(slotName), 0, value);
+    }
+
+    arcaneFX::registerClientChoreographer(this);
+  }
+
+  if (stream->readFlag()) // TriggerMask
+  {
+    stream->read(&trigger_mask);
+  }
+
+  // CONSTRAINT REMAPPING <<
+  if (stream->readFlag()) // RemapConstraintMask
+  {
+    S32 n_defs;
+    stream->read(&n_defs);
+    for (S32 i = 0; i < n_defs; i++)
+    {
+      StringTableEntry cons_name = stream->readSTString();
+      U8 cons_type; stream->read(&cons_type);
+      if (cons_type == OBJECT_CONSTRAINT)
+      {
+        SceneObject* scn_obj = NULL;
+        if (stream->readFlag())
+        {
+          S32 ghost_idx = stream->readRangedU32(0, NetConnection::MaxGhostCount);
+          scn_obj = dynamic_cast<SceneObject*>(conn->resolveGhost(ghost_idx));
+          if (scn_obj)
+          {
+            remapObjectConstraint(scn_obj, cons_name); 
+          }
+          else
+            Con::errorf("CANNOT RESOLVE GHOST %d %s", ghost_idx, cons_name);
+        }
+        else
+        {
+          if (stream->readFlag())
+          {
+            U16 scope_id = stream->readInt(NetObject::SCOPE_ID_BITS);
+            bool is_shape = stream->readFlag();
+            remapObjectConstraint(scope_id, cons_name, is_shape);
+          }
+        }
+      }
+      else if (cons_type == POINT_CONSTRAINT)
+      {
+        Point3F point;
+        mathRead(*stream, &point);
+        remapPointConstraint(point, cons_name);
+      }    
+      else if (cons_type == TRANSFORM_CONSTRAINT)
+      {
+        MatrixF xfm;
+        mathRead(*stream, &xfm);
+        remapTransformConstraint(xfm, cons_name);
+      }    
+    }
+  }
+  // CONSTRAINT REMAPPING >>
+}
+
+void afxChoreographer::executeScriptEvent(const char* method, afxConstraint* cons, 
+                                          const MatrixF& xfm, const char* data)
+{
+  SceneObject* cons_obj = (cons) ? cons->getSceneObject() : NULL;
+
+  char *arg_buf = Con::getArgBuffer(256);
+  Point3F pos;
+  xfm.getColumn(3,&pos);
+  AngAxisF aa(xfm);
+  dSprintf(arg_buf,256,"%g %g %g %g %g %g %g",
+           pos.x, pos.y, pos.z,
+           aa.axis.x, aa.axis.y, aa.axis.z, aa.angle);
+
+  // CALL SCRIPT afxChoreographerData::method(%choreographer, %constraint, %transform, %data)
+  Con::executef(exeblock, method, 
+                getIdString(),
+                (cons_obj) ? cons_obj->getIdString() : "",
+                arg_buf,
+                data);
+}
+
+void afxChoreographer::addObjectConstraint(SceneObject* object, const char* cons_name)
+{
+  if (!object || !cons_name)
+    return;
+
+  dynConstraintDef dyn_def;
+  dyn_def.cons_name = StringTable->insert(cons_name);
+  dyn_def.cons_type = OBJECT_CONSTRAINT;
+  dyn_def.cons_obj.object = object;
+  dyn_cons_defs->push_back(dyn_def);
+
+#if defined(AFX_CAP_SCOPE_TRACKING)
+  if (isServerObject() && object->isScopeable())
+    constraint_mgr->addScopeableObject(object);
+#endif
+
+  constraint_mgr->defineConstraint(OBJECT_CONSTRAINT, dyn_def.cons_name);
+  deleteNotify(object);
+}
+
+void afxChoreographer::addObjectConstraint(U16 scope_id, const char* cons_name, bool is_shape)
+{
+  if (!cons_name)
+    return;
+
+  dynConstraintDef dyn_def;
+  dyn_def.cons_name = StringTable->insert(cons_name);
+  dyn_def.cons_type = (is_shape) ? OBJECT_CONSTRAINT_SANS_SHAPE : OBJECT_CONSTRAINT_SANS_OBJ;
+  dyn_def.cons_obj.scope_id = scope_id;
+  dyn_cons_defs->push_back(dyn_def);
+
+  constraint_mgr->defineConstraint(OBJECT_CONSTRAINT, dyn_def.cons_name);
+}
+
+void afxChoreographer::addPointConstraint(Point3F& point, const char* cons_name)
+{
+  dynConstraintDef dyn_def;
+  dyn_def.cons_name = StringTable->insert(cons_name);
+  dyn_def.cons_type = POINT_CONSTRAINT;
+  dyn_def.cons_obj.point = new Point3F(point);
+  dyn_cons_defs->push_back(dyn_def);
+
+  constraint_mgr->defineConstraint(POINT_CONSTRAINT, dyn_def.cons_name);
+}
+
+void afxChoreographer::addTransformConstraint(MatrixF& xfm, const char* cons_name)
+{
+  dynConstraintDef dyn_def;
+  dyn_def.cons_name = StringTable->insert(cons_name);
+  dyn_def.cons_type = TRANSFORM_CONSTRAINT;
+  dyn_def.cons_obj.xfm = new MatrixF(xfm);
+  dyn_cons_defs->push_back(dyn_def);
+
+  constraint_mgr->defineConstraint(TRANSFORM_CONSTRAINT, dyn_def.cons_name);
+}
+
+// CONSTRAINT REMAPPING <<
+static inline U32 resolve_cons_spec(const char* source_spec, Point3F& pos, MatrixF& xfm, SceneObject** scn_obj)
+{
+  AngAxisF aa;
+
+  S32 args_n = dSscanf(source_spec, "%g %g %g %g %g %g %g", 
+                       &pos.x, &pos.y, &pos.z,
+                       &aa.axis.x, &aa.axis.y, &aa.axis.z, &aa.angle);
+
+  // TRANSFORM CONSTRAINT SRC
+  if (args_n == 7)
+  {
+     aa.setMatrix(&xfm);
+     xfm.setColumn(3,pos);
+     return afxEffectDefs::TRANSFORM_CONSTRAINT;
+  }
+
+  // POINT CONSTRAINT SRC 
+  if (args_n == 3)
+  {
+    return afxEffectDefs::POINT_CONSTRAINT;
+  }
+
+  SimObject* cons_sim_obj = Sim::findObject(source_spec);
+  *scn_obj = dynamic_cast<SceneObject*>(cons_sim_obj);
+  if (*scn_obj)
+  {
+    return afxEffectDefs::OBJECT_CONSTRAINT;
+  }
+
+  return afxEffectDefs::UNDEFINED_CONSTRAINT_TYPE;
+}
+// CONSTRAINT REMAPPING >>
+
+bool afxChoreographer::addConstraint(const char* source_spec, const char* cons_name)
+{
+  VectorF pos;
+  MatrixF xfm;
+  SceneObject* scn_obj;
+
+  switch (resolve_cons_spec(source_spec, pos, xfm, &scn_obj))
+  {
+  case TRANSFORM_CONSTRAINT:
+    addTransformConstraint(xfm, cons_name);
+    return true;
+  case POINT_CONSTRAINT:
+    addPointConstraint(pos, cons_name);
+    return true;
+  case OBJECT_CONSTRAINT:
+    addObjectConstraint(scn_obj, cons_name);
+    return true;
+  }
+
+  return false;
+}
+
+void afxChoreographer::addNamedEffect(afxEffectWrapper* ew)
+{
+  named_effects.addObject(ew);
+}
+
+void afxChoreographer::removeNamedEffect(afxEffectWrapper* ew)
+{
+  named_effects.removeObject(ew);
+}
+
+afxEffectWrapper* afxChoreographer::findNamedEffect(StringTableEntry name)
+{
+  return (afxEffectWrapper*) named_effects.findObject(name);
+}
+
+void afxChoreographer::setGhostConstraintObject(SceneObject* obj, StringTableEntry cons_name)
+{
+  if (constraint_mgr)
+    constraint_mgr->setReferenceObject(cons_name, obj);
+}
+
+void afxChoreographer::restoreScopedObject(SceneObject* obj)
+{
+  constraint_mgr->restoreScopedObject(obj, this);
+  constraint_mgr->adjustProcessOrdering(this);
+}
+
+void afxChoreographer::addExplicitClient(NetConnection* conn) 
+{ 
+  if (!conn)
+    return;
+
+  for (S32 i = 0; i < explicit_clients.size(); i++)
+  {
+    if (explicit_clients[i] == conn)
+      return;
+  }
+
+  explicit_clients.push_back(conn);
+  deleteNotify(conn);
+}
+
+void afxChoreographer::removeExplicitClient(NetConnection* conn) 
+{ 
+  if (!conn)
+    return;
+
+  for (S32 i = 0; i < explicit_clients.size(); i++)
+  {
+    if (explicit_clients[i] == conn)
+    {
+      clearNotify(conn);
+      explicit_clients.erase_fast(i);
+      return;
+    }
+  }
+}
+
+void afxChoreographer::postProcessAfterObject(GameBase* obj)
+{
+  proc_after_obj = obj;
+}
+
+void afxChoreographer::setTriggerMask(U32 mask)
+{
+  if (mask != trigger_mask)
+  {
+    trigger_mask = mask;
+    setMaskBits(TriggerMask);
+  }
+}
+
+afxParticlePool* afxChoreographer::findParticlePool(afxParticlePoolData* key_block, U32 key_index) 
+{ 
+  for (S32 i = 0; i < particle_pools.size(); i++)
+    if (particle_pools[i] && particle_pools[i]->hasMatchingKeyBlock(key_block, key_index))
+      return particle_pools[i];
+
+  return 0; 
+}
+
+void afxChoreographer::registerParticlePool(afxParticlePool* pool) 
+{ 
+  particle_pools.push_back(pool);
+}
+
+void afxChoreographer::unregisterParticlePool(afxParticlePool* pool) 
+{ 
+  for (S32 i = 0; i < particle_pools.size(); i++)
+    if (particle_pools[i] == pool)
+    {
+      particle_pools.erase_fast(i);
+      return;
+    }
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+DefineEngineMethod( afxChoreographer, setRanking, void, ( unsigned int ranking ),,
+   "Set a ranking value (0-255) for the choreographer.\n" )
+{
+  object->setRanking((U8)ranking);
+}
+
+DefineEngineMethod( afxChoreographer, setLevelOfDetail, void, ( unsigned int lod ),,
+   "Set a level-of-detail value (0-255) for the choreographer.\n" )
+{
+  object->setLevelOfDetail((U8)lod);
+}
+
+DefineEngineMethod( afxChoreographer, setExecConditions, void, ( U32 mask ),,
+   "Set a bitmask to specifiy the state of exec-conditions.\n" )
+{
+  object->setExecConditions(afxChoreographer::USER_EXEC_CONDS_MASK & mask);
+}
+
+DefineEngineMethod( afxChoreographer, addConstraint, void, ( const char* source, const char* name),,
+   "Add a dynamic constraint consistiing of a source and name. The source can be a SceneObject, a 3-valued position, or a 7-valued transform.\n" )
+{
+  if (!object->addConstraint(source, name))
+    Con::errorf("afxChoreographer::addConstraint() -- failed to resolve constraint source [%s].", source);
+}
+
+DefineEngineMethod( afxChoreographer, addExplicitClient, void, ( NetConnection* client ),,
+   "Add an explicit client.\n" )
+{
+   if (!client)
+   {
+      Con::errorf(ConsoleLogEntry::General, "afxChoreographer::addExplicitClient: Failed to resolve client connection");
+      return;
+   }
+
+   object->addExplicitClient(client);
+}
+
+DefineEngineMethod( afxChoreographer, setTriggerBit, void, ( U32 bit_num ),,
+   "Set a bit of the trigger-mask.\n" )
+{
+  U32 set_bit = 1 << bit_num;
+  object->setTriggerMask(set_bit | object->getTriggerMask());
+}
+
+DefineEngineMethod( afxChoreographer, clearTriggerBit, void, ( U32 bit_num ),,
+   "Unset a bit of the trigger-mask.\n" )
+{
+  U32 clear_bit = 1 << bit_num;
+  object->setTriggerMask(~clear_bit & object->getTriggerMask());
+}
+
+DefineEngineMethod( afxChoreographer, testTriggerBit, bool, ( U32 bit_num ),,
+   "Test state of a trigger-mask bit.\n" )
+{
+  U32 test_bit = 1 << bit_num;
+  return ((test_bit & object->getTriggerMask()) != 0);
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+// CONSTRAINT REMAPPING <<
+
+void afxChoreographer::remapObjectConstraint(SceneObject* object, const char* cons_name)
+{
+  if (!object || !cons_name)
+    return;
+
+  // must be a constraint-def with a matching name in the list
+  dynConstraintDef* dyn_def = find_cons_def_by_name(cons_name);
+  if (!dyn_def)
+  {
+    Con::errorf("afxChoreographer::remapObjectConstraint() -- failed to find constraint name [%s].", cons_name);
+    return;
+  }
+
+  // constraint-def must have matching name constraint-type
+  if (dyn_def->cons_type != OBJECT_CONSTRAINT)
+  {
+    Con::errorf("afxChoreographer::remapObjectConstraint() -- remapped contraint type does not match existing constraint type.");
+    return;
+  }
+
+  // nothing to do if new object is same as old 
+  if (dyn_def->cons_obj.object == object)
+  {
+#ifdef TORQUE_DEBUG
+    Con::warnf("afxChoreographer::remapObjectConstraint() -- remapped contraint object is same as existing object.");
+#endif
+    return;
+  }
+
+  dyn_def->cons_obj.object = object;
+  deleteNotify(object);
+
+  constraint_mgr->setReferenceObject(StringTable->insert(cons_name), object);
+
+#if defined(AFX_CAP_SCOPE_TRACKING)
+  if (isServerObject() && object->isScopeable())
+    constraint_mgr->addScopeableObject(object);
+#endif
+
+  if (isServerObject())
+  {
+    if (remapped_cons_sent)
+    {
+      remapped_cons_defs.clear();
+      remapped_cons_sent = false;
+    }
+    remapped_cons_defs.push_back(dyn_def);
+    setMaskBits(RemapConstraintMask);
+  }
+}
+
+void afxChoreographer::remapObjectConstraint(U16 scope_id, const char* cons_name, bool is_shape)
+{
+  if (!cons_name)
+    return;
+
+  // must be a constraint-def with a matching name in the list
+  dynConstraintDef* dyn_def = find_cons_def_by_name(cons_name);
+  if (!dyn_def)
+  {
+    Con::errorf("afxChoreographer::remapObjectConstraint() -- failed to find constraint name [%s].", cons_name);
+    return;
+  }
+
+  // constraint-def must have matching name constraint-type
+  if (dyn_def->cons_type != OBJECT_CONSTRAINT)
+  {
+    Con::errorf("afxChoreographer::remapObjectConstraint() -- remapped contraint type does not match existing constraint type.");
+    return;
+  }
+
+  constraint_mgr->setReferenceObjectByScopeId(StringTable->insert(cons_name), scope_id, is_shape);
+}
+
+void afxChoreographer::remapPointConstraint(Point3F& point, const char* cons_name)
+{
+  // must be a constraint-def with a matching name in the list
+  dynConstraintDef* dyn_def = find_cons_def_by_name(cons_name);
+  if (!dyn_def)
+  {
+    Con::errorf("afxChoreographer::remapPointConstraint() -- failed to find constraint name [%s].", cons_name);
+    return;
+  }
+
+  // constraint-def must have matching name constraint-type
+  if (dyn_def->cons_type != POINT_CONSTRAINT)
+  {
+    Con::errorf("afxChoreographer::remapPointConstraint() -- remapped contraint type does not match existing constraint type.");
+    return;
+  }
+
+  *dyn_def->cons_obj.point = point;
+
+  constraint_mgr->setReferencePoint(StringTable->insert(cons_name), point);
+
+  if (isServerObject())
+  {
+    if (remapped_cons_sent)
+    {
+      remapped_cons_defs.clear();
+      remapped_cons_sent = false;
+    }
+    remapped_cons_defs.push_back(dyn_def);
+    setMaskBits(RemapConstraintMask);
+  }
+}
+
+void afxChoreographer::remapTransformConstraint(MatrixF& xfm, const char* cons_name)
+{
+  // must be a constraint-def with a matching name in the list
+  dynConstraintDef* dyn_def = find_cons_def_by_name(cons_name);
+  if (!dyn_def)
+  {
+    Con::errorf("afxChoreographer::remapTransformConstraint() -- failed to find constraint name [%s].", cons_name);
+    return;
+  }
+
+  // constraint-def must have matching name constraint-type
+  if (dyn_def->cons_type != POINT_CONSTRAINT)
+  {
+    Con::errorf("afxChoreographer::remapTransformConstraint() -- remapped contraint type does not match existing constraint type.");
+    return;
+  }
+
+  *dyn_def->cons_obj.xfm = xfm;
+
+  constraint_mgr->setReferenceTransform(StringTable->insert(cons_name), xfm);
+
+  if (isServerObject())
+  {
+    if (remapped_cons_sent)
+    {
+      remapped_cons_defs.clear();
+      remapped_cons_sent = false;
+    }
+    remapped_cons_defs.push_back(dyn_def);
+    setMaskBits(RemapConstraintMask);
+  }
+}
+
+bool afxChoreographer::remapConstraint(const char* source_spec, const char* cons_name)
+{
+  SceneObject* scn_obj;
+  Point3F pos;
+  MatrixF xfm;
+
+  switch (resolve_cons_spec(source_spec, pos, xfm, &scn_obj))
+  {
+  case TRANSFORM_CONSTRAINT:
+    //addTransformConstraint(xfm, cons_name);
+    return true;
+  case POINT_CONSTRAINT:
+    //addPointConstraint(pos, cons_name);
+    return true;
+  case OBJECT_CONSTRAINT:
+    remapObjectConstraint(scn_obj, cons_name);
+    return true;
+  }
+
+  return false;
+}
+
+DefineEngineMethod( afxChoreographer, remapConstraint, void, ( const char* source, const char* name),,
+   "Remap a dynamic constraint to use a new source. The source can be a SceneObject, a 3-valued position, or a 7-valued transform. but must match type of existing source.\n" )
+{
+  if (!object->remapConstraint(source, name))
+    Con::errorf("afxChoreographer::remapConstraint() -- failed to resolve constraint source [%s].", source);
+}
+
+// CONSTRAINT REMAPPING >>
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//

+ 222 - 0
Engine/source/afx/afxChoreographer.h

@@ -0,0 +1,222 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
+// Copyright (C) 2015 Faust Logic, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _AFX_CHOREOGRAPHER_H_
+#define _AFX_CHOREOGRAPHER_H_
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#include "afxEffectDefs.h"
+#include "afxEffectWrapper.h"
+#include "afxMagicMissile.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxChoreographerData
+
+class afxChoreographerData : public GameBaseData, public afxEffectDefs
+{
+  typedef GameBaseData  Parent;
+
+public:
+  bool              exec_on_new_clients;
+  U8                echo_packet_usage;
+  StringTableEntry  client_script_file;
+  StringTableEntry  client_init_func;
+
+public:
+  /*C*/         afxChoreographerData();
+  /*C*/         afxChoreographerData(const afxChoreographerData&, bool = false);
+
+  virtual void  packData(BitStream*);
+  virtual void  unpackData(BitStream*);
+
+  bool          preload(bool server, String &errorStr);
+
+  static void   initPersistFields();
+
+  DECLARE_CONOBJECT(afxChoreographerData);
+  DECLARE_CATEGORY("AFX");
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxChoreographer
+
+class afxConstraint;
+class afxConstraintMgr;
+class afxEffectWrapper;
+class afxParticlePool;
+class afxParticlePoolData;
+class SimSet;
+class afxForceSetMgr;
+
+class afxChoreographer : public GameBase, public afxEffectDefs, public afxMagicMissileCallback
+{
+  typedef GameBase  Parent;
+
+public:
+  enum MaskBits 
+  {
+    TriggerMask         = Parent::NextFreeMask << 0,
+    RemapConstraintMask = Parent::NextFreeMask << 1, // CONSTRAINT REMAPPING
+    NextFreeMask        = Parent::NextFreeMask << 2
+  };
+
+  enum 
+  { 
+    USER_EXEC_CONDS_MASK = 0x00ffffff
+  };
+
+protected:
+  struct dynConstraintDef
+  {
+    StringTableEntry  cons_name;
+    U8                cons_type;
+    union
+    {
+      SceneObject* object;
+      Point3F*     point;
+      MatrixF*     xfm;
+      U16          scope_id;
+    } cons_obj;
+  };
+
+private:
+  afxChoreographerData*    datablock;
+  SimSet                   named_effects;
+  SimObject*               exeblock;
+  afxForceSetMgr*          force_set_mgr;
+  Vector<afxParticlePool*> particle_pools;
+  Vector<dynConstraintDef> dc_defs_a;
+  Vector<dynConstraintDef> dc_defs_b;
+  GameBase*                proc_after_obj;
+  U32                      trigger_mask;
+
+protected:
+  Vector<dynConstraintDef>* dyn_cons_defs;
+  Vector<dynConstraintDef>* dyn_cons_defs2;
+  afxConstraintMgr* constraint_mgr;
+  U32               choreographer_id;
+  U8                ranking;
+  U8                lod;
+  U32               exec_conds_mask;
+  SimObject*        extra;
+  Vector<NetConnection*> explicit_clients;
+  bool              started_with_newop;
+  bool              postpone_activation;
+
+  virtual void      pack_constraint_info(NetConnection* conn, BitStream* stream);
+  virtual void      unpack_constraint_info(NetConnection* conn, BitStream* stream);
+  void              setup_dynamic_constraints();
+  void              check_packet_usage(NetConnection*, BitStream*, S32 mark_stream_pos, const char* msg_tag);
+  SceneObject*      get_camera(Point3F* cam_pos=0) const;
+
+public:
+  /*C*/             afxChoreographer();
+  virtual           ~afxChoreographer();
+
+  static void       initPersistFields();
+
+  virtual bool      onAdd();
+  virtual void      onRemove();
+  virtual void      onDeleteNotify(SimObject*);
+  virtual bool      onNewDataBlock(GameBaseData* dptr, bool reload);
+  virtual U32       packUpdate(NetConnection*, U32, BitStream*);
+  virtual void      unpackUpdate(NetConnection*, BitStream*);
+
+  virtual void      sync_with_clients() { }
+  
+  afxConstraintMgr* getConstraintMgr() { return constraint_mgr; }
+  afxForceSetMgr*   getForceSetMgr() { return force_set_mgr; }
+
+  afxParticlePool*  findParticlePool(afxParticlePoolData* key_block, U32 key_index);
+  void              registerParticlePool(afxParticlePool*);
+  void              unregisterParticlePool(afxParticlePool*);
+
+  void              setRanking(U8 value) { ranking = value; }
+  U8                getRanking() const { return ranking; }
+  bool              testRanking(U8 low, U8 high) { return (ranking <= high && ranking >= low); }
+  void              setLevelOfDetail(U8 value) { lod = value; }
+  U8                getLevelOfDetail() const { return lod; }
+  bool              testLevelOfDetail(U8 low, U8 high) { return (lod <= high && lod >= low); }
+  void              setExecConditions(U32 mask) { exec_conds_mask = mask; }
+  U32               getExecConditions() const { return exec_conds_mask; }
+
+  virtual void      executeScriptEvent(const char* method, afxConstraint*, 
+                                       const MatrixF& xfm, const char* data);
+
+  virtual void      inflictDamage(const char * label, const char* flavor, SimObjectId target,
+                                  F32 amt, U8 count, F32 ad_amt, F32 rad, Point3F pos, F32 imp) { }
+
+  void              addObjectConstraint(SceneObject*, const char* cons_name);
+  void              addObjectConstraint(U16 scope_id, const char* cons_name, bool is_shape);
+  void              addPointConstraint(Point3F&, const char* cons_name);
+  void              addTransformConstraint(MatrixF&, const char* cons_name);
+  bool              addConstraint(const char* source_spec, const char* cons_name);
+
+  void              addNamedEffect(afxEffectWrapper*);
+  void              removeNamedEffect(afxEffectWrapper*);
+  afxEffectWrapper* findNamedEffect(StringTableEntry);
+
+  void              clearChoreographerId() { choreographer_id = 0; }
+  U32               getChoreographerId() { return choreographer_id; }
+  void              setGhostConstraintObject(SceneObject*, StringTableEntry cons_name);
+  void              setExtra(SimObject* extra) { this->extra = extra; }
+  void              addExplicitClient(NetConnection* conn);
+  void              removeExplicitClient(NetConnection* conn);
+  U32               getExplicitClientCount() { return explicit_clients.size(); }
+
+  void              restoreScopedObject(SceneObject* obj);
+  virtual void      restoreObject(SceneObject*) { };
+
+  void              postProcessAfterObject(GameBase* obj);
+  U32               getTriggerMask() const { return trigger_mask; }
+  void              setTriggerMask(U32 trigger_mask);
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//
+// missile watcher callbacks
+public:
+  virtual void  impactNotify(const Point3F& p, const Point3F& n, SceneObject*) { }
+
+  DECLARE_CONOBJECT(afxChoreographer);
+  DECLARE_CATEGORY("AFX");
+  
+  // CONSTRAINT REMAPPING <<
+protected:
+  Vector<dynConstraintDef*> remapped_cons_defs;
+  bool              remapped_cons_sent;
+  virtual bool      remap_builtin_constraint(SceneObject*, const char* cons_name) { return false; }     
+  dynConstraintDef* find_cons_def_by_name(const char* cons_name);
+public:
+  void              remapObjectConstraint(SceneObject*, const char* cons_name);
+  void              remapObjectConstraint(U16 scope_id, const char* cons_name, bool is_shape);
+  void              remapPointConstraint(Point3F&, const char* cons_name);
+  void              remapTransformConstraint(MatrixF&, const char* cons_name);
+  bool              remapConstraint(const char* source_spec, const char* cons_name);
+  // CONSTRAINT REMAPPING >> 
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#endif // _AFX_CHOREOGRAPHER_H_

+ 2613 - 0
Engine/source/afx/afxConstraint.cpp

@@ -0,0 +1,2613 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
+// Copyright (C) 2015 Faust Logic, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#include "arcaneFX.h"
+
+#include "T3D/aiPlayer.h"
+#include "T3D/tsStatic.h"
+#include "sim/netConnection.h"
+#include "ts/tsShapeInstance.h"
+
+#include "afxConstraint.h"
+#include "afxChoreographer.h"
+#include "afxEffectWrapper.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxConstraintDef
+
+// static
+StringTableEntry  afxConstraintDef::SCENE_CONS_KEY;
+StringTableEntry  afxConstraintDef::EFFECT_CONS_KEY;
+StringTableEntry  afxConstraintDef::GHOST_CONS_KEY;
+
+afxConstraintDef::afxConstraintDef()
+{
+  if (SCENE_CONS_KEY == 0)
+  {
+    SCENE_CONS_KEY = StringTable->insert("#scene");
+    EFFECT_CONS_KEY = StringTable->insert("#effect");
+    GHOST_CONS_KEY = StringTable->insert("#ghost");
+  }
+
+  reset();
+}
+
+bool afxConstraintDef::isDefined() 
+{
+  return (def_type != CONS_UNDEFINED); 
+}
+
+bool afxConstraintDef::isArbitraryObject() 
+{ 
+  return ((cons_src_name != ST_NULLSTRING) && (def_type == CONS_SCENE)); 
+}
+
+void afxConstraintDef::reset()
+{
+  cons_src_name = ST_NULLSTRING;
+  cons_node_name = ST_NULLSTRING;
+  def_type = CONS_UNDEFINED;
+  history_time = 0;
+  sample_rate = 30;
+  runs_on_server = false;
+  runs_on_client = false;
+  pos_at_box_center = false;
+  treat_as_camera = false;
+}
+
+bool afxConstraintDef::parseSpec(const char* spec, bool runs_on_server, 
+                                 bool runs_on_client)
+{
+  reset();
+
+  if (spec == 0 || spec[0] == '\0')
+    return false;
+
+  history_time = 0.0f;
+  sample_rate = 30;
+
+  this->runs_on_server = runs_on_server;
+  this->runs_on_client = runs_on_client;
+
+  // spec should be in one of these forms:
+  //    CONSTRAINT_NAME (only)
+  //    CONSTRAINT_NAME.NODE (shapeBase objects only)
+  //    CONSTRAINT_NAME.#center
+  //    object.OBJECT_NAME
+  //    object.OBJECT_NAME.NODE (shapeBase objects only)
+  //    object.OBJECT_NAME.#center
+  //    effect.EFFECT_NAME
+  //    effect.EFFECT_NAME.NODE
+  //    effect.EFFECT_NAME.#center
+  //    #ghost.EFFECT_NAME
+  //    #ghost.EFFECT_NAME.NODE
+  //    #ghost.EFFECT_NAME.#center
+  //
+
+  // create scratch buffer by duplicating spec.
+  char special = '\b';
+  char* buffer = dStrdup(spec);
+
+  // substitute a dots not inside parens with special character
+  S32 n_nested = 0;
+  for (char* b = buffer; (*b) != '\0'; b++)
+  {
+    if ((*b) == '(')
+      n_nested++;
+    else if ((*b) == ')')
+      n_nested--;
+    else if ((*b) == '.' && n_nested == 0)
+      (*b) = special;
+  }
+
+  // divide name into '.' separated tokens (up to 8)
+  char* words[8] = {0, 0, 0, 0, 0, 0, 0, 0};
+  char* dot = buffer;
+  int wdx = 0;
+  while (wdx < 8)
+  {
+    words[wdx] = dot;
+    dot = dStrchr(words[wdx++], special);
+    if (!dot)
+      break;
+    *(dot++) = '\0';
+    if ((*dot) == '\0')
+      break;
+  }
+
+  int n_words = wdx;
+
+  // at this point the spec has been split into words. 
+  // n_words indicates how many words we have.
+
+  // no words found (must have been all whitespace)
+  if (n_words < 1)
+  {
+    dFree(buffer);
+    return false;
+  }
+
+  char* hist_spec = 0;
+  char* words2[8] = {0, 0, 0, 0, 0, 0, 0, 0};
+  int n_words2 = 0;
+
+  // move words to words2 while extracting #center and #history
+  for (S32 i = 0; i < n_words; i++)
+  {
+    if (dStrcmp(words[i], "#center") == 0)
+      pos_at_box_center = true;
+    else if (dStrncmp(words[i], "#history(", 9) == 0)
+      hist_spec = words[i];
+    else
+      words2[n_words2++] = words[i];
+  }
+
+  // words2[] now contains just the constraint part
+
+  // no words found (must have been all #center and #history)
+  if (n_words2 < 1)
+  {
+    dFree(buffer);
+    return false;
+  }
+
+  if (hist_spec)
+  {
+    char* open_paren = dStrchr(hist_spec, '(');
+    if (open_paren)
+    {
+      hist_spec = open_paren+1;
+      if ((*hist_spec) != '\0')
+      {
+        char* close_paren = dStrchr(hist_spec, ')');
+        if (close_paren)
+          (*close_paren) = '\0';
+        char* slash = dStrchr(hist_spec, '/');
+        if (slash)
+          (*slash) = ' ';
+
+        F32 hist_age = 0.0;
+        U32 hist_rate = 30;
+        S32 args = dSscanf(hist_spec,"%g %d", &hist_age, &hist_rate);
+
+        if (args > 0)
+          history_time = hist_age;
+        if (args > 1)
+          sample_rate = hist_rate;
+      }
+    }
+  }
+
+  StringTableEntry cons_name_key = StringTable->insert(words2[0]);
+
+  // must be in CONSTRAINT_NAME (only) form
+  if (n_words2 == 1)
+  {
+    // arbitrary object/effect constraints must have a name
+    if (cons_name_key == SCENE_CONS_KEY || cons_name_key == EFFECT_CONS_KEY)
+    {
+      dFree(buffer);
+      return false;
+    }
+
+    cons_src_name = cons_name_key;
+    def_type = CONS_PREDEFINED;
+    dFree(buffer);
+    return true;
+  }
+
+  // "#scene.NAME" or "#scene.NAME.NODE""
+  if (cons_name_key == SCENE_CONS_KEY)
+  {
+    cons_src_name = StringTable->insert(words2[1]);
+    if (n_words2 > 2)
+      cons_node_name = StringTable->insert(words2[2]);
+    def_type = CONS_SCENE;
+    dFree(buffer);
+    return true;
+  }
+
+  // "#effect.NAME" or "#effect.NAME.NODE"
+  if (cons_name_key == EFFECT_CONS_KEY)
+  {
+    cons_src_name = StringTable->insert(words2[1]);
+    if (n_words2 > 2)
+      cons_node_name = StringTable->insert(words2[2]);
+    def_type = CONS_EFFECT;
+    dFree(buffer);
+    return true;
+  }
+
+  // "#ghost.NAME" or "#ghost.NAME.NODE"
+  if (cons_name_key == GHOST_CONS_KEY)
+  {
+    if (runs_on_server)
+    {
+      dFree(buffer);
+      return false;
+    }
+
+    cons_src_name = StringTable->insert(words2[1]);
+    if (n_words2 > 2)
+      cons_node_name = StringTable->insert(words2[2]);
+    def_type = CONS_GHOST;
+    dFree(buffer);
+    return true;
+  }
+
+  // "CONSTRAINT_NAME.NODE"
+  if (n_words2 == 2)
+  {
+    cons_src_name = cons_name_key;
+    cons_node_name = StringTable->insert(words2[1]);
+    def_type = CONS_PREDEFINED;
+    dFree(buffer);
+    return true;
+  }
+
+  // must be in unsupported form
+  dFree(buffer); 
+  return false;
+}
+
+void afxConstraintDef::gather_cons_defs(Vector<afxConstraintDef>& defs, afxEffectList& fx)
+{
+  for (S32 i = 0; i <  fx.size(); i++)
+  {
+    if (fx[i])
+      fx[i]->gather_cons_defs(defs);
+  }
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxConstraint
+
+afxConstraint::afxConstraint(afxConstraintMgr* mgr)
+{
+  this->mgr = mgr;
+  is_defined = false;
+  is_valid = false;
+  last_pos.zero();
+  last_xfm.identity();
+  history_time = 0.0f;
+  is_alive = true;
+  gone_missing = false;
+  change_code = 0;
+}
+
+afxConstraint::~afxConstraint()
+{
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+inline afxPointConstraint* newPointCons(afxConstraintMgr* mgr, bool hist)
+{
+  return (hist) ? new afxPointHistConstraint(mgr) : new afxPointConstraint(mgr);
+}
+
+inline afxTransformConstraint* newTransformCons(afxConstraintMgr* mgr, bool hist)
+{
+  return (hist) ? new afxTransformHistConstraint(mgr) : new afxTransformConstraint(mgr);
+}
+
+inline afxShapeConstraint* newShapeCons(afxConstraintMgr* mgr, bool hist)
+{
+  return (hist) ? new afxShapeHistConstraint(mgr) : new afxShapeConstraint(mgr);
+}
+
+inline afxShapeConstraint* newShapeCons(afxConstraintMgr* mgr, StringTableEntry name, bool hist)
+{
+  return (hist) ? new afxShapeHistConstraint(mgr, name) : new afxShapeConstraint(mgr, name);
+}
+
+inline afxShapeNodeConstraint* newShapeNodeCons(afxConstraintMgr* mgr, StringTableEntry name, StringTableEntry node, bool hist)
+{
+  return (hist) ? new afxShapeNodeHistConstraint(mgr, name, node) : new afxShapeNodeConstraint(mgr, name, node);
+}
+
+inline afxObjectConstraint* newObjectCons(afxConstraintMgr* mgr, bool hist)
+{
+  return (hist) ? new afxObjectHistConstraint(mgr) : new afxObjectConstraint(mgr);
+}
+
+inline afxObjectConstraint* newObjectCons(afxConstraintMgr* mgr, StringTableEntry name, bool hist)
+{
+  return (hist) ? new afxObjectHistConstraint(mgr, name) : new afxObjectConstraint(mgr, name);
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxConstraintMgr
+
+#define CONS_BY_ID(id) ((*constraints_v[(id).index])[(id).sub_index])
+#define CONS_BY_IJ(i,j) ((*constraints_v[(i)])[(j)])
+
+afxConstraintMgr::afxConstraintMgr()
+{
+  starttime = 0;
+  on_server = false;
+  initialized = false;
+  scoping_dist_sq = 1000.0f*1000.0f;
+  missing_objs = &missing_objs_a;
+  missing_objs2 = &missing_objs_b;
+}
+
+afxConstraintMgr::~afxConstraintMgr()
+{
+  for (S32 i = 0; i < constraints_v.size(); i++)
+  {
+    for (S32 j = 0; j < (*constraints_v[i]).size(); j++)
+      delete CONS_BY_IJ(i,j);
+    delete constraints_v[i];
+  }
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//
+
+S32 afxConstraintMgr::find_cons_idx_from_name(StringTableEntry which)
+{
+  for (S32 i = 0; i < constraints_v.size(); i++)
+  {
+    afxConstraint* cons = CONS_BY_IJ(i,0);
+    if (cons && afxConstraintDef::CONS_EFFECT != cons->cons_def.def_type && 
+        which == cons->cons_def.cons_src_name)
+    {
+        return i;
+    }
+  }
+
+  return -1;
+}
+
+S32 afxConstraintMgr::find_effect_cons_idx_from_name(StringTableEntry which)
+{
+  for (S32 i = 0; i < constraints_v.size(); i++)
+  {
+    afxConstraint* cons = CONS_BY_IJ(i,0);
+    if (cons && afxConstraintDef::CONS_EFFECT == cons->cons_def.def_type && 
+        which == cons->cons_def.cons_src_name)
+    {
+      return i;
+    }
+  }
+
+  return -1;
+}
+
+// Defines a predefined constraint with given name and type
+void afxConstraintMgr::defineConstraint(U32 type, StringTableEntry name)
+{
+  preDef predef = { name, type };
+  predefs.push_back(predef);
+}
+
+afxConstraintID afxConstraintMgr::setReferencePoint(StringTableEntry which, Point3F point, 
+                                                    Point3F vector)
+{
+  S32 idx = find_cons_idx_from_name(which);
+  if (idx < 0)
+    return afxConstraintID();
+
+  afxConstraintID id = afxConstraintID(idx);
+  setReferencePoint(id, point, vector); 
+
+  return id;
+}
+
+afxConstraintID afxConstraintMgr::setReferenceTransform(StringTableEntry which, MatrixF& xfm)
+{
+  S32 idx = find_cons_idx_from_name(which);
+  if (idx < 0)
+    return afxConstraintID();
+
+  afxConstraintID id = afxConstraintID(idx);
+  setReferenceTransform(id, xfm); 
+
+  return id;
+}
+
+// Assigns an existing scene-object to the named constraint
+afxConstraintID afxConstraintMgr::setReferenceObject(StringTableEntry which, SceneObject* obj)
+{
+  S32 idx = find_cons_idx_from_name(which);
+  if (idx < 0)
+    return afxConstraintID();
+
+  afxConstraintID id = afxConstraintID(idx);
+  setReferenceObject(id, obj); 
+
+  return id;
+}
+
+// Assigns an un-scoped scene-object by scope_id to the named constraint
+afxConstraintID afxConstraintMgr::setReferenceObjectByScopeId(StringTableEntry which, U16 scope_id, bool is_shape)
+{
+  S32 idx = find_cons_idx_from_name(which);
+  if (idx < 0)
+    return afxConstraintID();
+
+  afxConstraintID id = afxConstraintID(idx);
+  setReferenceObjectByScopeId(id, scope_id, is_shape); 
+
+  return id;
+}
+
+afxConstraintID afxConstraintMgr::setReferenceEffect(StringTableEntry which, afxEffectWrapper* ew)
+{
+  S32 idx = find_effect_cons_idx_from_name(which);
+
+  if (idx < 0)
+    return afxConstraintID();
+
+  afxConstraintID id = afxConstraintID(idx);
+  setReferenceEffect(id, ew); 
+
+  return id;
+}
+
+afxConstraintID afxConstraintMgr::createReferenceEffect(StringTableEntry which, afxEffectWrapper* ew)
+{
+  afxEffectConstraint* cons = new afxEffectConstraint(this, which);
+  //cons->cons_def = def;
+  cons->cons_def.def_type = afxConstraintDef::CONS_EFFECT;
+  cons->cons_def.cons_src_name = which;
+  afxConstraintList* list = new afxConstraintList();
+  list->push_back(cons);
+  constraints_v.push_back(list);
+
+  return setReferenceEffect(which, ew);
+}
+
+void afxConstraintMgr::setReferencePoint(afxConstraintID id, Point3F point, Point3F vector)
+{
+  afxPointConstraint* pt_cons = dynamic_cast<afxPointConstraint*>(CONS_BY_ID(id));
+
+  // need to change type
+  if (!pt_cons)
+  {
+    afxConstraint* cons = CONS_BY_ID(id);
+    pt_cons = newPointCons(this, cons->cons_def.history_time > 0.0f);
+    pt_cons->cons_def = cons->cons_def;
+    CONS_BY_ID(id) = pt_cons;
+    delete cons;
+  }
+
+  pt_cons->set(point, vector);
+
+  // nullify all subnodes
+  for (S32 j = 1; j < (*constraints_v[id.index]).size(); j++)
+  {
+    afxConstraint* cons = CONS_BY_IJ(id.index,j);
+    if (cons)
+      cons->unset();
+  }
+}
+
+void afxConstraintMgr::setReferenceTransform(afxConstraintID id, MatrixF& xfm)
+{
+  afxTransformConstraint* xfm_cons = dynamic_cast<afxTransformConstraint*>(CONS_BY_ID(id));
+
+  // need to change type
+  if (!xfm_cons)
+  {
+    afxConstraint* cons = CONS_BY_ID(id);
+    xfm_cons = newTransformCons(this, cons->cons_def.history_time > 0.0f);
+    xfm_cons->cons_def = cons->cons_def;
+    CONS_BY_ID(id) = xfm_cons;
+    delete cons;
+  }
+
+  xfm_cons->set(xfm);
+
+  // nullify all subnodes
+  for (S32 j = 1; j < (*constraints_v[id.index]).size(); j++)
+  {
+    afxConstraint* cons = CONS_BY_IJ(id.index,j);
+    if (cons)
+      cons->unset();
+  }
+}
+
+void afxConstraintMgr::set_ref_shape(afxConstraintID id, ShapeBase* shape)
+{
+  id.sub_index = 0;
+
+  afxShapeConstraint* shape_cons = dynamic_cast<afxShapeConstraint*>(CONS_BY_ID(id));
+
+  // need to change type
+  if (!shape_cons)
+  {
+    afxConstraint* cons = CONS_BY_ID(id);
+    shape_cons = newShapeCons(this, cons->cons_def.history_time > 0.0f);
+    shape_cons->cons_def = cons->cons_def;
+    CONS_BY_ID(id) = shape_cons;
+    delete cons;
+  }
+
+  // set new shape on root 
+  shape_cons->set(shape);
+
+  // update all subnodes
+  for (S32 j = 1; j < (*constraints_v[id.index]).size(); j++)
+  {
+    afxConstraint* cons = CONS_BY_IJ(id.index,j);
+    if (cons)
+    {
+      if (dynamic_cast<afxShapeNodeConstraint*>(cons))
+        ((afxShapeNodeConstraint*)cons)->set(shape);
+      else if (dynamic_cast<afxShapeConstraint*>(cons))
+        ((afxShapeConstraint*)cons)->set(shape);
+      else if (dynamic_cast<afxObjectConstraint*>(cons))
+        ((afxObjectConstraint*)cons)->set(shape);
+      else
+        cons->unset();
+    }
+  }
+}
+
+void afxConstraintMgr::set_ref_shape(afxConstraintID id, U16 scope_id)
+{
+  id.sub_index = 0;
+
+  afxShapeConstraint* shape_cons = dynamic_cast<afxShapeConstraint*>(CONS_BY_ID(id));
+
+  // need to change type
+  if (!shape_cons)
+  {
+    afxConstraint* cons = CONS_BY_ID(id);
+    shape_cons = newShapeCons(this, cons->cons_def.history_time > 0.0f);
+    shape_cons->cons_def = cons->cons_def;
+    CONS_BY_ID(id) = shape_cons;
+    delete cons;
+  }
+
+  // set new shape on root 
+  shape_cons->set_scope_id(scope_id);
+
+  // update all subnodes
+  for (S32 j = 1; j < (*constraints_v[id.index]).size(); j++)
+  {
+    afxConstraint* cons = CONS_BY_IJ(id.index,j);
+    if (cons)
+      cons->set_scope_id(scope_id);
+  }
+}
+
+// Assigns an existing scene-object to the constraint matching the given constraint-id.
+void afxConstraintMgr::setReferenceObject(afxConstraintID id, SceneObject* obj)
+{
+  if (!initialized)
+    Con::errorf("afxConstraintMgr::setReferenceObject() -- constraint manager not initialized");
+
+  if (!CONS_BY_ID(id)->cons_def.treat_as_camera)
+  {
+    ShapeBase* shape = dynamic_cast<ShapeBase*>(obj);
+    if (shape)
+    {
+      set_ref_shape(id, shape);
+      return;
+    }
+  }
+
+  afxObjectConstraint* obj_cons = dynamic_cast<afxObjectConstraint*>(CONS_BY_ID(id));
+
+  // need to change type
+  if (!obj_cons)
+  {
+    afxConstraint* cons = CONS_BY_ID(id);
+    obj_cons = newObjectCons(this, cons->cons_def.history_time > 0.0f);
+    obj_cons->cons_def = cons->cons_def;
+    CONS_BY_ID(id) = obj_cons;
+    delete cons;
+  }
+
+  obj_cons->set(obj);
+
+  // update all subnodes
+  for (S32 j = 1; j < (*constraints_v[id.index]).size(); j++)
+  {
+    afxConstraint* cons = CONS_BY_IJ(id.index,j);
+    if (cons)
+    {
+      if (dynamic_cast<afxObjectConstraint*>(cons))
+          ((afxObjectConstraint*)cons)->set(obj);
+      else
+        cons->unset();
+    }
+  }
+}
+
+// Assigns an un-scoped scene-object by scope_id to the constraint matching the 
+// given constraint-id.
+void afxConstraintMgr::setReferenceObjectByScopeId(afxConstraintID id, U16 scope_id, bool is_shape)
+{
+  if (!initialized)
+    Con::errorf("afxConstraintMgr::setReferenceObject() -- constraint manager not initialized");
+
+  if (is_shape)
+  {
+    set_ref_shape(id, scope_id);
+    return;
+  }
+
+  afxObjectConstraint* obj_cons = dynamic_cast<afxObjectConstraint*>(CONS_BY_ID(id));
+
+  // need to change type
+  if (!obj_cons)
+  {
+    afxConstraint* cons = CONS_BY_ID(id);
+    obj_cons = newObjectCons(this, cons->cons_def.history_time > 0.0f);
+    obj_cons->cons_def = cons->cons_def;
+    CONS_BY_ID(id) = obj_cons;
+    delete cons;
+  }
+
+  obj_cons->set_scope_id(scope_id);
+
+  // update all subnodes
+  for (S32 j = 1; j < (*constraints_v[id.index]).size(); j++)
+  {
+    afxConstraint* cons = CONS_BY_IJ(id.index,j);
+    if (cons)
+      cons->set_scope_id(scope_id);
+  }
+}
+
+void afxConstraintMgr::setReferenceEffect(afxConstraintID id, afxEffectWrapper* ew)
+{
+  afxEffectConstraint* eff_cons = dynamic_cast<afxEffectConstraint*>(CONS_BY_ID(id));
+  if (!eff_cons)
+    return;
+
+  eff_cons->set(ew);
+
+  // update all subnodes
+  for (S32 j = 1; j < (*constraints_v[id.index]).size(); j++)
+  {
+    afxConstraint* cons = CONS_BY_IJ(id.index,j);
+    if (cons)
+    {
+      if (dynamic_cast<afxEffectNodeConstraint*>(cons))
+        ((afxEffectNodeConstraint*)cons)->set(ew);
+      else if (dynamic_cast<afxEffectConstraint*>(cons))
+        ((afxEffectConstraint*)cons)->set(ew);
+      else
+        cons->unset();
+    }
+  }
+}
+
+void afxConstraintMgr::invalidateReference(afxConstraintID id)
+{
+  afxConstraint* cons = CONS_BY_ID(id);
+  if (cons)
+    cons->is_valid = false;
+}
+
+void afxConstraintMgr::create_constraint(const afxConstraintDef& def)
+{
+  if (def.def_type == afxConstraintDef::CONS_UNDEFINED)
+    return;
+
+  //Con::printf("CON - %s [%s] [%s] h=%g", def.cons_type_name, def.cons_src_name, def.cons_node_name, def.history_time);
+
+  bool want_history = (def.history_time > 0.0f);
+
+  // constraint is an arbitrary named scene object
+  //
+  if (def.def_type == afxConstraintDef::CONS_SCENE)
+  {
+    if (def.cons_src_name == ST_NULLSTRING)
+      return;
+
+    // find the arbitrary object by name
+    SceneObject* arb_obj;
+    if (on_server)
+    {
+      arb_obj = dynamic_cast<SceneObject*>(Sim::findObject(def.cons_src_name));
+      if (!arb_obj)
+         Con::errorf("afxConstraintMgr -- failed to find scene constraint source, \"%s\" on server.", 
+                     def.cons_src_name);
+    }
+    else
+    {
+      arb_obj = find_object_from_name(def.cons_src_name);
+      if (!arb_obj)
+         Con::errorf("afxConstraintMgr -- failed to find scene constraint source, \"%s\" on client.", 
+                     def.cons_src_name);
+    }
+
+    // if it's a shapeBase object, create a Shape or ShapeNode constraint
+    if (dynamic_cast<ShapeBase*>(arb_obj))
+    {
+      if (def.cons_node_name == ST_NULLSTRING && !def.pos_at_box_center)
+      {
+        afxShapeConstraint* cons = newShapeCons(this, def.cons_src_name, want_history);
+        cons->cons_def = def;
+        cons->set((ShapeBase*)arb_obj); 
+        afxConstraintList* list = new afxConstraintList();
+        list->push_back(cons);
+        constraints_v.push_back(list);
+      }
+      else if (def.pos_at_box_center)
+      {
+        afxShapeConstraint* cons = newShapeCons(this, def.cons_src_name, want_history);
+        cons->cons_def = def;
+        cons->set((ShapeBase*)arb_obj); 
+        afxConstraintList* list = constraints_v[constraints_v.size()-1]; // SHAPE-NODE CONS-LIST (#scene)(#center)
+        if (list && (*list)[0])
+          list->push_back(cons);
+      }
+      else
+      {
+        afxShapeNodeConstraint* sub = newShapeNodeCons(this, def.cons_src_name, def.cons_node_name, want_history);
+        sub->cons_def = def;
+        sub->set((ShapeBase*)arb_obj); 
+        afxConstraintList* list = constraints_v[constraints_v.size()-1];
+        if (list && (*list)[0])
+          list->push_back(sub);
+      }
+    }
+    // if it's not a shapeBase object, create an Object constraint
+    else if (arb_obj)
+    {
+      if (!def.pos_at_box_center)
+      {
+        afxObjectConstraint* cons = newObjectCons(this, def.cons_src_name, want_history);
+        cons->cons_def = def;
+        cons->set(arb_obj);
+        afxConstraintList* list = new afxConstraintList(); // OBJECT CONS-LIST (#scene)
+        list->push_back(cons);
+        constraints_v.push_back(list);
+      }
+      else // if (def.pos_at_box_center)
+      {
+        afxObjectConstraint* cons = newObjectCons(this, def.cons_src_name, want_history);
+        cons->cons_def = def;
+        cons->set(arb_obj); 
+        afxConstraintList* list = constraints_v[constraints_v.size()-1]; // OBJECT CONS-LIST (#scene)(#center)
+        if (list && (*list)[0])
+          list->push_back(cons);
+      }
+    }
+  }
+
+  // constraint is an arbitrary named effect
+  //
+  else if (def.def_type == afxConstraintDef::CONS_EFFECT)
+  {
+    if (def.cons_src_name == ST_NULLSTRING)
+      return;
+
+    // create an Effect constraint
+    if (def.cons_node_name == ST_NULLSTRING && !def.pos_at_box_center)
+    {
+      afxEffectConstraint* cons = new afxEffectConstraint(this, def.cons_src_name);
+      cons->cons_def = def;
+      afxConstraintList* list = new afxConstraintList();
+      list->push_back(cons);
+      constraints_v.push_back(list);
+    }
+    // create an Effect #center constraint
+    else if (def.pos_at_box_center)
+    {
+      afxEffectConstraint* cons = new afxEffectConstraint(this, def.cons_src_name);
+      cons->cons_def = def;
+      afxConstraintList* list = constraints_v[constraints_v.size()-1]; // EFFECT-NODE CONS-LIST (#effect)
+      if (list && (*list)[0])
+        list->push_back(cons);
+    }
+    // create an EffectNode constraint
+    else
+    {
+      afxEffectNodeConstraint* sub = new afxEffectNodeConstraint(this, def.cons_src_name, def.cons_node_name);
+      sub->cons_def = def;
+      afxConstraintList* list = constraints_v[constraints_v.size()-1];
+      if (list && (*list)[0])
+        list->push_back(sub);
+    }
+  }
+
+  // constraint is a predefined constraint
+  //
+  else
+  {
+    afxConstraint* cons = 0;
+    afxConstraint* cons_ctr = 0;
+    afxConstraint* sub = 0;
+
+    if (def.def_type == afxConstraintDef::CONS_GHOST)
+    {
+      for (S32 i = 0; i < predefs.size(); i++)
+      {
+        if (predefs[i].name == def.cons_src_name)
+        {
+          if (def.cons_node_name == ST_NULLSTRING && !def.pos_at_box_center)
+          {
+            cons = newShapeCons(this, want_history);
+            cons->cons_def = def;
+          }
+          else if (def.pos_at_box_center)
+          {
+            cons_ctr = newShapeCons(this, want_history);
+            cons_ctr->cons_def = def;
+          }
+          else
+          {
+            sub = newShapeNodeCons(this, ST_NULLSTRING, def.cons_node_name, want_history);
+            sub->cons_def = def;
+          }
+          break;
+        }
+      }
+    }
+    else
+    {
+      for (S32 i = 0; i < predefs.size(); i++)
+      {
+        if (predefs[i].name == def.cons_src_name)
+        {
+          switch (predefs[i].type)
+          {
+          case POINT_CONSTRAINT:
+            cons = newPointCons(this, want_history);
+            cons->cons_def = def;
+            break;
+          case TRANSFORM_CONSTRAINT:
+            cons = newTransformCons(this, want_history);
+            cons->cons_def = def;
+            break;
+          case OBJECT_CONSTRAINT:
+            if (def.cons_node_name == ST_NULLSTRING && !def.pos_at_box_center)
+            {
+              cons = newShapeCons(this, want_history);
+              cons->cons_def = def;
+            }
+            else if (def.pos_at_box_center)
+            {
+              cons_ctr = newShapeCons(this, want_history);
+              cons_ctr->cons_def = def;
+            }
+            else
+            {
+              sub = newShapeNodeCons(this, ST_NULLSTRING, def.cons_node_name, want_history);
+              sub->cons_def = def;
+            }
+            break;
+          case CAMERA_CONSTRAINT:
+            cons = newObjectCons(this, want_history);
+            cons->cons_def = def;
+            cons->cons_def.treat_as_camera = true;
+            break;
+          }
+          break;
+        }
+      }
+    }
+
+    if (cons)
+    {
+      afxConstraintList* list = new afxConstraintList();
+      list->push_back(cons);
+      constraints_v.push_back(list);
+    }
+    else if (cons_ctr && constraints_v.size() > 0)
+    {
+      afxConstraintList* list = constraints_v[constraints_v.size()-1]; // PREDEF-NODE CONS-LIST
+      if (list && (*list)[0])
+        list->push_back(cons_ctr);
+    }
+    else if (sub && constraints_v.size() > 0)
+    {
+      afxConstraintList* list = constraints_v[constraints_v.size()-1];
+      if (list && (*list)[0])
+        list->push_back(sub);
+    }
+    else
+      Con::printf("predef not found %s", def.cons_src_name);
+  }
+}
+
+afxConstraintID afxConstraintMgr::getConstraintId(const afxConstraintDef& def)
+{
+  if (def.def_type == afxConstraintDef::CONS_UNDEFINED)
+    return afxConstraintID();
+
+  if (def.cons_src_name != ST_NULLSTRING)
+  {
+    for (S32 i = 0; i < constraints_v.size(); i++)
+    {
+      afxConstraintList* list = constraints_v[i];
+      afxConstraint* cons = (*list)[0];
+      if (def.cons_src_name == cons->cons_def.cons_src_name)
+      {
+        for (S32 j = 0; j < list->size(); j++)
+        {
+          afxConstraint* sub = (*list)[j];
+          if (def.cons_node_name == sub->cons_def.cons_node_name && 
+              def.pos_at_box_center == sub->cons_def.pos_at_box_center && 
+              def.cons_src_name == sub->cons_def.cons_src_name)
+          {
+            return afxConstraintID(i, j);
+          }
+        }
+
+        // if we're here, it means the root object name matched but the node name
+        // did not.
+        if (def.def_type == afxConstraintDef::CONS_PREDEFINED && !def.pos_at_box_center)
+        {
+          afxShapeConstraint* shape_cons = dynamic_cast<afxShapeConstraint*>(cons);
+          if (shape_cons)
+          {
+             //Con::errorf("Append a Node constraint [%s.%s] [%d,%d]", def.cons_src_name, def.cons_node_name, i, list->size());
+             bool want_history = (def.history_time > 0.0f);
+             afxConstraint* sub = newShapeNodeCons(this, ST_NULLSTRING, def.cons_node_name, want_history);
+             sub->cons_def = def;
+             ((afxShapeConstraint*)sub)->set(shape_cons->shape);
+             list->push_back(sub);
+
+             return afxConstraintID(i, list->size()-1);
+          }
+        }
+
+        break;
+      }
+    }
+  }
+
+  return afxConstraintID();
+}
+
+afxConstraint* afxConstraintMgr::getConstraint(afxConstraintID id)
+{
+  if (id.undefined())
+    return 0;
+
+  afxConstraint* cons = CONS_BY_IJ(id.index,id.sub_index);
+  if (cons && !cons->isDefined())
+    return NULL;
+
+  return cons;
+}
+
+void afxConstraintMgr::sample(F32 dt, U32 now, const Point3F* cam_pos)
+{
+  U32 elapsed = now - starttime;
+
+  for (S32 i = 0; i < constraints_v.size(); i++)
+  {
+    afxConstraintList* list = constraints_v[i];
+    for (S32 j = 0; j < list->size(); j++)
+      (*list)[j]->sample(dt, elapsed, cam_pos);
+  }
+}
+
+S32 QSORT_CALLBACK cmp_cons_defs(const void* a, const void* b)
+{
+  afxConstraintDef* def_a = (afxConstraintDef*) a;
+  afxConstraintDef* def_b = (afxConstraintDef*) b;
+
+  if (def_a->def_type == def_b->def_type)
+  {
+    if (def_a->cons_src_name == def_b->cons_src_name)
+    {
+      if (def_a->pos_at_box_center == def_b->pos_at_box_center)
+        return (def_a->cons_node_name - def_b->cons_node_name);
+      else
+        return (def_a->pos_at_box_center) ? 1 : -1;
+    }
+    return (def_a->cons_src_name - def_b->cons_src_name);
+  }
+
+  return (def_a->def_type - def_b->def_type);
+}
+
+void afxConstraintMgr::initConstraintDefs(Vector<afxConstraintDef>& all_defs, bool on_server, F32 scoping_dist)
+{
+  initialized = true;
+  this->on_server = on_server;
+
+  if (scoping_dist > 0.0)
+    scoping_dist_sq = scoping_dist*scoping_dist;
+  else
+  {
+    SceneManager* sg = (on_server) ? gServerSceneGraph : gClientSceneGraph;
+    F32 vis_dist = (sg) ? sg->getVisibleDistance() : 1000.0f;
+    scoping_dist_sq = vis_dist*vis_dist;
+  }
+
+  if (all_defs.size() < 1)
+    return;
+
+  // find effect ghost constraints
+  if (!on_server)
+  {
+    Vector<afxConstraintDef> ghost_defs;
+
+    for (S32 i = 0; i < all_defs.size(); i++)
+      if (all_defs[i].def_type == afxConstraintDef::CONS_GHOST && all_defs[i].cons_src_name != ST_NULLSTRING)
+        ghost_defs.push_back(all_defs[i]);
+    
+    if (ghost_defs.size() > 0)
+    {
+      // sort the defs
+      if (ghost_defs.size() > 1)
+        dQsort(ghost_defs.address(), ghost_defs.size(), sizeof(afxConstraintDef), cmp_cons_defs);
+      
+      S32 last = 0;
+      defineConstraint(OBJECT_CONSTRAINT, ghost_defs[0].cons_src_name);
+
+      for (S32 i = 1; i < ghost_defs.size(); i++)
+      {
+        if (ghost_defs[last].cons_src_name != ghost_defs[i].cons_src_name)
+        {
+          defineConstraint(OBJECT_CONSTRAINT, ghost_defs[i].cons_src_name);
+          last++;
+        }
+      }
+    }
+  }
+
+  Vector<afxConstraintDef> defs;
+
+  // collect defs that run here (server or client)
+  if (on_server)
+  {
+    for (S32 i = 0; i < all_defs.size(); i++)
+      if (all_defs[i].runs_on_server)
+        defs.push_back(all_defs[i]);
+  }
+  else
+  {
+    for (S32 i = 0; i < all_defs.size(); i++)
+      if (all_defs[i].runs_on_client)
+        defs.push_back(all_defs[i]);
+  }
+
+  // create unique set of constraints.
+  //
+  if (defs.size() > 0)
+  {
+    // sort the defs
+    if (defs.size() > 1)
+      dQsort(defs.address(), defs.size(), sizeof(afxConstraintDef), cmp_cons_defs);
+    
+    Vector<afxConstraintDef> unique_defs;
+    S32 last = 0;
+    
+    // manufacture root-object def if absent
+    if (defs[0].cons_node_name != ST_NULLSTRING)
+    {
+      afxConstraintDef root_def = defs[0];
+      root_def.cons_node_name = ST_NULLSTRING;
+      unique_defs.push_back(root_def);
+      last++;
+    }
+    else if (defs[0].pos_at_box_center)
+    {
+      afxConstraintDef root_def = defs[0];
+      root_def.pos_at_box_center = false;
+      unique_defs.push_back(root_def);
+      last++;
+    }
+
+    unique_defs.push_back(defs[0]);
+    
+    for (S32 i = 1; i < defs.size(); i++)
+    {
+      if (unique_defs[last].cons_node_name != defs[i].cons_node_name ||
+          unique_defs[last].cons_src_name != defs[i].cons_src_name || 
+          unique_defs[last].pos_at_box_center != defs[i].pos_at_box_center || 
+          unique_defs[last].def_type != defs[i].def_type)
+      {
+        // manufacture root-object def if absent
+        if (defs[i].cons_src_name != ST_NULLSTRING && unique_defs[last].cons_src_name != defs[i].cons_src_name)
+        {
+          if (defs[i].cons_node_name != ST_NULLSTRING || defs[i].pos_at_box_center)
+          {
+            afxConstraintDef root_def = defs[i];
+            root_def.cons_node_name = ST_NULLSTRING;
+            root_def.pos_at_box_center = false;
+            unique_defs.push_back(root_def);
+            last++;
+          }
+        }
+        unique_defs.push_back(defs[i]);
+        last++;
+      }
+      else
+      {
+        if (defs[i].history_time > unique_defs[last].history_time)
+          unique_defs[last].history_time = defs[i].history_time;
+        if (defs[i].sample_rate > unique_defs[last].sample_rate)
+          unique_defs[last].sample_rate = defs[i].sample_rate;
+      }
+    }
+    
+    //Con::printf("\nConstraints on %s", (on_server) ? "server" : "client");
+    for (S32 i = 0; i < unique_defs.size(); i++)
+      create_constraint(unique_defs[i]);
+  }
+
+  // collect the names of all the arbitrary object constraints
+  // that run on clients and store in names_on_server array.
+  //
+  if (on_server)
+  {
+    names_on_server.clear();
+    defs.clear();
+
+    for (S32 i = 0; i < all_defs.size(); i++)
+      if (all_defs[i].runs_on_client && all_defs[i].isArbitraryObject())
+        defs.push_back(all_defs[i]);
+
+    if (defs.size() < 1)
+      return;
+
+    // sort the defs
+    if (defs.size() > 1)
+      dQsort(defs.address(), defs.size(), sizeof(afxConstraintDef), cmp_cons_defs);
+
+    S32 last = 0;
+    names_on_server.push_back(defs[0].cons_src_name);
+
+    for (S32 i = 1; i < defs.size(); i++)
+    {
+      if (names_on_server[last] != defs[i].cons_src_name)
+      {
+        names_on_server.push_back(defs[i].cons_src_name);
+        last++;
+      }
+    }
+  }
+}
+
+void afxConstraintMgr::packConstraintNames(NetConnection* conn, BitStream* stream)
+{
+  // pack any named constraint names and ghost indices
+  if (stream->writeFlag(names_on_server.size() > 0)) //-- ANY NAMED CONS_BY_ID?
+  {
+    stream->write(names_on_server.size());
+    for (S32 i = 0; i < names_on_server.size(); i++)
+    {
+      stream->writeString(names_on_server[i]);
+      NetObject* obj = dynamic_cast<NetObject*>(Sim::findObject(names_on_server[i]));
+      if (!obj)
+      {
+        //Con::printf("CONSTRAINT-OBJECT %s does not exist.", names_on_server[i]);
+        stream->write((S32)-1);
+      }
+      else
+      {
+        S32 ghost_id = conn->getGhostIndex(obj);
+        /*
+        if (ghost_id == -1)
+          Con::printf("CONSTRAINT-OBJECT %s does not have a ghost.", names_on_server[i]);
+        else
+          Con::printf("CONSTRAINT-OBJECT %s name to server.", names_on_server[i]);
+         */
+        stream->write(ghost_id);
+      }
+    }
+  }
+}
+
+void afxConstraintMgr::unpackConstraintNames(BitStream* stream)
+{
+  if (stream->readFlag())                                         //-- ANY NAMED CONS_BY_ID?
+  {
+    names_on_server.clear();
+    S32 sz; stream->read(&sz);
+    for (S32 i = 0; i < sz; i++)
+    {
+      names_on_server.push_back(stream->readSTString());
+      S32 ghost_id; stream->read(&ghost_id);
+      ghost_ids.push_back(ghost_id);
+    }
+  }
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//
+
+SceneObject* afxConstraintMgr::find_object_from_name(StringTableEntry name)
+{
+  if (names_on_server.size() > 0)
+  {
+    for (S32 i = 0; i < names_on_server.size(); i++)
+      if (names_on_server[i] == name)
+      {
+        if (ghost_ids[i] == -1)
+          return 0;
+        NetConnection* conn = NetConnection::getConnectionToServer();
+        if (!conn)
+          return 0;
+        return dynamic_cast<SceneObject*>(conn->resolveGhost(ghost_ids[i]));
+      }
+  }
+
+  return dynamic_cast<SceneObject*>(Sim::findObject(name));
+}
+
+void afxConstraintMgr::addScopeableObject(SceneObject* object)
+{
+  for (S32 i = 0; i < scopeable_objs.size(); i++)
+  {
+    if (scopeable_objs[i] == object)
+      return;
+  }
+
+  object->addScopeRef();
+  scopeable_objs.push_back(object);
+}
+
+void afxConstraintMgr::removeScopeableObject(SceneObject* object)
+{
+  for (S32 i = 0; i < scopeable_objs.size(); i++)
+    if (scopeable_objs[i] == object)
+    {
+      object->removeScopeRef();
+      scopeable_objs.erase_fast(i);
+      return;
+    }
+}
+
+void afxConstraintMgr::clearAllScopeableObjs()
+{
+  for (S32 i = 0; i < scopeable_objs.size(); i++)
+    scopeable_objs[i]->removeScopeRef();
+  scopeable_objs.clear();
+}
+
+void afxConstraintMgr::postMissingConstraintObject(afxConstraint* cons, bool is_deleting)
+{
+  if (cons->gone_missing)
+    return;
+
+  if (!is_deleting)
+  {
+    SceneObject* obj = arcaneFX::findScopedObject(cons->getScopeId());
+    if (obj)
+    {
+      cons->restoreObject(obj);
+      return;
+    }
+  }
+
+  cons->gone_missing = true;
+  missing_objs->push_back(cons);
+}
+
+void afxConstraintMgr::restoreScopedObject(SceneObject* obj, afxChoreographer* ch)
+{
+  for (S32 i = 0; i < missing_objs->size(); i++)
+  {
+    if ((*missing_objs)[i]->getScopeId() == obj->getScopeId())
+    {
+      (*missing_objs)[i]->gone_missing = false;
+      (*missing_objs)[i]->restoreObject(obj);
+      if (ch)
+        ch->restoreObject(obj);
+    }
+    else
+      missing_objs2->push_back((*missing_objs)[i]);
+  }
+
+  Vector<afxConstraint*>* tmp = missing_objs;
+  missing_objs = missing_objs2;
+  missing_objs2 = tmp;
+  missing_objs2->clear();
+}
+
+void afxConstraintMgr::adjustProcessOrdering(afxChoreographer* ch)
+{
+  Vector<ProcessObject*> cons_sources;
+
+  // add choreographer to the list
+  cons_sources.push_back(ch);
+
+  // collect all the ProcessObject related constraint sources
+  for (S32 i = 0; i < constraints_v.size(); i++)
+  {
+    afxConstraintList* list = constraints_v[i];
+    afxConstraint* cons = (*list)[0];
+    if (cons)
+    {
+      ProcessObject* pobj = dynamic_cast<ProcessObject*>(cons->getSceneObject());
+      if (pobj)
+        cons_sources.push_back(pobj);
+    }
+  }
+
+  ProcessList* proc_list;
+  if (ch->isServerObject())
+    proc_list = ServerProcessList::get();
+  else
+    proc_list = ClientProcessList::get();
+  if (!proc_list)
+    return;
+
+  GameBase* nearest_to_end = dynamic_cast<GameBase*>(proc_list->findNearestToEnd(cons_sources));
+  GameBase* chor = (GameBase*) ch;
+
+  // choreographer needs to be processed after the latest process object
+  if (chor != nearest_to_end && nearest_to_end != 0)
+  {
+    //Con::printf("Choreographer processing BEFORE some of its constraints... fixing. [%s] -- %s",
+    //   (ch->isServerObject()) ? "S" : "C", nearest_to_end->getClassName());
+    if (chor->isProperlyAdded())
+      ch->processAfter(nearest_to_end);
+    else
+      ch->postProcessAfterObject(nearest_to_end);
+  }
+  else
+  {
+    //Con::printf("Choreographer processing AFTER its constraints... fine. [%s]",
+    //   (ch->isServerObject()) ? "S" : "C");
+  }
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxPointConstraint
+
+afxPointConstraint::afxPointConstraint(afxConstraintMgr* mgr) 
+  : afxConstraint(mgr)
+{
+  point.zero();
+  vector.set(0,0,1);
+}
+
+afxPointConstraint::~afxPointConstraint()
+{
+}
+
+void afxPointConstraint::set(Point3F point, Point3F vector)
+{
+  this->point = point;
+  this->vector = vector;
+  is_defined = true;
+  is_valid = true;
+  change_code++;
+  sample(0.0f, 0, 0);
+}
+
+void afxPointConstraint::sample(F32 dt, U32 elapsed_ms, const Point3F* cam_pos)
+{
+  if (cam_pos)
+  {
+    Point3F dir = (*cam_pos) - point;
+    F32 dist_sq = dir.lenSquared();
+    if (dist_sq > mgr->getScopingDistanceSquared())
+    {
+      is_valid = false;
+      return;
+    }
+    is_valid = true;
+  }
+
+  last_pos = point;
+  last_xfm.identity();
+  last_xfm.setPosition(point);
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxTransformConstraint
+
+afxTransformConstraint::afxTransformConstraint(afxConstraintMgr* mgr) 
+  : afxConstraint(mgr)
+{
+  xfm.identity();
+}
+
+afxTransformConstraint::~afxTransformConstraint()
+{
+}
+
+void afxTransformConstraint::set(const MatrixF& xfm)
+{
+  this->xfm = xfm;
+  is_defined = true;
+  is_valid = true;
+  change_code++;
+  sample(0.0f, 0, 0);
+}
+
+void afxTransformConstraint::sample(F32 dt, U32 elapsed_ms, const Point3F* cam_pos)
+{
+  if (cam_pos)
+  {
+    Point3F dir = (*cam_pos) - xfm.getPosition();
+    F32 dist_sq = dir.lenSquared();
+    if (dist_sq > mgr->getScopingDistanceSquared())
+    {
+      is_valid = false;
+      return;
+    }
+    is_valid = true;
+  }
+
+  last_xfm = xfm;
+  last_pos = xfm.getPosition();
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxShapeConstraint
+
+afxShapeConstraint::afxShapeConstraint(afxConstraintMgr* mgr) 
+  : afxConstraint(mgr)
+{
+  arb_name = ST_NULLSTRING;
+  shape = 0;
+  scope_id = 0;
+  clip_tag = 0;
+  lock_tag = 0;
+}
+
+afxShapeConstraint::afxShapeConstraint(afxConstraintMgr* mgr, StringTableEntry arb_name) 
+  : afxConstraint(mgr)
+{
+  this->arb_name = arb_name;
+  shape = 0;
+  scope_id = 0;
+  clip_tag = 0;
+  lock_tag = 0;
+}
+
+afxShapeConstraint::~afxShapeConstraint()
+{
+  if (shape)
+    clearNotify(shape);
+}
+
+void afxShapeConstraint::set(ShapeBase* shape)
+{
+  if (this->shape)
+  {
+    scope_id = 0;
+    clearNotify(this->shape);
+    if (clip_tag > 0)
+      remapAnimation(clip_tag, shape);
+    if (lock_tag > 0)
+      unlockAnimation(lock_tag);
+  }
+
+  this->shape = shape;
+
+  if (this->shape)
+  {
+    deleteNotify(this->shape);
+    scope_id = this->shape->getScopeId();
+  }
+
+  if (this->shape != NULL)
+  {
+    is_defined = true;
+    is_valid = true;
+    change_code++;
+    sample(0.0f, 0, 0);
+  }
+  else
+    is_valid = false;
+}
+
+void afxShapeConstraint::set_scope_id(U16 scope_id)
+{
+  if (shape)
+    clearNotify(shape);
+
+  shape = 0;
+  this->scope_id = scope_id;
+
+  is_defined = (this->scope_id > 0);
+  is_valid = false;
+  mgr->postMissingConstraintObject(this);
+}
+
+void afxShapeConstraint::sample(F32 dt, U32 elapsed_ms, const Point3F* cam_pos)
+{
+  if (gone_missing)
+    return;
+
+  if (shape)
+  {
+    last_xfm = shape->getRenderTransform();
+    if (cons_def.pos_at_box_center)
+      last_pos = shape->getBoxCenter();
+    else
+      last_pos = shape->getRenderPosition();
+  }
+}
+
+void afxShapeConstraint::restoreObject(SceneObject* obj) 
+{ 
+  if (this->shape)
+  {
+    scope_id = 0;
+    clearNotify(this->shape);
+  }
+
+  this->shape = (ShapeBase* )obj;
+
+  if (this->shape)
+  {
+    deleteNotify(this->shape);
+    scope_id = this->shape->getScopeId();
+  }
+
+  is_valid = (this->shape != NULL);
+}
+
+void afxShapeConstraint::onDeleteNotify(SimObject* obj)
+{
+  if (shape == dynamic_cast<ShapeBase*>(obj))
+  {
+    shape = 0;
+    is_valid = false;
+    if (scope_id > 0)
+      mgr->postMissingConstraintObject(this, true);
+  }
+
+  Parent::onDeleteNotify(obj);
+}
+
+U32 afxShapeConstraint::setAnimClip(const char* clip, F32 pos, F32 rate, F32 trans, bool is_death_anim)
+{
+  if (!shape)
+    return 0;
+
+  if (shape->isServerObject())
+  {
+    AIPlayer* ai_player = dynamic_cast<AIPlayer*>(shape);
+    if (ai_player && !ai_player->isBlendAnimation(clip))
+    {
+      ai_player->saveMoveState();
+      ai_player->stopMove();
+    }
+  }
+
+  clip_tag = shape->playAnimation(clip, pos, rate, trans, false/*hold*/, true/*wait*/, is_death_anim);
+  return clip_tag;
+}
+
+void afxShapeConstraint::remapAnimation(U32 tag, ShapeBase* other_shape)
+{
+  if (clip_tag == 0)
+    return;
+
+  if (!shape)
+    return;
+
+  if (!other_shape)
+  {
+    resetAnimation(tag);
+    return;
+  }
+
+  Con::errorf("remapAnimation -- Clip name, %s.", shape->getLastClipName(tag));
+
+  if (shape->isClientObject())
+  {
+    shape->restoreAnimation(tag);
+  }
+  else
+  {
+    AIPlayer* ai_player = dynamic_cast<AIPlayer*>(shape);
+    if (ai_player)
+      ai_player->restartMove(tag);
+    else
+      shape->restoreAnimation(tag);
+  }
+
+  clip_tag = 0;
+}
+
+void afxShapeConstraint::resetAnimation(U32 tag)
+{
+  if (clip_tag == 0)
+    return;
+
+  if (!shape)
+    return;
+  
+  if (shape->isClientObject())
+  {
+    shape->restoreAnimation(tag);
+  }
+  else
+  {
+    AIPlayer* ai_player = dynamic_cast<AIPlayer*>(shape);
+    if (ai_player)
+      ai_player->restartMove(tag);
+    else
+      shape->restoreAnimation(tag);
+  }
+
+  if ((tag & 0x80000000) == 0 && tag == clip_tag)
+    clip_tag = 0;
+}
+
+U32 afxShapeConstraint::lockAnimation()
+{
+  if (!shape)
+    return 0;
+
+  lock_tag = shape->lockAnimation();
+  return lock_tag;
+}
+
+void afxShapeConstraint::unlockAnimation(U32 tag)
+{
+  if (lock_tag == 0)
+    return;
+
+  if (!shape)
+    return;
+  
+  shape->unlockAnimation(tag);
+  lock_tag = 0;
+}
+
+F32 afxShapeConstraint::getAnimClipDuration(const char* clip)
+{
+  return (shape) ? shape->getAnimationDuration(clip) : 0.0f;
+}
+
+S32 afxShapeConstraint::getDamageState()
+{
+  return (shape) ? shape->getDamageState() : -1;
+}
+
+U32 afxShapeConstraint::getTriggers()
+{
+  return (shape) ? shape->getShapeInstance()->getTriggerStateMask() : 0;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxShapeNodeConstraint
+
+afxShapeNodeConstraint::afxShapeNodeConstraint(afxConstraintMgr* mgr)  
+  : afxShapeConstraint(mgr)
+{
+  arb_node = ST_NULLSTRING;
+  shape_node_ID = -1;
+}
+
+afxShapeNodeConstraint::afxShapeNodeConstraint(afxConstraintMgr* mgr, StringTableEntry arb_name, StringTableEntry arb_node)
+  : afxShapeConstraint(mgr, arb_name)
+{
+  this->arb_node = arb_node;
+  shape_node_ID = -1;
+}
+
+void afxShapeNodeConstraint::set(ShapeBase* shape)
+{
+  if (shape)
+  {
+    shape_node_ID = shape->getShape()->findNode(arb_node);
+    if (shape_node_ID == -1)
+      Con::errorf("Failed to find node [%s]", arb_node);
+  }
+  else
+    shape_node_ID = -1;
+
+  Parent::set(shape);
+}
+
+void afxShapeNodeConstraint::set_scope_id(U16 scope_id)
+{
+  shape_node_ID = -1;
+  Parent::set_scope_id(scope_id);
+}
+
+void afxShapeNodeConstraint::sample(F32 dt, U32 elapsed_ms, const Point3F* cam_pos)
+{
+  if (shape && shape_node_ID != -1)
+  {
+    last_xfm = shape->getRenderTransform();
+    last_xfm.scale(shape->getScale());
+    last_xfm.mul(shape->getShapeInstance()->mNodeTransforms[shape_node_ID]);
+    last_pos = last_xfm.getPosition();
+  }
+}
+
+void afxShapeNodeConstraint::restoreObject(SceneObject* obj) 
+{ 
+  ShapeBase* shape = dynamic_cast<ShapeBase*>(obj);
+  if (shape)
+  {
+    shape_node_ID = shape->getShape()->findNode(arb_node);
+    if (shape_node_ID == -1)
+      Con::errorf("Failed to find node [%s]", arb_node);
+  }
+  else
+    shape_node_ID = -1;
+  Parent::restoreObject(obj);
+}
+
+void afxShapeNodeConstraint::onDeleteNotify(SimObject* obj)
+{
+  Parent::onDeleteNotify(obj);
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxObjectConstraint
+
+afxObjectConstraint::afxObjectConstraint(afxConstraintMgr* mgr) 
+  : afxConstraint(mgr)
+{
+  arb_name = ST_NULLSTRING;
+  obj = 0;
+  scope_id = 0;
+  is_camera = false;
+}
+
+afxObjectConstraint::afxObjectConstraint(afxConstraintMgr* mgr, StringTableEntry arb_name) 
+  : afxConstraint(mgr)
+{
+  this->arb_name = arb_name;
+  obj = 0;
+  scope_id = 0;
+  is_camera = false;
+}
+
+afxObjectConstraint::~afxObjectConstraint()
+{
+  if (obj)
+    clearNotify(obj);
+}
+
+void afxObjectConstraint::set(SceneObject* obj)
+{
+  if (this->obj)
+  {
+    scope_id = 0;
+    clearNotify(this->obj);
+  }
+
+  this->obj = obj;
+
+  if (this->obj)
+  {
+    deleteNotify(this->obj);
+    scope_id = this->obj->getScopeId();
+  }
+
+  if (this->obj != NULL)
+  {
+    is_camera = this->obj->isCamera();
+
+    is_defined = true;
+    is_valid = true;
+    change_code++;
+    sample(0.0f, 0, 0);
+  }
+  else
+    is_valid = false;
+}
+
+void afxObjectConstraint::set_scope_id(U16 scope_id)
+{
+  if (obj)
+    clearNotify(obj);
+
+  obj = 0;
+  this->scope_id = scope_id;
+
+  is_defined = (scope_id > 0);
+  is_valid = false;
+  mgr->postMissingConstraintObject(this);
+}
+
+void afxObjectConstraint::sample(F32 dt, U32 elapsed_ms, const Point3F* cam_pos)
+{
+  if (gone_missing)
+    return;
+
+  if (obj)
+  {
+    if (!is_camera && cons_def.treat_as_camera && dynamic_cast<ShapeBase*>(obj))
+    {
+      ShapeBase* cam_obj = (ShapeBase*) obj;
+      F32 pov = 1.0f;
+      cam_obj->getCameraTransform(&pov, &last_xfm);
+      last_xfm.getColumn(3, &last_pos);
+    }
+    else
+    {
+      last_xfm = obj->getRenderTransform();
+      if (cons_def.pos_at_box_center)
+        last_pos = obj->getBoxCenter();
+      else
+        last_pos = obj->getRenderPosition();
+    }
+  }
+}
+
+void afxObjectConstraint::restoreObject(SceneObject* obj)
+{
+  if (this->obj)
+  {
+    scope_id = 0;
+    clearNotify(this->obj);
+  }
+
+  this->obj = obj;
+
+  if (this->obj)
+  {
+    deleteNotify(this->obj);
+    scope_id = this->obj->getScopeId();
+  }
+
+  is_valid = (this->obj != NULL);
+}
+
+void afxObjectConstraint::onDeleteNotify(SimObject* obj)
+{
+  if (this->obj == dynamic_cast<SceneObject*>(obj))
+  {
+    this->obj = 0;
+    is_valid = false;
+    if (scope_id > 0)
+      mgr->postMissingConstraintObject(this, true);
+  }
+
+  Parent::onDeleteNotify(obj);
+}
+
+U32 afxObjectConstraint::getTriggers()
+{
+  TSStatic* ts_static = dynamic_cast<TSStatic*>(obj);
+  if (ts_static)
+  {
+    TSShapeInstance* obj_inst = ts_static->getShapeInstance();
+    return (obj_inst) ? obj_inst->getTriggerStateMask() : 0;
+  }
+
+  ShapeBase* shape_base = dynamic_cast<ShapeBase*>(obj);
+  if (shape_base)
+  {
+    TSShapeInstance* obj_inst = shape_base->getShapeInstance();
+    return (obj_inst) ? obj_inst->getTriggerStateMask() : 0;
+  }
+
+  return 0;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxEffectConstraint
+
+afxEffectConstraint::afxEffectConstraint(afxConstraintMgr* mgr) 
+  : afxConstraint(mgr)
+{
+  effect_name = ST_NULLSTRING;
+  effect = 0;
+}
+
+afxEffectConstraint::afxEffectConstraint(afxConstraintMgr* mgr, StringTableEntry effect_name) 
+  : afxConstraint(mgr)
+{
+  this->effect_name = effect_name;
+  effect = 0;
+}
+
+afxEffectConstraint::~afxEffectConstraint()
+{
+}
+
+bool afxEffectConstraint::getPosition(Point3F& pos, F32 hist) 
+{ 
+  if (!effect || !effect->inScope())
+    return false;
+ 
+  if (cons_def.pos_at_box_center)
+    effect->getUpdatedBoxCenter(pos);
+  else
+    effect->getUpdatedPosition(pos);
+  
+  return true;
+}
+
+bool afxEffectConstraint::getTransform(MatrixF& xfm, F32 hist) 
+{ 
+  if (!effect || !effect->inScope())
+    return false;
+  
+  effect->getUpdatedTransform(xfm);
+  return true;
+}
+
+bool afxEffectConstraint::getAltitudes(F32& terrain_alt, F32& interior_alt) 
+{ 
+  if (!effect)
+    return false;
+  
+  effect->getAltitudes(terrain_alt, interior_alt);
+  return true;
+}
+
+void afxEffectConstraint::set(afxEffectWrapper* effect)
+{
+  this->effect = effect;
+
+  if (this->effect != NULL)
+  {
+    is_defined = true;
+    is_valid = true;
+    change_code++;
+  }
+  else
+    is_valid = false;
+}
+
+U32 afxEffectConstraint::setAnimClip(const char* clip, F32 pos, F32 rate, F32 trans, bool is_death_anim)
+{
+  return (effect) ? effect->setAnimClip(clip, pos, rate, trans) : 0;
+}
+
+void afxEffectConstraint::resetAnimation(U32 tag)
+{
+  if (effect)
+    effect->resetAnimation(tag);
+}
+
+F32 afxEffectConstraint::getAnimClipDuration(const char* clip)
+{
+  return (effect) ? getAnimClipDuration(clip) : 0;
+}
+
+U32 afxEffectConstraint::getTriggers()
+{
+  return (effect) ? effect->getTriggers() : 0;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxEffectNodeConstraint
+
+afxEffectNodeConstraint::afxEffectNodeConstraint(afxConstraintMgr* mgr) 
+  : afxEffectConstraint(mgr)
+{
+  effect_node = ST_NULLSTRING;
+  effect_node_ID = -1;
+}
+
+afxEffectNodeConstraint::afxEffectNodeConstraint(afxConstraintMgr* mgr, StringTableEntry name, StringTableEntry node)
+: afxEffectConstraint(mgr, name)
+{
+  this->effect_node = node;
+  effect_node_ID = -1;
+}
+
+
+
+bool afxEffectNodeConstraint::getPosition(Point3F& pos, F32 hist) 
+{ 
+  if (!effect || !effect->inScope())
+    return false;
+  
+  TSShapeInstance* ts_shape_inst = effect->getTSShapeInstance();
+  if (!ts_shape_inst)
+    return false;
+
+  if (effect_node_ID == -1)
+  {
+    TSShape* ts_shape = effect->getTSShape();
+    effect_node_ID = (ts_shape) ? ts_shape->findNode(effect_node) : -1;
+  }
+
+  if (effect_node_ID == -1)
+    return false;
+
+  effect->getUpdatedTransform(last_xfm);
+
+  Point3F scale;
+  effect->getUpdatedScale(scale);
+
+  MatrixF gag = ts_shape_inst->mNodeTransforms[effect_node_ID];
+  gag.setPosition( gag.getPosition()*scale );
+
+  MatrixF xfm;
+  xfm.mul(last_xfm, gag);
+  //
+  pos = xfm.getPosition();
+
+  return true;
+}
+
+bool afxEffectNodeConstraint::getTransform(MatrixF& xfm, F32 hist) 
+{ 
+  if (!effect || !effect->inScope())
+    return false;
+  
+  TSShapeInstance* ts_shape_inst = effect->getTSShapeInstance();
+  if (!ts_shape_inst)
+    return false;
+
+  if (effect_node_ID == -1)
+  {
+    TSShape* ts_shape = effect->getTSShape();
+    effect_node_ID = (ts_shape) ? ts_shape->findNode(effect_node) : -1;
+  }
+
+  if (effect_node_ID == -1)
+    return false;
+
+  effect->getUpdatedTransform(last_xfm);
+
+  Point3F scale;
+  effect->getUpdatedScale(scale);
+
+  MatrixF gag = ts_shape_inst->mNodeTransforms[effect_node_ID];
+  gag.setPosition( gag.getPosition()*scale );
+
+  xfm.mul(last_xfm, gag);
+
+  return true;
+}
+
+void afxEffectNodeConstraint::set(afxEffectWrapper* effect)
+{
+  if (effect)
+  {
+    TSShape* ts_shape = effect->getTSShape();
+    effect_node_ID = (ts_shape) ? ts_shape->findNode(effect_node) : -1;
+  }
+  else
+    effect_node_ID = -1;
+
+  Parent::set(effect);
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxSampleBuffer
+
+afxSampleBuffer::afxSampleBuffer()
+{
+  buffer_sz = 0;
+  buffer_ms = 0;
+  ms_per_sample = 33;
+  elapsed_ms = 0;
+  last_sample_ms = 0;
+  next_sample_num = 0;
+  n_samples = 0;
+}
+
+afxSampleBuffer::~afxSampleBuffer()
+{
+}
+
+void afxSampleBuffer::configHistory(F32 hist_len, U8 sample_rate)
+{
+  buffer_sz = mCeil(hist_len*sample_rate) + 1;
+  ms_per_sample = mCeil(1000.0f/sample_rate);
+  buffer_ms = buffer_sz*ms_per_sample;
+}
+
+void afxSampleBuffer::recordSample(F32 dt, U32 elapsed_ms, void* data)
+{
+  this->elapsed_ms = elapsed_ms;
+
+  if (!data)
+    return;
+
+  U32 now_sample_num = elapsed_ms/ms_per_sample;
+  if (next_sample_num <= now_sample_num)
+  {
+    last_sample_ms = elapsed_ms;
+    while (next_sample_num <= now_sample_num)
+    {
+      recSample(next_sample_num % buffer_sz, data);
+      next_sample_num++;
+      n_samples++;
+    }
+  }
+}
+
+inline bool afxSampleBuffer::compute_idx_from_lag(F32 lag, U32& idx) 
+{ 
+  bool in_bounds = true;
+
+  U32 lag_ms = lag*1000.0f;
+  U32 rec_ms = (elapsed_ms < buffer_ms) ? elapsed_ms : buffer_ms;
+  if (lag_ms > rec_ms)
+  {
+    // hasn't produced enough history
+    lag_ms = rec_ms;
+    in_bounds = false;
+  }
+
+  U32 latest_sample_num = last_sample_ms/ms_per_sample;
+  U32 then_sample_num = (elapsed_ms - lag_ms)/ms_per_sample;
+
+  if (then_sample_num > latest_sample_num)
+  {
+    // latest sample is older than lag
+    then_sample_num = latest_sample_num;
+    in_bounds = false;
+  }
+
+  idx = then_sample_num % buffer_sz;
+  return in_bounds;
+}
+
+inline bool afxSampleBuffer::compute_idx_from_lag(F32 lag, U32& idx1, U32& idx2, F32& t) 
+{ 
+  bool in_bounds = true;
+
+  F32 lag_ms = lag*1000.0f;
+  F32 rec_ms = (elapsed_ms < buffer_ms) ? elapsed_ms : buffer_ms;
+  if (lag_ms > rec_ms)
+  {
+    // hasn't produced enough history
+    lag_ms = rec_ms;
+    in_bounds = false;
+  }
+
+  F32 per_samp = ms_per_sample;
+  F32 latest_sample_num = last_sample_ms/per_samp;
+  F32 then_sample_num = (elapsed_ms - lag_ms)/per_samp;
+
+  U32 latest_sample_num_i = latest_sample_num;
+  U32 then_sample_num_i = then_sample_num;
+
+  if (then_sample_num_i >= latest_sample_num_i)
+  {
+    if (latest_sample_num_i < then_sample_num_i)
+      in_bounds = false;
+    t = 0.0;
+    idx1 = then_sample_num_i % buffer_sz;
+    idx2 = idx1;
+  }
+  else
+  {
+    t = then_sample_num - then_sample_num_i;
+    idx1 = then_sample_num_i % buffer_sz;
+    idx2 = (then_sample_num_i+1) % buffer_sz;
+  }
+
+  return in_bounds;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxSampleXfmBuffer
+
+afxSampleXfmBuffer::afxSampleXfmBuffer()
+{
+  xfm_buffer = 0;
+}
+
+afxSampleXfmBuffer::~afxSampleXfmBuffer()
+{
+  delete [] xfm_buffer;
+}
+
+void afxSampleXfmBuffer::configHistory(F32 hist_len, U8 sample_rate)
+{
+  if (!xfm_buffer)
+  {
+    afxSampleBuffer::configHistory(hist_len, sample_rate);
+    if (buffer_sz > 0)
+      xfm_buffer = new MatrixF[buffer_sz];
+  }  
+}
+
+void afxSampleXfmBuffer::recSample(U32 idx, void* data)
+{
+  xfm_buffer[idx] = *((MatrixF*)data);
+}
+
+void afxSampleXfmBuffer::getSample(F32 lag, void* data, bool& in_bounds) 
+{ 
+  U32 idx1, idx2;
+  F32 t;
+  in_bounds = compute_idx_from_lag(lag, idx1, idx2, t);
+
+  if (idx1 == idx2)
+  {
+    MatrixF* m1 = &xfm_buffer[idx1];
+    *((MatrixF*)data) = *m1;
+  }
+  else
+  {
+    MatrixF* m1 = &xfm_buffer[idx1];
+    MatrixF* m2 = &xfm_buffer[idx2];
+
+    Point3F p1 = m1->getPosition();
+    Point3F p2 = m2->getPosition();
+    Point3F p; p.interpolate(p1, p2, t);
+
+    if (t < 0.5f)
+      *((MatrixF*)data) = *m1;
+    else
+      *((MatrixF*)data) = *m2;
+
+    ((MatrixF*)data)->setPosition(p);
+  }
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxPointHistConstraint
+
+afxPointHistConstraint::afxPointHistConstraint(afxConstraintMgr* mgr)
+  : afxPointConstraint(mgr)
+{
+  samples = 0;
+}
+
+afxPointHistConstraint::~afxPointHistConstraint()
+{
+  delete samples;
+}
+
+void afxPointHistConstraint::set(Point3F point, Point3F vector)
+{
+  if (!samples)
+  {
+    samples = new afxSampleXfmBuffer;
+    samples->configHistory(cons_def.history_time, cons_def.sample_rate);
+  }
+  
+  Parent::set(point, vector);
+}
+
+void afxPointHistConstraint::sample(F32 dt, U32 elapsed_ms, const Point3F* cam_pos)
+{
+  Parent::sample(dt, elapsed_ms, cam_pos);
+
+  if (isDefined())
+  {
+    if (isValid())
+      samples->recordSample(dt, elapsed_ms, &last_xfm);
+    else
+      samples->recordSample(dt, elapsed_ms, 0);
+  }
+}
+
+bool afxPointHistConstraint::getPosition(Point3F& pos, F32 hist) 
+{ 
+  bool in_bounds;
+
+  MatrixF xfm;
+  samples->getSample(hist, &xfm, in_bounds);
+
+  pos = xfm.getPosition();
+
+  return in_bounds;
+}
+
+bool afxPointHistConstraint::getTransform(MatrixF& xfm, F32 hist) 
+{ 
+  bool in_bounds;
+
+  samples->getSample(hist, &xfm, in_bounds);
+
+  return in_bounds; 
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxPointHistConstraint
+
+afxTransformHistConstraint::afxTransformHistConstraint(afxConstraintMgr* mgr)
+  : afxTransformConstraint(mgr)
+{
+  samples = 0;
+}
+
+afxTransformHistConstraint::~afxTransformHistConstraint()
+{
+  delete samples;
+}
+
+void afxTransformHistConstraint::set(const MatrixF& xfm)
+{
+  if (!samples)
+  {
+    samples = new afxSampleXfmBuffer;
+    samples->configHistory(cons_def.history_time, cons_def.sample_rate);
+  }
+  
+  Parent::set(xfm);
+}
+
+void afxTransformHistConstraint::sample(F32 dt, U32 elapsed_ms, const Point3F* cam_pos)
+{
+  Parent::sample(dt, elapsed_ms, cam_pos);
+
+  if (isDefined())
+  {
+    if (isValid())
+      samples->recordSample(dt, elapsed_ms, &last_xfm);
+    else
+      samples->recordSample(dt, elapsed_ms, 0);
+  }
+}
+
+bool afxTransformHistConstraint::getPosition(Point3F& pos, F32 hist) 
+{ 
+  bool in_bounds;
+
+  MatrixF xfm;
+  samples->getSample(hist, &xfm, in_bounds);
+
+  pos = xfm.getPosition();
+
+  return in_bounds;
+}
+
+bool afxTransformHistConstraint::getTransform(MatrixF& xfm, F32 hist) 
+{ 
+  bool in_bounds;
+
+  samples->getSample(hist, &xfm, in_bounds);
+
+  return in_bounds; 
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxShapeHistConstraint
+
+afxShapeHistConstraint::afxShapeHistConstraint(afxConstraintMgr* mgr)
+  : afxShapeConstraint(mgr)
+{
+  samples = 0;
+}
+
+afxShapeHistConstraint::afxShapeHistConstraint(afxConstraintMgr* mgr, StringTableEntry arb_name)
+  : afxShapeConstraint(mgr, arb_name)
+{
+  samples = 0;
+}
+
+afxShapeHistConstraint::~afxShapeHistConstraint()
+{
+  delete samples;
+}
+
+void afxShapeHistConstraint::set(ShapeBase* shape)
+{
+  if (shape && !samples)
+  {
+    samples = new afxSampleXfmBuffer;
+    samples->configHistory(cons_def.history_time, cons_def.sample_rate);
+  }
+  
+  Parent::set(shape);
+}
+
+void afxShapeHistConstraint::set_scope_id(U16 scope_id)
+{
+  if (scope_id > 0 && !samples)
+  {
+    samples = new afxSampleXfmBuffer;
+    samples->configHistory(cons_def.history_time, cons_def.sample_rate);
+  }
+  
+  Parent::set_scope_id(scope_id);
+}
+
+void afxShapeHistConstraint::sample(F32 dt, U32 elapsed_ms, const Point3F* cam_pos)
+{
+  Parent::sample(dt, elapsed_ms, cam_pos);
+
+  if (isDefined())
+  {
+    if (isValid())
+      samples->recordSample(dt, elapsed_ms, &last_xfm);
+    else
+      samples->recordSample(dt, elapsed_ms, 0);
+  }
+}
+
+bool afxShapeHistConstraint::getPosition(Point3F& pos, F32 hist) 
+{ 
+  bool in_bounds;
+
+  MatrixF xfm;
+  samples->getSample(hist, &xfm, in_bounds);
+
+  pos = xfm.getPosition();
+
+  return in_bounds;
+}
+
+bool afxShapeHistConstraint::getTransform(MatrixF& xfm, F32 hist) 
+{ 
+  bool in_bounds;
+
+  samples->getSample(hist, &xfm, in_bounds);
+
+  return in_bounds; 
+}
+
+void afxShapeHistConstraint::onDeleteNotify(SimObject* obj)
+{
+  Parent::onDeleteNotify(obj);
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxShapeNodeHistConstraint
+
+afxShapeNodeHistConstraint::afxShapeNodeHistConstraint(afxConstraintMgr* mgr)
+  : afxShapeNodeConstraint(mgr)
+{
+  samples = 0;
+}
+
+afxShapeNodeHistConstraint::afxShapeNodeHistConstraint(afxConstraintMgr* mgr, StringTableEntry arb_name,
+                                                       StringTableEntry arb_node)
+  : afxShapeNodeConstraint(mgr, arb_name, arb_node)
+{
+  samples = 0;
+}
+
+afxShapeNodeHistConstraint::~afxShapeNodeHistConstraint()
+{
+  delete samples;
+}
+
+void afxShapeNodeHistConstraint::set(ShapeBase* shape)
+{
+  if (shape && !samples)
+  {
+    samples = new afxSampleXfmBuffer;
+    samples->configHistory(cons_def.history_time, cons_def.sample_rate);
+  }
+  
+  Parent::set(shape);
+}
+
+void afxShapeNodeHistConstraint::set_scope_id(U16 scope_id)
+{
+  if (scope_id > 0 && !samples)
+  {
+    samples = new afxSampleXfmBuffer;
+    samples->configHistory(cons_def.history_time, cons_def.sample_rate);
+  }
+  
+  Parent::set_scope_id(scope_id);
+}
+
+void afxShapeNodeHistConstraint::sample(F32 dt, U32 elapsed_ms, const Point3F* cam_pos)
+{
+  Parent::sample(dt, elapsed_ms, cam_pos);
+
+  if (isDefined())
+  {
+    if (isValid())
+      samples->recordSample(dt, elapsed_ms, &last_xfm);
+    else
+      samples->recordSample(dt, elapsed_ms, 0);
+  }
+}
+
+bool afxShapeNodeHistConstraint::getPosition(Point3F& pos, F32 hist) 
+{ 
+  bool in_bounds;
+
+  MatrixF xfm;
+  samples->getSample(hist, &xfm, in_bounds);
+
+  pos = xfm.getPosition();
+
+  return in_bounds;
+}
+
+bool afxShapeNodeHistConstraint::getTransform(MatrixF& xfm, F32 hist) 
+{ 
+  bool in_bounds;
+
+  samples->getSample(hist, &xfm, in_bounds);
+
+  return in_bounds; 
+}
+
+void afxShapeNodeHistConstraint::onDeleteNotify(SimObject* obj)
+{
+  Parent::onDeleteNotify(obj);
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxObjectHistConstraint
+
+afxObjectHistConstraint::afxObjectHistConstraint(afxConstraintMgr* mgr)
+  : afxObjectConstraint(mgr)
+{
+  samples = 0;
+}
+
+afxObjectHistConstraint::afxObjectHistConstraint(afxConstraintMgr* mgr, StringTableEntry arb_name)
+  : afxObjectConstraint(mgr, arb_name)
+{
+  samples = 0;
+}
+
+afxObjectHistConstraint::~afxObjectHistConstraint()
+{
+  delete samples;
+}
+
+void afxObjectHistConstraint::set(SceneObject* obj)
+{
+  if (obj && !samples)
+  {
+    samples = new afxSampleXfmBuffer;
+    samples->configHistory(cons_def.history_time, cons_def.sample_rate);
+  }
+  
+  Parent::set(obj);
+}
+
+void afxObjectHistConstraint::set_scope_id(U16 scope_id)
+{
+  if (scope_id > 0 && !samples)
+  {
+    samples = new afxSampleXfmBuffer;
+    samples->configHistory(cons_def.history_time, cons_def.sample_rate);
+  }
+  
+  Parent::set_scope_id(scope_id);
+}
+
+void afxObjectHistConstraint::sample(F32 dt, U32 elapsed_ms, const Point3F* cam_pos)
+{
+  Parent::sample(dt, elapsed_ms, cam_pos);
+
+  if (isDefined())
+  {
+    if (isValid())
+      samples->recordSample(dt, elapsed_ms, &last_xfm);
+    else
+      samples->recordSample(dt, elapsed_ms, 0);
+  }
+}
+
+bool afxObjectHistConstraint::getPosition(Point3F& pos, F32 hist) 
+{ 
+  bool in_bounds;
+
+  MatrixF xfm;
+  samples->getSample(hist, &xfm, in_bounds);
+
+  pos = xfm.getPosition();
+
+  return in_bounds;
+}
+
+bool afxObjectHistConstraint::getTransform(MatrixF& xfm, F32 hist) 
+{ 
+  bool in_bounds;
+
+  samples->getSample(hist, &xfm, in_bounds);
+
+  return in_bounds; 
+}
+
+void afxObjectHistConstraint::onDeleteNotify(SimObject* obj)
+{
+  Parent::onDeleteNotify(obj);
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+

+ 677 - 0
Engine/source/afx/afxConstraint.h

@@ -0,0 +1,677 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
+// Copyright (C) 2015 Faust Logic, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _AFX_CONSTRAINT_H_
+#define _AFX_CONSTRAINT_H_
+
+#include "core/util/tVector.h"
+#include "T3D/shapeBase.h"
+
+#include "afxEffectDefs.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxConstraintDef
+
+class afxEffectBaseData;
+
+struct afxConstraintDef : public afxEffectDefs
+{
+  enum DefType
+  {
+    CONS_UNDEFINED,
+    CONS_PREDEFINED,
+    CONS_SCENE,
+    CONS_EFFECT,
+    CONS_GHOST
+  };
+
+  DefType           def_type;
+
+  StringTableEntry  cons_src_name;
+  StringTableEntry  cons_node_name;
+  F32               history_time;
+  U8                sample_rate;
+
+  bool              runs_on_server;
+  bool              runs_on_client;
+  bool              pos_at_box_center;
+  bool              treat_as_camera;
+
+  /*C*/             afxConstraintDef();
+
+  bool              isDefined();
+
+  bool              isArbitraryObject();
+  void              reset();
+  bool              parseSpec(const char* spec, bool runs_on_server, bool runs_on_client);
+
+  static void       gather_cons_defs(Vector<afxConstraintDef>& defs, Vector<afxEffectBaseData*>& fx);
+
+  static StringTableEntry  SCENE_CONS_KEY;
+  static StringTableEntry  EFFECT_CONS_KEY;
+  static StringTableEntry  GHOST_CONS_KEY;
+};
+
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxConstraint
+//  Abstract base-class for a simple constraint mechanism used to constrain
+//  special effects to spell related objects such as the spellcaster, target,
+//  projectile, or impact location.
+//
+//  note -- the direction vectors don't really fit... should probably consider separate
+//    constraint types for position, orientation, and possibly a look-at constraint.
+//
+
+class SceneObject;
+class afxConstraintMgr;
+  
+class afxConstraint : public SimObject, public afxEffectDefs
+{
+  friend class afxConstraintMgr;
+  typedef SimObject Parent;
+
+protected:
+  afxConstraintMgr* mgr;
+  afxConstraintDef  cons_def;
+  bool              is_defined;
+  bool              is_valid;
+  Point3F           last_pos;
+  MatrixF           last_xfm;
+  F32               history_time;
+  bool              is_alive;
+  bool              gone_missing;
+  U32               change_code;
+
+public:
+  /*C*/             afxConstraint(afxConstraintMgr*);
+  virtual           ~afxConstraint();
+
+  virtual bool      getPosition(Point3F& pos, F32 hist=0.0f) 
+                      { pos = last_pos; return is_valid; }
+  virtual bool      getTransform(MatrixF& xfm, F32 hist=0.0f) 
+                      { xfm = last_xfm; return is_valid;}
+  virtual bool      getAltitudes(F32& terrain_alt, F32& interior_alt) { return false; }
+
+  virtual bool      isDefined() { return is_defined; }
+  virtual bool      isValid() { return is_valid; }
+  virtual U32       getChangeCode() { return change_code; }
+
+  virtual U32       setAnimClip(const char* clip, F32 pos, F32 rate, F32 trans, bool is_death_anim) 
+                                  { return 0; };
+  virtual void      resetAnimation(U32 tag) { };
+  virtual U32       lockAnimation() { return 0; }
+  virtual void      unlockAnimation(U32 tag) { }
+  virtual F32       getAnimClipDuration(const char* clip) { return 0.0f; }
+
+  virtual S32       getDamageState() { return -1; }
+  virtual void      setLivingState(bool state) { is_alive = state; };
+  virtual bool      getLivingState() { return is_alive; };
+
+  virtual void      sample(F32 dt, U32 elapsed_ms, const Point3F* cam_pos)=0;
+
+  virtual SceneObject* getSceneObject()=0;
+  virtual void      restoreObject(SceneObject*)=0;
+  virtual U16       getScopeId()=0;
+
+  virtual U32       getTriggers()=0;
+
+  virtual void      set_scope_id(U16 scope_id) { }
+  virtual void      unset() { }
+};
+
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxConstraintMgr
+
+class ShapeBase;
+class afxEffectWrapper;
+class afxShapeConstraint;
+class afxObjectConstraint;
+class BitStream;
+class NetConnection;
+
+struct afxConstraintID
+{
+  S16 index;
+  S16 sub_index;
+
+  afxConstraintID() { index = -1; sub_index = 0; }
+  afxConstraintID(S16 idx, S16 sub=0) { index = idx; sub_index = sub; }
+  bool undefined() const { return (index < 0); }
+};
+ 
+typedef Vector<afxConstraint*> afxConstraintList;
+
+class afxConstraintMgr : public afxEffectDefs
+{
+  typedef SimObject Parent;
+
+  struct preDef
+  {
+    StringTableEntry  name;
+    U32               type;
+  };
+
+  Vector<afxConstraintList*>  constraints_v;
+
+  Vector<StringTableEntry>  names_on_server;
+  Vector<S32>               ghost_ids;
+  Vector<preDef>            predefs;
+  U32                       starttime;
+  bool                      on_server;
+  bool                      initialized;
+  F32                       scoping_dist_sq;
+
+  SceneObject*        find_object_from_name(StringTableEntry);
+  S32                 find_cons_idx_from_name(StringTableEntry);
+  S32                 find_effect_cons_idx_from_name(StringTableEntry);
+
+  void                create_constraint(const afxConstraintDef&);    
+  void                set_ref_shape(afxConstraintID which_id, ShapeBase*);
+  void                set_ref_shape(afxConstraintID which_id, U16 scope_id);
+
+public:
+  /*C*/               afxConstraintMgr();
+  /*D*/               ~afxConstraintMgr();
+
+  void                defineConstraint(U32 type, StringTableEntry);
+
+  afxConstraintID     setReferencePoint(StringTableEntry which, Point3F point);
+  afxConstraintID     setReferencePoint(StringTableEntry which, Point3F point, Point3F vector);
+  afxConstraintID     setReferenceTransform(StringTableEntry which, MatrixF& xfm);
+  afxConstraintID     setReferenceObject(StringTableEntry which, SceneObject*);
+  afxConstraintID     setReferenceObjectByScopeId(StringTableEntry which, U16 scope_id, bool is_shape);
+  afxConstraintID     setReferenceEffect(StringTableEntry which, afxEffectWrapper*);
+  afxConstraintID     createReferenceEffect(StringTableEntry which, afxEffectWrapper*);
+
+  void                setReferencePoint(afxConstraintID which_id, Point3F point);
+  void                setReferencePoint(afxConstraintID which_id, Point3F point, Point3F vector);
+  void                setReferenceTransform(afxConstraintID which_id, MatrixF& xfm);
+  void                setReferenceObject(afxConstraintID which_id, SceneObject*);
+  void                setReferenceObjectByScopeId(afxConstraintID which_id, U16 scope_id, bool is_shape);
+  void                setReferenceEffect(afxConstraintID which_id, afxEffectWrapper*);
+
+  void                invalidateReference(afxConstraintID which_id);
+                      
+  afxConstraintID     getConstraintId(const afxConstraintDef&);
+  afxConstraint*      getConstraint(afxConstraintID cons_id);
+
+  void                sample(F32 dt, U32 now, const Point3F* cam_pos=0);
+
+  void                setStartTime(U32 timestamp) { starttime = timestamp; }
+  void                initConstraintDefs(Vector<afxConstraintDef>&, bool on_server, F32 scoping_dist=-1.0f); 
+  void                packConstraintNames(NetConnection* conn, BitStream* stream);
+  void                unpackConstraintNames(BitStream* stream);
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//
+// scope-tracking
+private:
+  Vector<SceneObject*>  scopeable_objs;
+  Vector<U16>           scopeable_ids;
+  Vector<afxConstraint*>* missing_objs;
+  Vector<afxConstraint*>* missing_objs2;
+  Vector<afxConstraint*> missing_objs_a;
+  Vector<afxConstraint*> missing_objs_b;
+
+public:
+  void                  addScopeableObject(SceneObject*);
+  void                  removeScopeableObject(SceneObject*);
+  void                  clearAllScopeableObjs();
+
+  void                  postMissingConstraintObject(afxConstraint*, bool is_deleting=false);
+  void                  restoreScopedObject(SceneObject*, afxChoreographer* ch);
+  void                  adjustProcessOrdering(afxChoreographer*);
+
+  F32                   getScopingDistanceSquared() const { return scoping_dist_sq; }
+};
+
+inline afxConstraintID afxConstraintMgr::setReferencePoint(StringTableEntry which, Point3F point)
+{
+  return setReferencePoint(which, point, Point3F(0,0,1));
+}
+
+inline void afxConstraintMgr::setReferencePoint(afxConstraintID which, Point3F point) 
+{
+  setReferencePoint(which, point, Point3F(0,0,1));
+}
+
+
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxPointConstraint
+//  This constrains to a specific 3D position such as an impact location.
+//
+
+class afxPointConstraint : public afxConstraint
+{
+  typedef afxConstraint  Parent;
+
+protected:
+  Point3F           point;
+  Point3F           vector;
+
+public:
+  /*C*/             afxPointConstraint(afxConstraintMgr*);
+  virtual           ~afxPointConstraint();
+
+  virtual void      set(Point3F point, Point3F vector);
+  virtual void      sample(F32 dt, U32 elapsed_ms, const Point3F* cam_pos);
+
+  virtual SceneObject* getSceneObject() { return 0; }
+  virtual void      restoreObject(SceneObject*) { }
+  virtual U16       getScopeId() { return 0; }
+  virtual U32       getTriggers() { return 0; }
+
+  virtual void      unset() { set(Point3F::Zero, Point3F(0,0,1)); }
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxTransformConstraint
+//  This constrains to a specific 3D transformation.
+//
+
+class afxTransformConstraint : public afxConstraint
+{
+  typedef afxConstraint  Parent;
+
+protected:
+  MatrixF           xfm;
+
+public:
+  /*C*/             afxTransformConstraint(afxConstraintMgr*);
+  virtual           ~afxTransformConstraint();
+
+  virtual void      set(const MatrixF& xfm);
+  virtual void      sample(F32 dt, U32 elapsed_ms, const Point3F* cam_pos);
+
+  virtual SceneObject* getSceneObject() { return 0; }
+  virtual void      restoreObject(SceneObject*) { }
+  virtual U16       getScopeId() { return 0; }
+  virtual U32       getTriggers() { return 0; }
+
+  virtual void      unset() { set(MatrixF::Identity); }
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxShapeConstraint
+//  This constrains to a hierarchical shape (subclasses of ShapeBase), such as a 
+//  Player or a Vehicle. You can also constrain to named sub-nodes of a shape.
+
+class ShapeBase;
+class SceneObject;
+
+class afxShapeConstraint : public afxConstraint
+{
+  friend class afxConstraintMgr;
+  typedef afxConstraint  Parent;
+
+protected:
+  StringTableEntry  arb_name;
+  ShapeBase*        shape;
+  U16               scope_id;
+  U32               clip_tag;
+  U32               lock_tag;
+
+public:
+  /*C*/             afxShapeConstraint(afxConstraintMgr*);
+  /*C*/             afxShapeConstraint(afxConstraintMgr*, StringTableEntry arb_name);
+  virtual           ~afxShapeConstraint();
+
+  virtual void      set(ShapeBase* shape);
+  virtual void      set_scope_id(U16 scope_id);
+  virtual void      sample(F32 dt, U32 elapsed_ms, const Point3F* cam_pos);
+
+  virtual U32       setAnimClip(const char* clip, F32 pos, F32 rate, F32 trans, bool is_death_anim);
+  virtual void      resetAnimation(U32 tag);
+  virtual U32       lockAnimation();
+  virtual void      unlockAnimation(U32 tag);
+  virtual F32       getAnimClipDuration(const char* clip);
+
+  void              remapAnimation(U32 tag, ShapeBase* other_shape);
+
+  virtual S32       getDamageState();
+
+  virtual SceneObject* getSceneObject() { return shape; }
+  virtual void      restoreObject(SceneObject*);
+  virtual U16       getScopeId() { return scope_id; }
+  virtual U32       getTriggers();
+
+  virtual void      onDeleteNotify(SimObject*);
+
+  virtual void      unset() { set(0); }
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxShapeNodeConstraint
+
+class afxShapeNodeConstraint : public afxShapeConstraint
+{
+  friend class afxConstraintMgr;
+  typedef afxShapeConstraint  Parent;
+
+protected:
+  StringTableEntry  arb_node;
+  S32               shape_node_ID;
+
+public:
+  /*C*/             afxShapeNodeConstraint(afxConstraintMgr*);
+  /*C*/             afxShapeNodeConstraint(afxConstraintMgr*, StringTableEntry arb_name, StringTableEntry arb_node);
+
+  virtual void      set(ShapeBase* shape);
+  virtual void      set_scope_id(U16 scope_id);
+  virtual void      sample(F32 dt, U32 elapsed_ms, const Point3F* cam_pos);
+  virtual void      restoreObject(SceneObject*);
+
+  S32               getNodeID() const { return shape_node_ID; }
+
+  virtual void      onDeleteNotify(SimObject*);
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxObjectConstraint
+//  This constrains to a simple 3D object (subclasses of SceneObject), such as an 
+//  afxMagicMissile or a Projectile. You cannot constrain to sub-nodes with an
+//  afxObjectConstraint, use afxShapeConstraint instead.
+
+class SceneObject;
+
+class afxObjectConstraint : public afxConstraint
+{
+  friend class afxConstraintMgr;
+  typedef afxConstraint  Parent;
+
+protected:
+  StringTableEntry  arb_name;
+  SceneObject*      obj;
+  U16               scope_id;
+  bool              is_camera;
+
+public:
+                    afxObjectConstraint(afxConstraintMgr*);
+                    afxObjectConstraint(afxConstraintMgr*, StringTableEntry arb_name);
+  virtual           ~afxObjectConstraint();
+
+  virtual void      set(SceneObject* obj);
+  virtual void      set_scope_id(U16 scope_id);
+  virtual void      sample(F32 dt, U32 elapsed_ms, const Point3F* cam_pos);
+
+  virtual SceneObject* getSceneObject() { return obj; }
+  virtual void      restoreObject(SceneObject*);
+  virtual U16       getScopeId() { return scope_id; }
+  virtual U32       getTriggers();
+
+  virtual void      onDeleteNotify(SimObject*);
+
+  virtual void      unset() { set(0); }
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxEffectConstraint
+//  This constrains to a hierarchical shape (subclasses of ShapeBase), such as a 
+//  Player or a Vehicle. You can also constrain to named sub-nodes of a shape.
+
+class afxEffectWrapper;
+
+class afxEffectConstraint : public afxConstraint
+{
+  friend class afxConstraintMgr;
+  typedef afxConstraint  Parent;
+
+protected:
+  StringTableEntry  effect_name;
+  afxEffectWrapper* effect;
+  U32               clip_tag;
+  bool              is_death_clip;
+  U32               lock_tag;
+
+public:
+  /*C*/             afxEffectConstraint(afxConstraintMgr*);
+  /*C*/             afxEffectConstraint(afxConstraintMgr*, StringTableEntry effect_name);
+  virtual           ~afxEffectConstraint();
+
+  virtual bool      getPosition(Point3F& pos, F32 hist=0.0f); 
+  virtual bool      getTransform(MatrixF& xfm, F32 hist=0.0f);
+  virtual bool      getAltitudes(F32& terrain_alt, F32& interior_alt);
+
+  virtual void      set(afxEffectWrapper* effect);
+  virtual void      sample(F32 dt, U32 elapsed_ms, const Point3F* cam_pos) { }
+
+  virtual U32       setAnimClip(const char* clip, F32 pos, F32 rate, F32 trans, bool is_death_anim);
+  virtual void      resetAnimation(U32 tag);
+  virtual F32       getAnimClipDuration(const char* clip);
+
+  virtual SceneObject* getSceneObject() { return 0; }
+  virtual void      restoreObject(SceneObject*) { }
+  virtual U16       getScopeId() { return 0; }
+  virtual U32       getTriggers();
+
+  virtual void      unset() { set(0); }
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxEffectNodeConstraint
+
+class afxEffectNodeConstraint : public afxEffectConstraint
+{
+  friend class afxConstraintMgr;
+  typedef afxEffectConstraint  Parent;
+
+protected:
+  StringTableEntry  effect_node;
+  S32               effect_node_ID;
+
+public:
+  /*C*/             afxEffectNodeConstraint(afxConstraintMgr*);
+  /*C*/             afxEffectNodeConstraint(afxConstraintMgr*, StringTableEntry name, StringTableEntry node);
+
+  virtual bool      getPosition(Point3F& pos, F32 hist=0.0f); 
+  virtual bool      getTransform(MatrixF& xfm, F32 hist=0.0f);
+
+  virtual void      set(afxEffectWrapper* effect);
+
+  S32               getNodeID() const { return effect_node_ID; }
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxSampleBuffer
+
+class afxSampleBuffer
+{
+protected:
+  U32               buffer_sz;
+  U32               buffer_ms;
+  U32               ms_per_sample;
+  U32               elapsed_ms;
+  U32               last_sample_ms;
+  U32               next_sample_num;
+  U32               n_samples;
+
+  virtual void      recSample(U32 idx, void* data) = 0;
+  bool              compute_idx_from_lag(F32 lag, U32& idx);
+  bool              compute_idx_from_lag(F32 lag, U32& idx1, U32& idx2, F32& t);
+
+public:
+  /*C*/             afxSampleBuffer();
+  virtual           ~afxSampleBuffer();
+
+  virtual void      configHistory(F32 hist_len, U8 sample_rate);
+  void              recordSample(F32 dt, U32 elapsed_ms, void* data);
+  virtual void      getSample(F32 lag, void* data, bool& oob) = 0;
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxSampleXfmBuffer
+
+class afxSampleXfmBuffer : public afxSampleBuffer
+{
+  typedef afxSampleBuffer  Parent;
+
+protected:
+  MatrixF*          xfm_buffer;
+
+  virtual void      recSample(U32 idx, void* data);
+
+public:
+  /*C*/             afxSampleXfmBuffer();
+  virtual           ~afxSampleXfmBuffer();
+
+  virtual void      configHistory(F32 hist_len, U8 sample_rate);
+  virtual void      getSample(F32 lag, void* data, bool& oob);
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxPointHistConstraint
+//  This class extends afxPointConstraint to remember its values for a period of time.
+
+class afxPointHistConstraint : public afxPointConstraint
+{
+  friend class afxConstraintMgr;
+  typedef afxPointConstraint  Parent;
+
+protected:
+  afxSampleBuffer*  samples;
+
+public:
+  /*C*/             afxPointHistConstraint(afxConstraintMgr*);
+  virtual           ~afxPointHistConstraint();
+
+  virtual void      set(Point3F point, Point3F vector);
+  virtual void      sample(F32 dt, U32 elapsed_ms, const Point3F* cam_pos);
+
+  virtual bool      getPosition(Point3F& pos, F32 hist=0.0f);
+  virtual bool      getTransform(MatrixF& xfm, F32 hist=0.0f);
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxPointHistConstraint
+//  This class extends afxTransformConstraint to remember its values for a period of time.
+
+class afxTransformHistConstraint : public afxTransformConstraint
+{
+  friend class afxConstraintMgr;
+  typedef afxTransformConstraint  Parent;
+
+protected:
+  afxSampleBuffer*  samples;
+
+public:
+  /*C*/             afxTransformHistConstraint(afxConstraintMgr*);
+  virtual           ~afxTransformHistConstraint();
+
+  virtual void      set(const MatrixF& xfm);
+  virtual void      sample(F32 dt, U32 elapsed_ms, const Point3F* cam_pos);
+
+  virtual bool      getPosition(Point3F& pos, F32 hist=0.0f);
+  virtual bool      getTransform(MatrixF& xfm, F32 hist=0.0f);
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxShapeHistConstraint
+//  This class extends afxShapeConstraint to remember its values for a period of time.
+
+class afxShapeHistConstraint : public afxShapeConstraint
+{
+  friend class afxConstraintMgr;
+  typedef afxShapeConstraint  Parent;
+
+protected:
+  afxSampleBuffer*  samples;
+
+public:
+  /*C*/             afxShapeHistConstraint(afxConstraintMgr*);
+  /*C*/             afxShapeHistConstraint(afxConstraintMgr*, StringTableEntry arb_name);
+  virtual           ~afxShapeHistConstraint();
+
+  virtual void      set(ShapeBase* shape);
+  virtual void      set_scope_id(U16 scope_id);
+  virtual void      sample(F32 dt, U32 elapsed_ms, const Point3F* cam_pos);
+
+  virtual bool      getPosition(Point3F& pos, F32 hist=0.0f);
+  virtual bool      getTransform(MatrixF& xfm, F32 hist=0.0f);
+
+  virtual void      onDeleteNotify(SimObject*);
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxShapeNodeHistConstraint
+//  This class extends afxShapeConstraint to remember its values for a period of time.
+
+class afxShapeNodeHistConstraint : public afxShapeNodeConstraint
+{
+  friend class afxConstraintMgr;
+  typedef afxShapeNodeConstraint  Parent;
+
+protected:
+  afxSampleBuffer*  samples;
+
+public:
+  /*C*/             afxShapeNodeHistConstraint(afxConstraintMgr*);
+  /*C*/             afxShapeNodeHistConstraint(afxConstraintMgr*, StringTableEntry arb_name, StringTableEntry arb_node);
+  virtual           ~afxShapeNodeHistConstraint();
+
+  virtual void      set(ShapeBase* shape);
+  virtual void      set_scope_id(U16 scope_id);
+  virtual void      sample(F32 dt, U32 elapsed_ms, const Point3F* cam_pos);
+
+  virtual bool      getPosition(Point3F& pos, F32 hist=0.0f);
+  virtual bool      getTransform(MatrixF& xfm, F32 hist=0.0f);
+
+  virtual void      onDeleteNotify(SimObject*);
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxObjectHistConstraint
+//  This class extends afxObjectConstraint to remember its values for a period of time.
+
+class SceneObject;
+
+class afxObjectHistConstraint : public afxObjectConstraint
+{
+  friend class afxConstraintMgr;
+  typedef afxObjectConstraint  Parent;
+
+protected:
+  afxSampleBuffer*  samples;
+
+public:
+                    afxObjectHistConstraint(afxConstraintMgr*);
+                    afxObjectHistConstraint(afxConstraintMgr*, StringTableEntry arb_name);
+  virtual           ~afxObjectHistConstraint();
+
+  virtual void      set(SceneObject* obj);
+  virtual void      set_scope_id(U16 scope_id);
+  virtual void      sample(F32 dt, U32 elapsed_ms, const Point3F* cam_pos);
+
+  virtual bool      getPosition(Point3F& pos, F32 hist=0.0f);
+  virtual bool      getTransform(MatrixF& xfm, F32 hist=0.0f);
+
+  virtual void      onDeleteNotify(SimObject*);
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#endif // _AFX_CONSTRAINT_H_
+

+ 114 - 0
Engine/source/afx/afxEffectDefs.h

@@ -0,0 +1,114 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
+// Copyright (C) 2015 Faust Logic, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _AFX_EFFECT_DEFS_H_
+#define _AFX_EFFECT_DEFS_H_
+
+#include "afx/arcaneFX.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxEffectBASE
+
+class afxEffectDefs
+{
+public:
+
+  enum
+  {
+    MAX_EFFECTS_PER_PHRASE  = 1023,
+    EFFECTS_PER_PHRASE_BITS = 10
+  };
+
+  // effect networking
+  enum
+  {
+    SERVER_ONLY       = BIT(0),
+    SCOPE_ALWAYS      = BIT(1),
+    GHOSTABLE         = BIT(2),
+    CLIENT_ONLY       = BIT(3),
+    SERVER_AND_CLIENT = BIT(4)
+  };
+  
+  // effect condititons
+  enum 
+  {
+    DISABLED = BIT(0),
+    ENABLED = BIT(1),
+    FAILING = BIT(2),
+    ALIVE = ENABLED,
+    DEAD = DISABLED,
+    DYING = FAILING,
+    //
+    IMPACTED_SOMETHING  = BIT(31),
+    IMPACTED_TARGET     = BIT(30),
+    IMPACTED_PRIMARY    = BIT(29),
+    IMPACT_IN_WATER     = BIT(28),
+    CASTER_IN_WATER     = BIT(27),
+  };
+
+  enum
+  {
+    REQUIRES_STOP     = BIT(0),
+    RUNS_ON_SERVER    = BIT(1),
+    RUNS_ON_CLIENT    = BIT(2),
+  };
+
+  enum 
+  {
+    MAX_XFM_MODIFIERS = 32,
+    INFINITE_LIFETIME = (24*60*60)
+  };
+
+  enum
+  {
+    POINT_CONSTRAINT,
+    TRANSFORM_CONSTRAINT,
+    OBJECT_CONSTRAINT,
+    CAMERA_CONSTRAINT,
+    OBJECT_CONSTRAINT_SANS_OBJ,
+    OBJECT_CONSTRAINT_SANS_SHAPE,
+    UNDEFINED_CONSTRAINT_TYPE
+  };
+
+  enum
+  {
+    DIRECT_DAMAGE,
+    DAMAGE_OVER_TIME,
+    AREA_DAMAGE
+  };
+
+  enum
+  {
+    TIMING_DELAY      = BIT(0), 
+    TIMING_LIFETIME   = BIT(1),
+    TIMING_FADE_IN    = BIT(2),
+    TIMING_FADE_OUT   = BIT(3),
+    TIMING_BITS       = 2
+  };
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#endif // _AFX_EFFECT_DEFS_H_

+ 270 - 0
Engine/source/afx/afxEffectGroup.cpp

@@ -0,0 +1,270 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
+// Copyright (C) 2015 Faust Logic, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#include "afx/arcaneFX.h"
+
+#include "console/engineAPI.h"
+
+#include "afx/afxEffectGroup.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxEffectGroupData::egValidator
+//
+// When an effect is added using "addEffect", this validator intercepts the value
+// and adds it to the dynamic effects list. 
+//
+void afxEffectGroupData::egValidator::validateType(SimObject* object, void* typePtr)
+{
+  afxEffectGroupData* eff_data = dynamic_cast<afxEffectGroupData*>(object);
+  afxEffectBaseData** ew = (afxEffectBaseData**)(typePtr);
+
+  if (eff_data && ew)
+  {
+    eff_data->fx_list.push_back(*ew);
+    *ew = 0;
+  }
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxEffectGroupData
+
+IMPLEMENT_CO_DATABLOCK_V1(afxEffectGroupData);
+
+ConsoleDocClass( afxEffectGroupData,
+   "@brief A datablock that describes an Effect Group.\n\n"
+
+   "afxEffectGroupData provides a way for adding several effects to a choreographer as a "
+   "group and can be used wherever an afxEffectWrapperData is used. Basically, an "
+   "effect-group is a simple list of effect-wrappers. When an effect-group is added to a "
+   "choreographer, the end result is almost the same as adding all of the group's "
+   "effect-wrappers directly to the choreographer. The main difference is that the "
+   "grouped effects can be turned on and off collectively and created in multiples. "
+   "Effect-groups can also contain other effect-groups, forming a hierarchy of effects.\n\n"
+
+   "A great strength of effect-groups is that they have a count setting that multiplies "
+   "the number of times the effects in the group are added to the owning choreographer "
+   "and this doesn't happen until the choreographer instance is created and launched. "
+   "This makes a big difference for certain kinds of effects, such as fireworks, that "
+   "tend to consist of small groupings of effects that are repeated many times with "
+   "slight variations. With groups, an effect like this has a very compact representation "
+   "for transmitting from server to clients, that only expands when actually used.\n\n"
+
+   "Effect-groups with a count greater than one are extremely useful when some of the "
+   "effects use field substitutions. When an effect-group is expanded, it essentially runs "
+   "through a for-loop from 0 to count-1 and creates a new set of effect instances each "
+   "time through the loop. For each new set of effects, their group-index is set to the "
+   "index of this for-loop, which in turn replaces the ## token used in any field "
+   "substitutions in the child effects. In essence, the for-loop index becomes a parameter "
+   "of the child effects which can be used to vary the effects created in each loop.\n\n"
+
+   "@see afxEffectBaseData\n\n"
+   "@see afxEffectWrapperData\n\n"
+
+   "@ingroup afxEffects\n"
+   "@ingroup AFX\n"
+   "@ingroup Datablocks\n"
+);
+
+afxEffectGroupData::afxEffectGroupData()
+{
+  group_enabled = true;
+  group_count = 1;
+  idx_offset = 0;
+  assign_idx = false;
+
+  // dummy entry holds effect-wrapper pointer while a special validator
+  // grabs it and adds it to an appropriate effects list
+  dummy_fx_entry = NULL;
+
+  // marked true if datablock ids need to
+  // be converted into pointers
+  do_id_convert = false;
+}
+
+afxEffectGroupData::afxEffectGroupData(const afxEffectGroupData& other, bool temp_clone) : afxEffectBaseData(other, temp_clone)
+{
+  group_enabled = other.group_enabled;
+  group_count = other.group_count;
+  idx_offset = other.idx_offset;
+  assign_idx = other.assign_idx;
+  timing = other.timing;
+  dummy_fx_entry = other.dummy_fx_entry;
+  do_id_convert = other.do_id_convert; // --
+  fx_list = other.fx_list; // -- 
+}
+
+void afxEffectGroupData::reloadReset()
+{
+  fx_list.clear();
+}
+
+void afxEffectGroupData::pack_fx(BitStream* stream, const afxEffectList& fx, bool packed)
+{
+  stream->writeInt(fx.size(), EFFECTS_PER_PHRASE_BITS);
+  for (int i = 0; i < fx.size(); i++)
+    writeDatablockID(stream, fx[i], packed);
+}
+
+void afxEffectGroupData::unpack_fx(BitStream* stream, afxEffectList& fx)
+{
+  fx.clear();
+  S32 n_fx = stream->readInt(EFFECTS_PER_PHRASE_BITS);
+  for (int i = 0; i < n_fx; i++)
+    fx.push_back((afxEffectWrapperData*)readDatablockID(stream));
+}
+
+#define myOffset(field) Offset(field, afxEffectGroupData)
+
+void afxEffectGroupData::initPersistFields()
+{
+  addField("groupEnabled",   TypeBool,    myOffset(group_enabled),
+    "...");
+  addField("count",          TypeS32,     myOffset(group_count),
+    "...");
+  addField("indexOffset",    TypeS8,      myOffset(idx_offset),
+    "...");
+  addField("assignIndices",  TypeBool,    myOffset(assign_idx),
+    "...");
+
+  addField("delay",          TypeF32,     myOffset(timing.delay),
+    "...");
+  addField("lifetime",       TypeF32,     myOffset(timing.lifetime),
+    "...");
+  addField("fadeInTime",     TypeF32,     myOffset(timing.fade_in_time),
+    "...");
+  addField("fadeOutTime",    TypeF32,     myOffset(timing.fade_out_time),
+    "...");
+
+  // effect lists
+  // for each of these, dummy_fx_entry is set and then a validator adds it to the appropriate effects list 
+  static egValidator emptyValidator(0);
+  
+  addFieldV("addEffect",  TYPEID<afxEffectBaseData>(),  myOffset(dummy_fx_entry), &emptyValidator,
+    "...");
+
+  Parent::initPersistFields();
+
+  // disallow some field substitutions
+  disableFieldSubstitutions("addEffect");
+}
+
+void afxEffectGroupData::packData(BitStream* stream)
+{
+	Parent::packData(stream);
+
+  stream->writeFlag(group_enabled);
+  stream->write(group_count);
+  stream->write(idx_offset);
+  stream->writeFlag(assign_idx);
+  stream->write(timing.delay);
+  stream->write(timing.lifetime);
+  stream->write(timing.fade_in_time);
+  stream->write(timing.fade_out_time);
+
+  pack_fx(stream, fx_list, packed);
+}
+
+void afxEffectGroupData::unpackData(BitStream* stream)
+{
+  Parent::unpackData(stream);
+
+  group_enabled = stream->readFlag();
+  stream->read(&group_count);
+  stream->read(&idx_offset);
+  assign_idx = stream->readFlag();
+  stream->read(&timing.delay);
+  stream->read(&timing.lifetime);
+  stream->read(&timing.fade_in_time);
+  stream->read(&timing.fade_out_time);
+
+  do_id_convert = true;
+  unpack_fx(stream, fx_list);
+}
+
+bool afxEffectGroupData::preload(bool server, String &errorStr)
+{
+  if (!Parent::preload(server, errorStr))
+    return false;
+
+  // Resolve objects transmitted from server
+  if (!server) 
+  {
+    if (do_id_convert)
+    {
+      for (S32 i = 0; i < fx_list.size(); i++)
+      {
+        SimObjectId db_id = SimObjectId((uintptr_t)fx_list[i]);
+        if (db_id != 0)
+        {
+          // try to convert id to pointer
+          if (!Sim::findObject(db_id, fx_list[i]))
+          {
+            Con::errorf(ConsoleLogEntry::General, 
+              "afxEffectGroupData::preload() -- bad datablockId: 0x%x", 
+              db_id);
+          }
+        }
+      }
+      do_id_convert = false;
+    }
+  }
+
+  return true;
+}
+
+void afxEffectGroupData::gather_cons_defs(Vector<afxConstraintDef>& defs)
+{
+  for (S32 i = 0; i < fx_list.size(); i++)
+  {
+    if (fx_list[i])
+      fx_list[i]->gather_cons_defs(defs);
+  }
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//
+
+DefineEngineMethod(afxEffectGroupData, reset, void, (),,
+                   "Resets an effect-group datablock during reload.\n\n"
+                   "@ingroup AFX")
+{
+  object->reloadReset();
+}
+
+DefineEngineMethod(afxEffectGroupData, addEffect, void, (afxEffectBaseData* effect),,
+                   "Adds an effect (wrapper or group) to an effect-group.\n\n"
+                   "@ingroup AFX")
+{
+  if (!effect) 
+  {
+    Con::errorf("afxEffectGroupData::addEffect() -- missing afxEffectWrapperData.");
+    return;
+  }
+  
+  object->fx_list.push_back(effect);
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+

+ 103 - 0
Engine/source/afx/afxEffectGroup.h

@@ -0,0 +1,103 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
+// Copyright (C) 2015 Faust Logic, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _AFX_EFFECT_GROUP_H_
+#define _AFX_EFFECT_GROUP_H_
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#include "console/typeValidators.h"
+
+#include "afx/afxEffectDefs.h"
+#include "afx/afxEffectWrapper.h"
+
+class afxEffectWrapperData;
+
+struct afxGroupTimingData
+{
+  F32     delay;
+  F32     lifetime;
+  F32     fade_in_time;
+  F32     fade_out_time;
+
+  afxGroupTimingData()
+  {
+    delay = 0.0f;
+    lifetime = 0.0f;
+    fade_in_time = 0.0f;
+    fade_out_time = 0.0f;
+  }
+};
+
+class afxEffectGroupData : public afxEffectBaseData
+{
+  typedef afxEffectBaseData Parent;
+
+  class egValidator : public TypeValidator
+  {
+    U32 id;
+  public:
+    egValidator(U32 id) { this->id = id; }
+    void validateType(SimObject *object, void *typePtr);
+  };
+
+  bool          do_id_convert;
+
+public:
+  afxEffectList       fx_list;
+  bool                group_enabled;
+  S32                 group_count;
+  U8                  idx_offset;
+  bool                assign_idx;
+  afxGroupTimingData  timing;
+  afxEffectBaseData*  dummy_fx_entry;
+
+private:
+  void          pack_fx(BitStream* stream, const afxEffectList& fx, bool packed);
+  void          unpack_fx(BitStream* stream, afxEffectList& fx);
+  
+public:
+  /*C*/         afxEffectGroupData();
+  /*C*/         afxEffectGroupData(const afxEffectGroupData&, bool = false);
+
+  virtual void  reloadReset();
+
+  virtual void  packData(BitStream*);
+  virtual void  unpackData(BitStream*);
+
+  bool          preload(bool server, String &errorStr);
+
+  virtual void  gather_cons_defs(Vector<afxConstraintDef>& defs);
+
+  virtual bool  allowSubstitutions() const { return true; }
+
+  static void   initPersistFields();
+
+  DECLARE_CONOBJECT(afxEffectGroupData);
+  DECLARE_CATEGORY("AFX");
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+#endif // _AFX_EFFECT_GROUP_H_

+ 358 - 0
Engine/source/afx/afxEffectVector.cpp

@@ -0,0 +1,358 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
+// Copyright (C) 2015 Faust Logic, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#include "afx/arcaneFX.h"
+
+#include "afxChoreographer.h"
+#include "afxEffectVector.h"
+#include "afxConstraint.h"
+#include "afxEffectWrapper.h"
+#include "afxEffectGroup.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+void afxEffectVector::filter_client_server()
+{
+  if (empty())
+    return;
+
+  for (S32 i = 0; i < fx_v->size(); i++)
+  {
+    if ((*fx_v)[i]->datablock->runsHere(on_server))
+      fx_v2->push_back((*fx_v)[i]);
+    else
+    {
+      delete (*fx_v)[i];
+      (*fx_v)[i] = 0;
+    }
+  }
+
+  swap_vecs();
+
+  fx_v2->clear();
+}
+
+void afxEffectVector::calc_fx_dur_and_afterlife()
+{
+  total_fx_dur = 0.0f;
+  after_life = 0.0f;
+
+  if (empty())
+    return;
+
+  for (S32 i = 0; i < fx_v->size(); i++)
+  {
+    afxEffectWrapper* ew = (*fx_v)[i];
+    if (ew)
+    {
+      F32 ew_dur;
+      if (ew->ew_timing.lifetime < 0)
+      {
+        if (phrase_dur > ew->ew_timing.delay)
+          ew_dur = phrase_dur + ew->afterStopTime();
+        else
+          ew_dur = ew->ew_timing.delay + ew->afterStopTime();
+      }
+      else
+        ew_dur = ew->ew_timing.delay + ew->ew_timing.lifetime + ew->ew_timing.fade_out_time;
+
+      if (ew_dur > total_fx_dur)
+        total_fx_dur = ew_dur;
+
+      F32 after = ew->afterStopTime();
+      if (after > after_life)
+        after_life = after;
+    }
+  }
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//
+
+afxEffectVector::afxEffectVector()
+{
+  fx_v = 0;
+  fx_v2 = 0;
+  active = false;
+  on_server = false;
+  total_fx_dur = 0;
+  after_life = 0;
+}
+
+afxEffectVector::~afxEffectVector()
+{
+  stop(true);
+  delete fx_v;
+  delete fx_v2;
+}
+
+void afxEffectVector::effects_init(afxChoreographer* chor, afxEffectList& effects, bool will_stop, F32 time_factor, 
+                                   S32 group_index, const afxGroupTimingData* group_timing)
+{ 
+  afxConstraintMgr* cons_mgr = chor->getConstraintMgr();
+
+  for (S32 i = 0; i < effects.size(); i++)
+  {
+    if (dynamic_cast<afxEffectGroupData*>(effects[i]))
+    {
+      afxEffectGroupData* eg = (afxEffectGroupData*)effects[i];
+      if (eg->getSubstitutionCount() > 0)
+      {
+        // clone the datablock and perform substitutions
+        afxEffectGroupData* orig_db = eg;
+        eg = new afxEffectGroupData(*orig_db, true);
+        orig_db->performSubstitutions(eg, chor, group_index);
+      }
+
+      if (eg->group_enabled)
+      {
+        if (eg->assign_idx)
+        {
+          for (S32 j = 0; j < eg->group_count; j++)
+            effects_init(chor, eg->fx_list, will_stop, time_factor, j+eg->idx_offset, &eg->timing);
+        }
+        else
+        {
+          for (S32 j = 0; j < eg->group_count; j++)
+            effects_init(chor, eg->fx_list, will_stop, time_factor, group_index, &eg->timing);
+        }
+      }
+
+      if (eg->isTempClone())
+        delete eg;
+    }
+    else if (dynamic_cast<afxEffectWrapperData*>(effects[i]))
+    {
+      afxEffectWrapperData* ewd = (afxEffectWrapperData*)effects[i];
+
+      if (ewd->getSubstitutionCount() > 0)
+      {
+        // clone the ewd and perform substitutions
+        afxEffectWrapperData* orig_db = ewd;
+        ewd = new afxEffectWrapperData(*orig_db, true);
+        orig_db->performSubstitutions(ewd, chor, group_index);
+      }
+
+      if (ewd->effect_enabled)
+      {
+        static afxEffectTimingData inherited_timing;
+        bool use_inherited_timing = false;
+        if (ewd->inherit_timing != 0)
+        {
+          if (group_timing)
+          {
+            inherited_timing = ewd->ewd_timing;
+            if ((ewd->inherit_timing & afxEffectDefs::TIMING_DELAY) != 0)
+              inherited_timing.delay = group_timing->delay;
+            if ((ewd->inherit_timing & afxEffectDefs::TIMING_LIFETIME) != 0)
+              inherited_timing.lifetime = group_timing->lifetime;
+            if ((ewd->inherit_timing & afxEffectDefs::TIMING_FADE_IN) != 0)
+              inherited_timing.fade_in_time = group_timing->fade_in_time;
+            if ((ewd->inherit_timing & afxEffectDefs::TIMING_FADE_OUT) != 0)
+              inherited_timing.fade_out_time = group_timing->fade_out_time;
+          }
+          else
+          {
+            Con::warnf("afxEffectVector::effects_init() -- %s::inheritGroupTiming is non-zero but wrapper is not in a group.");
+          }
+        }
+
+        const afxEffectTimingData& timing = (use_inherited_timing) ? inherited_timing : ewd->ewd_timing;
+
+        if ( (will_stop || !ewd->requiresStop(timing)) && 
+             (chor->testRanking(ewd->ranking_range.low, ewd->ranking_range.high)) &&
+             (chor->testLevelOfDetail(ewd->lod_range.low, ewd->lod_range.high)) && 
+             (ewd->testExecConditions(chor->getExecConditions()))
+            )
+        {
+          afxEffectWrapper* effect;
+          effect = afxEffectWrapper::ew_create(chor, ewd, cons_mgr, time_factor, group_index);
+          if (effect)
+            fx_v->push_back(effect);
+        }      
+      }
+      else
+      {
+        if (ewd->isTempClone())
+          delete ewd;
+      }
+
+    }
+  }
+}
+
+void afxEffectVector::ev_init(afxChoreographer* chor, afxEffectList& effects, bool on_server, 
+                              bool will_stop, F32 time_factor, F32 phrase_dur, S32 group_index)
+{
+  this->on_server = on_server;
+  this->phrase_dur = phrase_dur;
+
+  fx_v = new Vector<afxEffectWrapper*>;
+
+  effects_init(chor, effects, will_stop, time_factor, group_index);
+
+  fx_v2 = new Vector<afxEffectWrapper*>(fx_v->size());
+}
+
+void afxEffectVector::start(F32 timestamp)
+{
+  if (empty())
+    return;
+
+  // At this point both client and server effects are in the list.
+  // Timing adjustments are made during prestart().
+  for (S32 i = 0; i < fx_v->size(); i++)
+    (*fx_v)[i]->prestart();
+
+  // duration and afterlife values are pre-calculated here
+  calc_fx_dur_and_afterlife();
+
+  // now we filter out client-only or server-only effects that
+  // don't belong here,
+  filter_client_server();
+
+  active = true;
+
+  for (S32 j = 0; j < fx_v->size(); j++)
+  {
+    if ((*fx_v)[j]->start(timestamp))
+      fx_v2->push_back((*fx_v)[j]);
+    else
+    {
+      delete (*fx_v)[j];
+      (*fx_v)[j] = 0;
+    }
+  }
+
+  swap_vecs();
+  fx_v2->clear();
+}
+
+void afxEffectVector::update(F32 dt)
+{
+  if (empty())
+  {
+    active = false;
+    return;
+  }
+
+  for (int i = 0; i < fx_v->size(); i++)
+  {
+    (*fx_v)[i]->update(dt);
+
+    if ((*fx_v)[i]->isDone() || (*fx_v)[i]->isAborted())
+    {
+      // effect has ended, cleanup and delete
+      (*fx_v)[i]->cleanup();
+      delete (*fx_v)[i];
+      (*fx_v)[i] = 0;
+    }
+    else
+    {
+      // effect is still going, so keep it around
+      fx_v2->push_back((*fx_v)[i]);
+    }
+  }
+
+  swap_vecs();
+
+  fx_v2->clear();
+
+  if (empty())
+  {
+    active = false;
+    delete fx_v; fx_v =0;
+    delete fx_v2; fx_v2 = 0;
+  }
+}
+
+void afxEffectVector::stop(bool force_cleanup)
+{
+  if (empty())
+  {
+    active = false;
+    return;
+  }
+
+  for (int i = 0; i < fx_v->size(); i++)
+  {
+    (*fx_v)[i]->stop();
+
+    if (force_cleanup || (*fx_v)[i]->deleteWhenStopped())
+    {
+      // effect is over when stopped, cleanup and delete 
+      (*fx_v)[i]->cleanup();
+      delete (*fx_v)[i];
+      (*fx_v)[i] = 0;
+    }
+    else
+    {
+      // effect needs to fadeout or something, so keep it around
+      fx_v2->push_back((*fx_v)[i]);
+    }
+  }
+
+  swap_vecs();
+
+  fx_v2->clear();
+
+  if (empty())
+  {
+    active = false;
+    delete fx_v; fx_v =0;
+    delete fx_v2; fx_v2 = 0;
+  }
+}
+
+void afxEffectVector::interrupt()
+{
+  if (empty())
+  {
+    active = false;
+    return;
+  }
+
+  for (int i = 0; i < fx_v->size(); i++)
+  {
+    (*fx_v)[i]->stop();
+    (*fx_v)[i]->cleanup();
+    delete (*fx_v)[i];
+    (*fx_v)[i] = 0;
+  }
+
+  swap_vecs();
+
+  fx_v2->clear();
+
+  if (empty())
+  {
+    active = false;
+    delete fx_v; fx_v =0;
+    delete fx_v2; fx_v2 = 0;
+  }
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+

+ 86 - 0
Engine/source/afx/afxEffectVector.h

@@ -0,0 +1,86 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
+// Copyright (C) 2015 Faust Logic, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _AFX_EFFECT_VECTOR_H_
+#define _AFX_EFFECT_VECTOR_H_
+
+#include "afx/afxEffectWrapper.h"
+#include "afx/afxEffectGroup.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxEffectVector
+
+class afxEffectWrapper;
+class afxChoreographer;
+
+class afxEffectVector
+{
+  Vector<afxEffectWrapper*>*  fx_v;
+  Vector<afxEffectWrapper*>*  fx_v2;
+
+  bool          active;
+  bool          on_server;
+  F32           phrase_dur;
+  F32           total_fx_dur;
+  F32           after_life;
+
+  void          swap_vecs();
+  void          filter_client_server();
+  void          calc_fx_dur_and_afterlife();
+
+  void          effects_init(afxChoreographer*, afxEffectList&, bool will_stop, F32 time_factor, 
+                             S32 group_index, const afxGroupTimingData* group_timing=0);
+
+public:
+  /*C*/         afxEffectVector();
+  /*D*/         ~afxEffectVector();
+
+  void          ev_init(afxChoreographer*, afxEffectList&, bool on_server, bool will_stop, 
+                        F32 time_factor, F32 phrase_dur, S32 group_index=0);
+
+  void          start(F32 timestamp);
+  void          update(F32 dt);
+  void          stop(bool force_cleanup=false);
+  void          interrupt();
+  bool          empty() { return (!fx_v || fx_v->empty()); }
+  bool          isActive() { return active; }
+  S32           count() { return (fx_v) ? fx_v->size() : 0; }
+
+  F32           getTotalDur() { return total_fx_dur; }
+  F32           getAfterLife() { return after_life; }
+
+  Vector<afxEffectWrapper*>* getFX() { return fx_v; }
+};
+
+inline void afxEffectVector::swap_vecs()
+{
+  Vector<afxEffectWrapper*>* tmp = fx_v;
+  fx_v = fx_v2;
+  fx_v2 = tmp;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#endif // _AFX_EFFECT_VECTOR_H_

+ 1193 - 0
Engine/source/afx/afxEffectWrapper.cpp

@@ -0,0 +1,1193 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
+// Copyright (C) 2015 Faust Logic, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#include "afx/arcaneFX.h"
+
+#include "math/mathIO.h"
+
+#include "afx/ce/afxComponentEffect.h"
+#include "afx/afxResidueMgr.h"
+#include "afx/afxChoreographer.h"
+#include "afx/afxConstraint.h"
+#include "afx/xm/afxXfmMod.h"
+#include "afx/afxEffectWrapper.h"
+#include "afx/util/afxAnimCurve.h"
+#include "afx/util/afxEase.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxEffectWrapperData
+
+IMPLEMENT_CO_DATABLOCK_V1(afxEffectBaseData);
+
+ConsoleDocClass( afxEffectBaseData,
+   "@brief A datablock baseclass for afxEffectWrapperData and afxEffectGroupData.\n\n"
+
+   "Not intended to be used directly, afxEffectBaseData exists to provide base member "
+   "variables and generic functionality for the derived classes afxEffectWrapperData and "
+   "afxEffectGroupData.\n\n"
+
+   "@see afxEffectWrapperData\n\n"
+   "@see afxEffectGroupData\n\n"
+
+   "@ingroup afxEffects\n"
+   "@ingroup AFX\n"
+   "@ingroup Datablocks\n"
+);
+
+IMPLEMENT_CO_DATABLOCK_V1(afxEffectWrapperData);
+
+ConsoleDocClass( afxEffectWrapperData,
+   "@brief A datablock that describes an Effect Wrapper.\n\n"
+
+   "Conceptually an effect wrapper encloses a building-block effect and acts "
+   "as a handle for adding the effect to a choreographer. Effect wrapper fields "
+   "primarily deal with effect timing, constraints, and conditional effect execution.\n\n"
+
+   "@see afxEffectBaseData\n\n"
+   "@see afxEffectGroupData\n\n"
+
+   "@ingroup afxEffects\n"
+   "@ingroup AFX\n"
+   "@ingroup Datablocks\n"
+);
+
+afxEffectWrapperData::afxEffectWrapperData()
+{
+  effect_name = ST_NULLSTRING;
+  effect_data = 0;
+  effect_desc = 0;
+  data_ID = 0;
+  use_as_cons_obj = false;
+  use_ghost_as_cons_obj = false;
+
+  // constraint data
+  cons_spec = ST_NULLSTRING;
+  pos_cons_spec = ST_NULLSTRING;
+  orient_cons_spec = ST_NULLSTRING;
+  aim_cons_spec = StringTable->insert("camera");
+  life_cons_spec = ST_NULLSTRING;
+
+  // conditional execution flags
+  effect_enabled = true;
+  ranking_range.set(0,255);
+  lod_range.set(0,255);
+  life_conds = 0;
+  for (S32 i = 0; i < MAX_CONDITION_STATES; i++)
+  { 
+    exec_cond_on_bits[i] = 0;
+    exec_cond_off_bits[i] = 0;
+    exec_cond_bitmasks[i] = 0;
+  }
+
+  ewd_timing.lifetime = -1;
+
+  user_fade_out_time = 0.0;
+
+  is_looping = false;
+  n_loops = 0;
+  loop_gap_time = 0.0f;
+
+  ignore_time_factor = false;
+  propagate_time_factor = false;
+
+  // residue settings
+
+  // scaling factors
+  rate_factor = 1.0f;
+  scale_factor = 1.0f;
+
+  dMemset(xfm_modifiers, 0, sizeof(xfm_modifiers));
+
+  forced_bbox.minExtents.set(1,1,1);
+  forced_bbox.maxExtents.set(-1,-1,-1);
+
+  update_forced_bbox = false;
+
+  // marked true if datablock ids need to
+  // be converted into pointers
+  do_id_convert = false;
+
+  sort_priority = 0;
+  direction.set(0,1,0);
+  speed = 0.0f;
+  mass = 1.0f;
+
+  borrow_altitudes = false;
+  vis_keys_spec = ST_NULLSTRING;
+  vis_keys = 0;
+
+  group_index = -1;
+  inherit_timing = 0;
+}
+
+afxEffectWrapperData::afxEffectWrapperData(const afxEffectWrapperData& other, bool temp_clone) : afxEffectBaseData(other, temp_clone)
+{
+  effect_name = other.effect_name;
+  effect_data = other.effect_data;
+  effect_desc = other.effect_desc;
+  data_ID = other.data_ID;
+  use_as_cons_obj = other.use_as_cons_obj;
+  use_ghost_as_cons_obj = other.use_ghost_as_cons_obj;
+  cons_spec = other.cons_spec;
+  pos_cons_spec = other.pos_cons_spec;
+  orient_cons_spec = other.orient_cons_spec;
+  aim_cons_spec = other.aim_cons_spec;
+  life_cons_spec = other.life_cons_spec;
+  cons_def = other.cons_def;
+  pos_cons_def = other.pos_cons_def;
+  orient_cons_def = other.orient_cons_def;
+  aim_cons_def = other.aim_cons_def;
+  life_cons_def = other.life_cons_def;
+  effect_enabled = other.effect_enabled;
+  ranking_range = other.ranking_range;
+  lod_range = other.lod_range;
+  life_conds = other.life_conds;
+  dMemcpy(exec_cond_on_bits, other.exec_cond_on_bits, sizeof(exec_cond_on_bits));
+  dMemcpy(exec_cond_off_bits, other.exec_cond_off_bits, sizeof(exec_cond_off_bits));
+  dMemcpy(exec_cond_bitmasks, other.exec_cond_bitmasks, sizeof(exec_cond_bitmasks));
+  ewd_timing = other.ewd_timing;
+  user_fade_out_time = other.user_fade_out_time;
+  is_looping = other.is_looping;
+  n_loops = other.n_loops;
+  loop_gap_time = other.loop_gap_time;
+  ignore_time_factor = other.ignore_time_factor;
+  propagate_time_factor = other.propagate_time_factor;
+  rate_factor = other.rate_factor;
+  scale_factor = other.scale_factor;
+  dMemcpy(xfm_modifiers, other.xfm_modifiers, sizeof(xfm_modifiers));
+  forced_bbox = other.forced_bbox;
+  update_forced_bbox = other.update_forced_bbox;
+  do_id_convert = other.do_id_convert;
+  sort_priority = other.sort_priority;
+  direction = other.direction;
+  speed = other.speed;
+  mass = other.mass;
+  borrow_altitudes = other.borrow_altitudes;
+  vis_keys_spec = other.vis_keys_spec;
+  vis_keys = other.vis_keys;
+  if (other.vis_keys)
+  {
+    vis_keys = new afxAnimCurve();
+    for (S32 i = 0; i < other.vis_keys->numKeys(); i++)
+    {
+      F32 when = other.vis_keys->getKeyTime(i);
+      F32 what = other.vis_keys->getKeyValue(i);
+      vis_keys->addKey(when, what);
+    }
+  }
+  else
+    vis_keys = 0;
+  group_index = other.group_index;
+  inherit_timing = other.inherit_timing;
+}
+
+afxEffectWrapperData::~afxEffectWrapperData()
+{
+  if (vis_keys)
+    delete vis_keys;
+}
+
+#define myOffset(field) Offset(field, afxEffectWrapperData)
+
+void afxEffectWrapperData::initPersistFields()
+{
+  // the wrapped effect
+  addField("effect",       TYPEID<SimDataBlock>(),    myOffset(effect_data),
+    "...");
+  addField("effectName",   TypeString,                myOffset(effect_name),
+    "...");
+
+  // constraints
+  addField("constraint",              TypeString,   myOffset(cons_spec),
+    "...");
+  addField("posConstraint",           TypeString,   myOffset(pos_cons_spec),
+    "...");
+  addField("posConstraint2",          TypeString,   myOffset(aim_cons_spec),
+    "...");
+  addField("orientConstraint",        TypeString,   myOffset(orient_cons_spec),
+    "...");
+  addField("lifeConstraint",          TypeString,   myOffset(life_cons_spec),
+    "...");
+  //
+  addField("isConstraintSrc",         TypeBool,     myOffset(use_as_cons_obj),
+    "...");
+  addField("ghostIsConstraintSrc",    TypeBool,     myOffset(use_ghost_as_cons_obj),
+    "...");
+
+  addField("delay",             TypeF32,          myOffset(ewd_timing.delay),
+    "...");
+  addField("lifetime",          TypeF32,          myOffset(ewd_timing.lifetime),
+    "...");
+  addField("fadeInTime",        TypeF32,          myOffset(ewd_timing.fade_in_time),
+    "...");
+  addField("residueLifetime",   TypeF32,          myOffset(ewd_timing.residue_lifetime),
+    "...");
+  addField("fadeInEase",        TypePoint2F,      myOffset(ewd_timing.fadein_ease),
+    "...");
+  addField("fadeOutEase",       TypePoint2F,      myOffset(ewd_timing.fadeout_ease),
+    "...");
+  addField("lifetimeBias",      TypeF32,          myOffset(ewd_timing.life_bias),
+    "...");
+  addField("fadeOutTime",       TypeF32,          myOffset(user_fade_out_time),
+    "...");
+
+  addField("rateFactor",        TypeF32,          myOffset(rate_factor),
+    "...");
+  addField("scaleFactor",       TypeF32,          myOffset(scale_factor),
+    "...");
+
+  addField("isLooping",         TypeBool,         myOffset(is_looping),
+    "...");
+  addField("loopCount",         TypeS32,          myOffset(n_loops),
+    "...");
+  addField("loopGapTime",       TypeF32,          myOffset(loop_gap_time),
+    "...");
+
+  addField("ignoreTimeFactor",    TypeBool,       myOffset(ignore_time_factor),
+    "...");
+  addField("propagateTimeFactor", TypeBool,       myOffset(propagate_time_factor),
+    "...");
+
+  addField("effectEnabled",         TypeBool,         myOffset(effect_enabled),
+    "...");
+  addField("rankingRange",          TypeByteRange,    myOffset(ranking_range),
+    "...");
+  addField("levelOfDetailRange",    TypeByteRange,    myOffset(lod_range),
+    "...");
+  addField("lifeConditions",        TypeS32,      myOffset(life_conds),
+    "...");
+  addField("execConditions",        TypeS32,      myOffset(exec_cond_on_bits),  MAX_CONDITION_STATES,
+    "...");
+  addField("execOffConditions",     TypeS32,      myOffset(exec_cond_off_bits), MAX_CONDITION_STATES,
+    "...");
+
+  addField("xfmModifiers",    TYPEID<afxXM_BaseData>(),  myOffset(xfm_modifiers),  MAX_XFM_MODIFIERS,
+    "...");
+
+  addField("forcedBBox",        TypeBox3F,        myOffset(forced_bbox),
+    "...");
+  addField("updateForcedBBox",  TypeBool,         myOffset(update_forced_bbox),
+    "...");
+
+  addField("sortPriority",      TypeS8,           myOffset(sort_priority),
+    "...");
+  addField("direction",         TypePoint3F,      myOffset(direction),
+    "...");
+  addField("speed",             TypeF32,          myOffset(speed),
+    "...");
+  addField("mass",              TypeF32,          myOffset(mass),
+    "...");
+
+  addField("borrowAltitudes",   TypeBool,         myOffset(borrow_altitudes),
+    "...");
+  addField("visibilityKeys",    TypeString,       myOffset(vis_keys_spec),
+    "...");
+
+  addField("groupIndex",          TypeS32,        myOffset(group_index),
+    "...");
+  addField("inheritGroupTiming",  TypeS32,        myOffset(inherit_timing),
+    "...");
+
+  Parent::initPersistFields();
+
+  // disallow some field substitutions
+  disableFieldSubstitutions("effect");
+  onlyKeepClearSubstitutions("xfmModifiers"); // subs resolving to "~~", or "~0" are OK
+
+  // Conditional Execution Flags
+  Con::setIntVariable("$afx::DISABLED", DISABLED);
+  Con::setIntVariable("$afx::ENABLED", ENABLED);
+  Con::setIntVariable("$afx::FAILING", FAILING);
+  Con::setIntVariable("$afx::DEAD", DEAD);
+  Con::setIntVariable("$afx::ALIVE", ALIVE);
+  Con::setIntVariable("$afx::DYING", DYING);
+}
+
+bool afxEffectWrapperData::onAdd()
+{
+  if (Parent::onAdd() == false)
+    return false;
+
+  if (!effect_data)
+  {
+    if (!Sim::findObject((SimObjectId)data_ID, effect_data))
+    {
+      Con::errorf("afxEffectWrapperData::onAdd() -- bad datablockId: 0x%x", data_ID);
+      return false;
+    }
+  }
+
+  if (effect_data)
+  {
+    if (!afxEffectAdapterDesc::identifyEffect(this))
+    {
+      Con::errorf("afxEffectWrapperData::onAdd() -- unknown effect type.");
+      return false;
+    }
+  }
+
+  parse_cons_specs();
+  parse_vis_keys();
+
+  // figure out if fade-out is for effect of residue
+  if (ewd_timing.residue_lifetime > 0)
+  {
+    ewd_timing.residue_fadetime = user_fade_out_time;
+    ewd_timing.fade_out_time = 0.0f;
+  }
+  else
+  {
+    ewd_timing.residue_fadetime = 0.0f;
+    ewd_timing.fade_out_time = user_fade_out_time;
+  }
+
+  // adjust fade-in time
+  if (ewd_timing.lifetime >= 0)
+  {
+    ewd_timing.fade_in_time = getMin(ewd_timing.lifetime, ewd_timing.fade_in_time);
+  }
+
+  // adjust exec-conditions
+  for (S32 i = 0; i < MAX_CONDITION_STATES; i++)
+    exec_cond_bitmasks[i] = exec_cond_on_bits[i] | exec_cond_off_bits[i];
+
+  return true;
+}
+
+void afxEffectWrapperData::packData(BitStream* stream)
+{
+  Parent::packData(stream);
+
+  writeDatablockID(stream, effect_data, packed);
+
+  stream->writeString(effect_name);
+
+  stream->writeString(cons_spec);
+  stream->writeString(pos_cons_spec);
+  stream->writeString(orient_cons_spec);
+  stream->writeString(aim_cons_spec);
+  stream->writeString(life_cons_spec);
+  //
+  stream->write(use_as_cons_obj);
+  //stream->write(use_ghost_as_cons_obj);
+
+  stream->writeFlag(effect_enabled);
+  stream->write(ranking_range.low);
+  stream->write(ranking_range.high);
+  stream->write(lod_range.low);
+  stream->write(lod_range.high);
+
+  for (S32 i = 0; i < MAX_CONDITION_STATES; i++)
+  {
+    stream->write(exec_cond_on_bits[i]);
+    stream->write(exec_cond_off_bits[i]);
+  }
+  stream->write(life_conds);
+  stream->write(ewd_timing.delay);
+  stream->write(ewd_timing.lifetime);
+  stream->write(ewd_timing.fade_in_time);
+  stream->write(user_fade_out_time);
+  stream->write(is_looping);
+  stream->write(n_loops);
+  stream->write(loop_gap_time);
+  stream->write(ignore_time_factor);
+  stream->write(propagate_time_factor);
+  stream->write(ewd_timing.residue_lifetime);
+  stream->write(rate_factor);
+  stream->write(scale_factor);
+
+  // modifiers
+  pack_mods(stream, xfm_modifiers, packed);
+
+  mathWrite(*stream, forced_bbox);
+  stream->write(update_forced_bbox);
+
+  stream->write(sort_priority);
+  mathWrite(*stream, direction);
+  stream->write(speed);
+  stream->write(mass);
+
+  stream->write(borrow_altitudes);
+  if (stream->writeFlag(vis_keys_spec != ST_NULLSTRING))
+    stream->writeLongString(1023, vis_keys_spec);
+
+  if (stream->writeFlag(group_index != -1))
+    stream->write(group_index);
+
+  stream->writeInt(inherit_timing, TIMING_BITS);
+}
+
+void afxEffectWrapperData::unpackData(BitStream* stream)
+{
+  Parent::unpackData(stream);
+
+  data_ID = readDatablockID(stream);
+
+  effect_name = stream->readSTString();
+
+  cons_spec = stream->readSTString();
+  pos_cons_spec = stream->readSTString();
+  orient_cons_spec = stream->readSTString();
+  aim_cons_spec = stream->readSTString();
+  life_cons_spec = stream->readSTString();
+  //
+  stream->read(&use_as_cons_obj);
+  //stream->read(&use_ghost_as_cons_obj);
+
+  effect_enabled = stream->readFlag();
+  stream->read(&ranking_range.low);
+  stream->read(&ranking_range.high);
+  stream->read(&lod_range.low);
+  stream->read(&lod_range.high);
+
+  for (S32 i = 0; i < MAX_CONDITION_STATES; i++)
+  {
+    stream->read(&exec_cond_on_bits[i]);
+    stream->read(&exec_cond_off_bits[i]);
+  }
+  stream->read(&life_conds);
+  stream->read(&ewd_timing.delay);
+  stream->read(&ewd_timing.lifetime);
+  stream->read(&ewd_timing.fade_in_time);
+  stream->read(&user_fade_out_time);
+  stream->read(&is_looping);
+  stream->read(&n_loops);
+  stream->read(&loop_gap_time);
+  stream->read(&ignore_time_factor);
+  stream->read(&propagate_time_factor);
+  stream->read(&ewd_timing.residue_lifetime);
+  stream->read(&rate_factor);
+  stream->read(&scale_factor);
+
+  // modifiers
+  do_id_convert = true;
+  unpack_mods(stream, xfm_modifiers);
+
+  mathRead(*stream, &forced_bbox);
+  stream->read(&update_forced_bbox);
+
+  stream->read(&sort_priority);
+  mathRead(*stream, &direction);
+  stream->read(&speed);
+  stream->read(&mass);
+
+  stream->read(&borrow_altitudes);
+  if (stream->readFlag())
+  {
+    char buf[1024];
+    stream->readLongString(1023, buf);
+    vis_keys_spec = StringTable->insert(buf);
+  }
+  else
+    vis_keys_spec = ST_NULLSTRING;
+
+  if (stream->readFlag())
+    stream->read(&group_index);
+  else
+    group_index = -1;
+
+  inherit_timing = stream->readInt(TIMING_BITS);
+}
+
+/* static*/ 
+S32 num_modifiers(afxXM_BaseData* mods[])
+{
+  S32 n_mods = 0;
+  for (int i = 0; i < afxEffectDefs::MAX_XFM_MODIFIERS; i++)
+  {
+    if (mods[i])
+    {
+      if (i != n_mods)
+      {
+        mods[n_mods] = mods[i];
+        mods[i] = 0;
+      }
+      n_mods++;
+    }
+  }
+
+  return n_mods;
+}
+
+void afxEffectWrapperData::parse_cons_specs()
+{
+  // parse the constraint specifications
+  bool runs_on_s = runsOnServer();
+  bool runs_on_c = runsOnClient();
+  cons_def.parseSpec(cons_spec, runs_on_s, runs_on_c);
+  pos_cons_def.parseSpec(pos_cons_spec, runs_on_s, runs_on_c);
+  orient_cons_def.parseSpec(orient_cons_spec, runs_on_s, runs_on_c);
+  aim_cons_def.parseSpec(aim_cons_spec, runs_on_s, runs_on_c);
+  life_cons_def.parseSpec(life_cons_spec, runs_on_s, runs_on_c);
+  if (cons_def.isDefined())
+  {
+    pos_cons_def = cons_def;
+    if (!orient_cons_def.isDefined())
+      orient_cons_def = cons_def;
+  }
+}
+
+void afxEffectWrapperData::parse_vis_keys()
+{
+  if (vis_keys_spec != ST_NULLSTRING)
+  {
+    if (vis_keys)
+      delete vis_keys;
+    vis_keys = new afxAnimCurve();
+
+    char* keys_buffer = dStrdup(vis_keys_spec);
+
+    char* key_token = dStrtok(keys_buffer, " \t");
+    while (key_token != NULL)
+    {
+      char* colon = dStrchr(key_token, ':');
+      if (colon)
+      {
+        *colon = '\0';
+
+        F32 when = dAtof(key_token);
+        F32 what = dAtof(colon+1);
+
+        vis_keys->addKey(when, what);
+      }
+      key_token = dStrtok(NULL, " \t");
+    }
+
+    dFree(keys_buffer);
+    vis_keys->sort();
+  }
+}
+
+void afxEffectWrapperData::gather_cons_defs(Vector<afxConstraintDef>& defs)
+{
+  if (pos_cons_def.isDefined())
+    defs.push_back(pos_cons_def);
+  if (orient_cons_def.isDefined())
+    defs.push_back(orient_cons_def);
+  if (aim_cons_def.isDefined())
+    defs.push_back(aim_cons_def);
+  if (life_cons_def.isDefined())
+    defs.push_back(life_cons_def);
+
+  afxComponentEffectData* ce_data = dynamic_cast<afxComponentEffectData*>(effect_data);
+  if (ce_data)
+    ce_data->gather_cons_defs(defs);
+}
+
+void afxEffectWrapperData::pack_mods(BitStream* stream, afxXM_BaseData* mods[], bool packed)
+{
+  S32 n_mods = num_modifiers(mods);
+  stream->writeInt(n_mods, 6);
+  for (int i = 0; i < n_mods; i++)
+    writeDatablockID(stream, mods[i], packed);
+}
+
+void afxEffectWrapperData::unpack_mods(BitStream* stream, afxXM_BaseData* mods[])
+{
+  S32 n_mods = stream->readInt(6);
+  for (int i = 0; i < n_mods; i++)
+    mods[i] = (afxXM_BaseData*) readDatablockID(stream);
+}
+
+bool afxEffectWrapperData::preload(bool server, String &errorStr)
+{
+  if (!Parent::preload(server, errorStr))
+    return false;
+  
+  // Resolve objects transmitted from server
+  if (!server) 
+  {
+    if (do_id_convert)
+    {
+      for (int i = 0; i < MAX_XFM_MODIFIERS; i++)
+      {
+        SimObjectId db_id = SimObjectId((uintptr_t)xfm_modifiers[i]);
+        if (db_id != 0)
+        {
+          // try to convert id to pointer
+          if (!Sim::findObject(db_id, xfm_modifiers[i]))
+          {
+            Con::errorf("afxEffectWrapperData::preload() -- bad datablockId: 0x%x (xfm_modifiers[%d])",
+              db_id, i);
+          }
+        }
+        do_id_convert = false;
+      }
+    }
+  }
+  
+  return true;
+}
+
+void afxEffectWrapperData::onPerformSubstitutions()
+{
+  Parent::onPerformSubstitutions();
+
+  parse_cons_specs();
+  parse_vis_keys();
+
+  if (ewd_timing.residue_lifetime > 0)
+  {
+    ewd_timing.residue_fadetime = user_fade_out_time;
+    ewd_timing.fade_out_time = 0.0f;
+  }
+  else
+  {
+    ewd_timing.residue_fadetime = 0.0f;
+    ewd_timing.fade_out_time = user_fade_out_time;
+  }
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxEffectWrapper
+
+IMPLEMENT_CONOBJECT(afxEffectWrapper);
+
+ConsoleDocClass( afxEffectWrapper,
+   "@brief An Effect Wrapper as defined by an afxEffectWrapperData datablock.\n\n"
+
+   "Conceptually an effect wrapper encloses a building-block effect and acts "
+   "as a handle for adding the effect to a choreographer. Effect wrapper fields "
+   "primarily deal with effect timing, constraints, and conditional effect execution.\n\n"
+
+   "Not intended to be used directly, afxEffectWrapper is an internal baseclass used to "
+   "implement effect-specific adapter classes.\n\n"
+
+   "@ingroup afxEffects\n"
+   "@ingroup AFX\n"
+);
+
+afxEffectWrapper::afxEffectWrapper()
+{
+  choreographer = 0;
+  datablock = 0;
+  cons_mgr = 0;
+
+  cond_alive = true;
+  elapsed = 0;
+  life_end = 0;
+  life_elapsed = 0;
+  stopped = false;
+  n_updates = 0;
+  fade_value = 1.0f;
+  last_fade_value = 0.0f;
+  fade_in_end = 0.0;
+  fade_out_start = 0.0f;
+  in_scope = true;
+  is_aborted = false;
+  do_fade_inout = false;
+  do_fades = false;
+  full_lifetime = 0;
+
+  time_factor = 1.0f;
+  prop_time_factor = 1.0f;
+
+  live_scale_factor = 1.0f;
+  live_fade_factor = 1.0f;
+  terrain_altitude = -1.0f;
+  interior_altitude = -1.0f;
+
+  group_index = 0;
+
+  dMemset(xfm_modifiers, 0, sizeof(xfm_modifiers));
+}
+
+afxEffectWrapper::~afxEffectWrapper()
+{
+  for (S32 i = 0; i < MAX_XFM_MODIFIERS; i++)
+    if (xfm_modifiers[i])
+      delete xfm_modifiers[i];
+
+  if (datablock && datablock->effect_name != ST_NULLSTRING)
+  {
+    choreographer->removeNamedEffect(this);
+    if (datablock->use_as_cons_obj && !effect_cons_id.undefined())
+      cons_mgr->setReferenceEffect(effect_cons_id, 0);
+  }
+
+  if (datablock && datablock->isTempClone())
+    delete datablock;
+  datablock = 0;
+}
+
+#undef myOffset
+#define myOffset(field) Offset(field, afxEffectWrapper)
+
+void afxEffectWrapper::initPersistFields()
+{
+  addField("liveScaleFactor",     TypeF32,    myOffset(live_scale_factor),
+    "...");
+  addField("liveFadeFactor",      TypeF32,    myOffset(live_fade_factor),
+    "...");
+
+  Parent::initPersistFields();
+}
+
+void afxEffectWrapper::ew_init(afxChoreographer*     choreographer, 
+                               afxEffectWrapperData* datablock, 
+                               afxConstraintMgr*     cons_mgr,
+                               F32                   time_factor)
+{
+  AssertFatal(choreographer != NULL, "Choreographer is missing.");
+  AssertFatal(datablock != NULL, "Datablock is missing.");
+  AssertFatal(cons_mgr != NULL, "Constraint manager is missing.");
+
+  this->choreographer = choreographer;
+  this->datablock = datablock;
+  this->cons_mgr = cons_mgr;
+  ea_set_datablock(datablock->effect_data);
+
+  ew_timing = datablock->ewd_timing;
+  if (ew_timing.life_bias != 1.0f)
+  {
+    if (ew_timing.lifetime > 0)
+      ew_timing.lifetime *= ew_timing.life_bias;
+    ew_timing.fade_in_time *= ew_timing.life_bias;
+    ew_timing.fade_out_time *= ew_timing.life_bias;
+  }
+
+  pos_cons_id = cons_mgr->getConstraintId(datablock->pos_cons_def);
+  orient_cons_id = cons_mgr->getConstraintId(datablock->orient_cons_def);
+  aim_cons_id = cons_mgr->getConstraintId(datablock->aim_cons_def);
+  life_cons_id = cons_mgr->getConstraintId(datablock->life_cons_def);
+
+  this->time_factor = (datablock->ignore_time_factor) ? 1.0f : time_factor;
+
+  if (datablock->propagate_time_factor)
+    prop_time_factor = time_factor;
+
+  if (datablock->runsHere(choreographer->isServerObject()))
+  {
+    for (int i = 0; i < MAX_XFM_MODIFIERS && datablock->xfm_modifiers[i] != 0; i++)
+    {
+      xfm_modifiers[i] = datablock->xfm_modifiers[i]->create(this, choreographer->isServerObject());
+      AssertFatal(xfm_modifiers[i] != 0, avar("Error, creation failed for xfm_modifiers[%d] of %s.", i, datablock->getName()));
+      if (xfm_modifiers[i] == 0)
+        Con::errorf("Error, creation failed for xfm_modifiers[%d] of %s.", i, datablock->getName());
+    }
+  }
+
+  if (datablock->effect_name != ST_NULLSTRING)
+  {
+    assignName(datablock->effect_name);
+    choreographer->addNamedEffect(this);
+    if (datablock->use_as_cons_obj)
+    {
+      effect_cons_id = cons_mgr->setReferenceEffect(datablock->effect_name, this);
+      if (effect_cons_id.undefined() && datablock->isTempClone() && datablock->runsHere(choreographer->isServerObject()))
+        effect_cons_id = cons_mgr->createReferenceEffect(datablock->effect_name, this);
+    }
+  }
+}
+
+void afxEffectWrapper::prestart() 
+{
+  // modify timing values by time_factor
+  if (ew_timing.lifetime > 0)
+    ew_timing.lifetime *= time_factor;
+  ew_timing.delay *= time_factor;
+  ew_timing.fade_in_time *= time_factor;
+  ew_timing.fade_out_time *= time_factor;
+
+  if (ew_timing.lifetime < 0)
+  {
+    full_lifetime = INFINITE_LIFETIME;
+    life_end = INFINITE_LIFETIME;
+  }
+  else
+  {
+    full_lifetime = ew_timing.lifetime + ew_timing.fade_out_time;
+    life_end = ew_timing.delay + ew_timing.lifetime;
+  }
+
+  if ((ew_timing.fade_in_time + ew_timing.fade_out_time) > 0.0f)
+  {
+    fade_in_end = ew_timing.delay + ew_timing.fade_in_time;
+    if (full_lifetime == INFINITE_LIFETIME)
+      fade_out_start = INFINITE_LIFETIME;
+    else
+      fade_out_start = ew_timing.delay + ew_timing.lifetime;
+    do_fade_inout = true;
+  }
+
+  if (!do_fade_inout && datablock->vis_keys != NULL && datablock->vis_keys->numKeys() > 0)
+  {
+    //do_fades = true;
+    fade_out_start = ew_timing.delay + ew_timing.lifetime;
+  }
+}
+
+bool afxEffectWrapper::start(F32 timestamp) 
+{ 
+  if (!ea_is_enabled())
+  {
+    Con::warnf("afxEffectWrapper::start() -- effect type of %s is currently disabled.", datablock->getName());
+    return false;
+  }
+
+  afxConstraint* life_constraint = getLifeConstraint();
+  if (life_constraint)
+    cond_alive = life_constraint->getLivingState();
+
+  elapsed = timestamp; 
+
+  for (S32 i = 0; i < MAX_XFM_MODIFIERS; i++)
+  {
+    if (!xfm_modifiers[i])
+      break;
+    else
+      xfm_modifiers[i]->start(timestamp);
+  }
+
+  if (!ea_start())
+  {
+    // subclass should print error message if applicable, so no message here.
+    return false;
+  }
+
+  update(0.0f);
+  return !isAborted();
+}
+
+bool afxEffectWrapper::test_life_conds()
+{
+  afxConstraint* life_constraint = getLifeConstraint();
+  if (!life_constraint || datablock->life_conds == 0)
+    return true;
+
+  S32 now_state = life_constraint->getDamageState();
+  if ((datablock->life_conds & DEAD) != 0 && now_state == ShapeBase::Disabled)
+    return true;
+  if ((datablock->life_conds & ALIVE) != 0 && now_state == ShapeBase::Enabled)
+    return true;
+  if ((datablock->life_conds & DYING) != 0)
+    return (cond_alive && now_state == ShapeBase::Disabled);
+
+  return false;
+}
+
+bool afxEffectWrapper::update(F32 dt) 
+{ 
+  elapsed += dt; 
+
+  // life_elapsed won't exceed full_lifetime
+  life_elapsed = getMin(elapsed - ew_timing.delay, full_lifetime);
+
+  // update() returns early if elapsed is outside of active timing range 
+  //     (delay <= elapsed <= delay+lifetime)
+  // note: execution is always allowed beyond this point at least once, 
+  //       even if elapsed exceeds the lifetime.
+  if (elapsed < ew_timing.delay)
+  {
+    setScopeStatus(false);
+    return false;
+  }
+  
+  if (!datablock->requiresStop(ew_timing) && ew_timing.lifetime < 0)
+  {
+    F32 afterlife = elapsed - ew_timing.delay;
+    if (afterlife > 1.0f || ((afterlife > 0.0f) && (n_updates > 0)))
+    {
+      setScopeStatus(ew_timing.residue_lifetime > 0.0f);
+      return false;
+    }
+  }
+  else
+  {
+    F32 afterlife = elapsed - (full_lifetime + ew_timing.delay);
+    if (afterlife > 1.0f || ((afterlife > 0.0f) && (n_updates > 0)))
+    {
+      setScopeStatus(ew_timing.residue_lifetime > 0.0f);
+      return false;
+    }
+  }
+
+  // first time here, test if required conditions for effect are met
+  if (n_updates == 0)
+  {
+    if (!test_life_conds())
+    {
+      elapsed = full_lifetime + ew_timing.delay;
+      setScopeStatus(false);
+      n_updates++;
+      return false;
+    }
+  }
+
+  setScopeStatus(true);
+  n_updates++;
+
+
+  // calculate current fade value if enabled
+  if (do_fade_inout)
+  {
+    if (ew_timing.fade_in_time > 0 && elapsed <= fade_in_end)
+    {
+      F32 t = mClampF((elapsed-ew_timing.delay)/ew_timing.fade_in_time, 0.0f, 1.0f);
+      fade_value = afxEase::t(t, ew_timing.fadein_ease.x,ew_timing.fadein_ease.y);
+      do_fades = true;
+    }
+    else if (elapsed > fade_out_start)
+    {
+      if (ew_timing.fade_out_time == 0)
+        fade_value = 0.0f;
+      else
+      {
+        F32 t = mClampF(1.0f-(elapsed-fade_out_start)/ew_timing.fade_out_time, 0.0f, 1.0f);
+        fade_value = afxEase::t(t, ew_timing.fadeout_ease.x,ew_timing.fadeout_ease.y);
+      }
+      do_fades = true;
+    }
+    else
+    {
+      fade_value = 1.0f;
+      do_fades = false;
+    }
+  }
+  else
+  {
+    fade_value = 1.0;
+    do_fades = false;
+  }
+
+  if (datablock->vis_keys && datablock->vis_keys->numKeys() > 0)
+  {
+    F32 vis = datablock->vis_keys->evaluate(elapsed-ew_timing.delay);
+    fade_value *= mClampF(vis, 0.0f, 1.0f);
+    do_fades = (fade_value < 1.0f);
+  }
+
+  // DEAL WITH CONSTRAINTS
+  afxXM_Params params;
+  Point3F& CONS_POS = params.pos;
+  MatrixF& CONS_XFM = params.ori;
+  Point3F& CONS_AIM = params.pos2;
+  Point3F& CONS_SCALE = params.scale;
+  LinearColorF& CONS_COLOR = params.color;
+
+  afxConstraint* pos_constraint = getPosConstraint();
+  if (pos_constraint)
+  {
+    bool valid = pos_constraint->getPosition(CONS_POS, datablock->pos_cons_def.history_time);
+    if (!valid)
+      getUnconstrainedPosition(CONS_POS);
+    setScopeStatus(valid);
+    if (valid && datablock->borrow_altitudes)
+    {
+      F32 terr_alt, inter_alt;
+      if (pos_constraint->getAltitudes(terr_alt, inter_alt))
+      {
+        terrain_altitude = terr_alt;
+        interior_altitude = inter_alt;
+      }
+    }
+  }
+  else
+  {
+    getUnconstrainedPosition(CONS_POS);
+    setScopeStatus(false);
+  }
+
+  afxConstraint* orient_constraint = getOrientConstraint();
+  if (orient_constraint) 
+  {
+    orient_constraint->getTransform(CONS_XFM, datablock->pos_cons_def.history_time);
+  }
+  else
+  {
+    getUnconstrainedTransform(CONS_XFM);
+  }
+
+  afxConstraint* aim_constraint = getAimConstraint();
+  if (aim_constraint)
+    aim_constraint->getPosition(CONS_AIM, datablock->pos_cons_def.history_time);
+  else
+    CONS_AIM.zero();
+
+  CONS_SCALE.set(datablock->scale_factor, datablock->scale_factor, datablock->scale_factor);
+
+  /*
+  if (datablock->isPositional() && CONS_POS.isZero() && in_scope)
+    Con::errorf("#EFFECT AT ORIGIN [%s] time=%g", datablock->getName(), dt);
+  */
+
+  getBaseColor(CONS_COLOR);
+
+  params.vis = fade_value;
+
+  // apply modifiers
+  for (int i = 0; i < MAX_XFM_MODIFIERS; i++)
+  {
+    if (!xfm_modifiers[i])
+      break;
+    else
+      xfm_modifiers[i]->updateParams(dt, life_elapsed, params);
+  }
+
+  // final pos/orient is determined
+  updated_xfm = CONS_XFM;  
+  updated_pos = CONS_POS;
+  updated_aim = CONS_AIM;
+  updated_xfm.setPosition(updated_pos);
+  updated_scale = CONS_SCALE;
+  updated_color = CONS_COLOR;
+
+  if (params.vis > 1.0f)
+    fade_value = 1.0f;
+  else
+    fade_value = params.vis;
+
+  if (last_fade_value != fade_value)
+  {
+    do_fades = true;
+    last_fade_value = fade_value;
+  }
+  else
+  {
+    do_fades = (fade_value < 1.0f);
+  }
+
+  if (!ea_update(dt))
+  {
+    is_aborted = true;
+    Con::errorf("afxEffectWrapper::update() -- effect %s ended unexpectedly.", datablock->getName());
+  }
+
+  return true;
+}
+
+void afxEffectWrapper::stop() 
+{ 
+  if (!datablock->requiresStop(ew_timing))
+    return;
+
+  stopped = true; 
+
+  // this resets full_lifetime so it starts to shrink or fade
+  if (full_lifetime == INFINITE_LIFETIME)
+  {
+    full_lifetime = (elapsed - ew_timing.delay) + afterStopTime();
+    life_end = elapsed; 
+    if (ew_timing.fade_out_time > 0)
+      fade_out_start = elapsed;
+  }
+}
+
+void afxEffectWrapper::cleanup(bool was_stopped)
+{ 
+  ea_finish(was_stopped);
+  if (!effect_cons_id.undefined())
+  {
+    cons_mgr->setReferenceEffect(effect_cons_id, 0);
+    effect_cons_id = afxConstraintID();
+  }
+}
+
+void afxEffectWrapper::setScopeStatus(bool in_scope)
+{ 
+  if (this->in_scope != in_scope)
+  {
+    this->in_scope = in_scope;
+    ea_set_scope_status(in_scope);
+  }
+}
+
+bool afxEffectWrapper::isDone() 
+{ 
+  if (!datablock->is_looping)
+    return (elapsed >= (life_end + ew_timing.fade_out_time));
+
+  return false;
+}
+
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+// static 
+afxEffectWrapper* afxEffectWrapper::ew_create(afxChoreographer*      choreographer, 
+                                              afxEffectWrapperData*  datablock, 
+                                              afxConstraintMgr*      cons_mgr,
+                                              F32                    time_factor,
+                                              S32                    group_index)
+{
+  afxEffectWrapper* adapter = datablock->effect_desc->create();
+
+  if (adapter)
+  {
+    adapter->group_index = (datablock->group_index != -1) ? datablock->group_index : group_index;
+    adapter->ew_init(choreographer, datablock, cons_mgr, time_factor); 
+  }
+
+  return adapter;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+Vector<afxEffectAdapterDesc*>* afxEffectAdapterDesc::adapters = 0;
+
+afxEffectAdapterDesc::afxEffectAdapterDesc() 
+{ 
+  if (!adapters)
+    adapters = new Vector<afxEffectAdapterDesc*>;
+
+  adapters->push_back(this);
+}
+
+bool afxEffectAdapterDesc::identifyEffect(afxEffectWrapperData* ew)
+{
+  if (!ew || !ew->effect_data)
+  {
+    Con::errorf("afxEffectAdapterDesc::identifyEffect() -- effect datablock was not specified.");
+    return false;
+  }
+
+  if (!adapters)
+  {
+    Con::errorf("afxEffectAdapterDesc::identifyEffect() -- adapter registration list has not been allocated.");
+    return false;
+  }
+
+  if (adapters->size() == 0)
+  {
+    Con::errorf("afxEffectAdapterDesc::identifyEffect() -- no effect adapters have been registered.");
+    return false;
+  }
+
+  for (S32 i = 0; i < adapters->size(); i++)
+  {
+    if ((*adapters)[i]->testEffectType(ew->effect_data))
+    {
+      ew->effect_desc = (*adapters)[i];
+      (*adapters)[i]->prepEffect(ew);
+      return true;
+    }
+  }
+
+  Con::errorf("afxEffectAdapterDesc::identifyEffect() -- effect %s has an undefined type. -- %d", 
+    ew->effect_data->getName(), adapters->size());
+  return false;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//

+ 392 - 0
Engine/source/afx/afxEffectWrapper.h

@@ -0,0 +1,392 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
+// Copyright (C) 2015 Faust Logic, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _AFX_EFFECT_WRAPPER_H_
+#define _AFX_EFFECT_WRAPPER_H_
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#include "afx/arcaneFX.h"
+#include "afxEffectDefs.h"
+#include "afxConstraint.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+struct afxEffectTimingData
+{
+  F32     delay;
+  F32     lifetime;
+  F32     fade_in_time;
+  F32     fade_out_time;
+  F32     residue_lifetime;   
+  F32     residue_fadetime;
+  F32     life_bias;
+  Point2F fadein_ease;
+  Point2F fadeout_ease;
+
+  afxEffectTimingData()
+  {
+    delay = 0.0f;
+    lifetime = 0.0f;
+    fade_in_time = 0.0f;
+    fade_out_time = 0.0f;
+    residue_lifetime = 0.0f;
+    residue_fadetime = 0.0f;
+    life_bias = 1.0f;
+    fadein_ease.set(0.0f, 1.0f);
+    fadeout_ease.set(0.0f, 1.0f);
+  }
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+class afxEffectWrapperData;
+class afxAnimCurve;
+
+class afxEffectAdapterDesc
+{
+private:
+  static Vector<afxEffectAdapterDesc*>* adapters;
+
+public:
+  /*C*/         afxEffectAdapterDesc();
+
+  virtual bool  testEffectType(const SimDataBlock*) const=0;
+  virtual bool  requiresStop(const afxEffectWrapperData*, const afxEffectTimingData&) const=0;
+  virtual bool  runsOnServer(const afxEffectWrapperData*) const=0;
+  virtual bool  runsOnClient(const afxEffectWrapperData*) const=0;
+  virtual bool  isPositional(const afxEffectWrapperData*) const { return true; }
+  virtual void  prepEffect(afxEffectWrapperData*) const { }
+
+  virtual afxEffectWrapper* create() const=0;
+
+  static bool   identifyEffect(afxEffectWrapperData*);
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+class afxXM_BaseData;
+
+class afxEffectBaseData : public GameBaseData, public afxEffectDefs
+{
+  typedef GameBaseData  Parent;
+
+public:
+  /*C*/           afxEffectBaseData() { }
+  /*C*/           afxEffectBaseData(const afxEffectBaseData& other, bool temp=false)  : GameBaseData(other, temp){ }
+
+  virtual void    gather_cons_defs(Vector<afxConstraintDef>& defs) { };
+
+  DECLARE_CONOBJECT(afxEffectBaseData);
+  DECLARE_CATEGORY("AFX");
+};
+
+//class afxEffectWrapperData : public GameBaseData, public afxEffectDefs
+class afxEffectWrapperData : public afxEffectBaseData
+{
+  //typedef GameBaseData  Parent;
+  typedef afxEffectBaseData  Parent;
+
+  bool                  do_id_convert;
+
+public:
+  enum  { MAX_CONDITION_STATES = 4 };
+
+public:
+  StringTableEntry      effect_name; 
+  bool                  use_as_cons_obj;    
+  bool                  use_ghost_as_cons_obj;    
+
+  StringTableEntry      cons_spec; 
+  StringTableEntry      pos_cons_spec;
+  StringTableEntry      orient_cons_spec;
+  StringTableEntry      aim_cons_spec;
+  StringTableEntry      life_cons_spec;
+  //
+  afxConstraintDef      cons_def;
+  afxConstraintDef      pos_cons_def;
+  afxConstraintDef      orient_cons_def;
+  afxConstraintDef      aim_cons_def;
+  afxConstraintDef      life_cons_def;
+
+  afxEffectTimingData   ewd_timing;
+  U32                   inherit_timing;
+  
+  F32                   scale_factor;       // scale size if applicable
+  F32                   rate_factor;        // scale rate if applicable
+  F32                   user_fade_out_time;
+
+  bool                  is_looping;
+  U32                   n_loops;
+  F32                   loop_gap_time;
+
+  bool                  ignore_time_factor;
+  bool                  propagate_time_factor;
+
+  ByteRange             ranking_range;
+  ByteRange             lod_range;
+  S32                   life_conds;
+  bool                  effect_enabled;
+  U32                   exec_cond_on_bits[MAX_CONDITION_STATES];
+  U32                   exec_cond_off_bits[MAX_CONDITION_STATES];
+  U32                   exec_cond_bitmasks[MAX_CONDITION_STATES];
+
+  S32                   data_ID;
+
+  afxXM_BaseData*       xfm_modifiers[MAX_XFM_MODIFIERS];
+
+  Box3F                 forced_bbox;
+  bool                  update_forced_bbox;
+
+  S8                    sort_priority;
+  Point3F               direction;
+  F32                   speed;
+  F32                   mass;
+
+  bool                  borrow_altitudes;
+  StringTableEntry      vis_keys_spec;
+  afxAnimCurve*         vis_keys;
+
+  SimDataBlock*         effect_data;
+  afxEffectAdapterDesc* effect_desc;
+
+  S32                   group_index;
+
+  void                  parse_cons_specs();
+  void                  parse_vis_keys();
+  void                  gather_cons_defs(Vector<afxConstraintDef>& defs);
+  void                  pack_mods(BitStream*, afxXM_BaseData* mods[], bool packed);
+  void                  unpack_mods(BitStream*, afxXM_BaseData* mods[]);
+
+public:
+  /*C*/             afxEffectWrapperData();
+  /*C*/             afxEffectWrapperData(const afxEffectWrapperData&, bool = false);
+  /*D*/             ~afxEffectWrapperData();
+
+  virtual bool      onAdd();
+  virtual void      packData(BitStream*);
+  virtual void      unpackData(BitStream*);
+
+  bool              preload(bool server, String &errorStr);
+
+  virtual void      onPerformSubstitutions();
+
+  bool              requiresStop(const afxEffectTimingData& timing) { return effect_desc->requiresStop(this, timing); }
+  bool              runsOnServer() { return effect_desc->runsOnServer(this); }
+  bool              runsOnClient() { return effect_desc->runsOnClient(this); }
+  bool              runsHere(bool server_here) { return (server_here) ? runsOnServer() : runsOnClient(); }
+  bool              isPositional() { return effect_desc->isPositional(this); }
+  bool              testExecConditions(U32 conditions);
+
+  F32               afterStopTime() { return ewd_timing.fade_out_time; }
+
+  virtual bool      allowSubstitutions() const { return true; }
+
+  static void       initPersistFields();
+
+  DECLARE_CONOBJECT(afxEffectWrapperData);
+  DECLARE_CATEGORY("AFX");
+};
+
+inline bool afxEffectWrapperData::testExecConditions(U32 conditions)
+{
+  if (exec_cond_bitmasks[0] == 0)
+    return true;
+
+  if ((exec_cond_bitmasks[0] & conditions) == exec_cond_on_bits[0])
+    return true;
+
+  for (S32 i = 1; i < MAX_CONDITION_STATES; i++)
+  {
+    if (exec_cond_bitmasks[i] == 0)
+      return false;
+    if ((exec_cond_bitmasks[i] & conditions) == exec_cond_on_bits[i])
+      return true;
+  }
+  return false;
+}
+
+typedef Vector<afxEffectBaseData*> afxEffectList;
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxEffectWrapper 
+//
+//  NOTE -- this not a subclass of GameBase... it is only meant to exist on
+//    the client-side.
+
+class ShapeBase;
+class GameBase;
+class TSShape;
+class TSShapeInstance;
+class SceneObject;
+class afxConstraint;
+class afxConstraintMgr;
+class afxChoreographer;
+class afxXM_Base;
+
+class afxEffectWrapper : public SimObject,  public afxEffectDefs
+{
+  typedef SimObject Parent;
+  friend class afxEffectVector;
+
+private:
+  bool              test_life_conds();
+
+protected:
+  afxEffectWrapperData* datablock;
+
+  afxEffectTimingData   ew_timing;
+
+  F32               fade_in_end;
+  F32               fade_out_start;
+  F32               full_lifetime;
+
+  F32               time_factor;
+  F32               prop_time_factor;
+
+  afxChoreographer* choreographer;
+  afxConstraintMgr* cons_mgr;
+
+  afxConstraintID   pos_cons_id;
+  afxConstraintID   orient_cons_id;
+  afxConstraintID   aim_cons_id;
+  afxConstraintID   life_cons_id;
+
+  afxConstraintID   effect_cons_id;
+
+  F32               elapsed;
+  F32               life_elapsed;
+  F32               life_end;
+  bool              stopped;
+  bool              cond_alive;
+
+  U32               n_updates;
+
+  MatrixF           updated_xfm;
+  Point3F           updated_pos;
+  Point3F           updated_aim;
+  Point3F           updated_scale;
+  LinearColorF            updated_color;
+
+  F32               fade_value;
+  F32               last_fade_value;
+
+  bool              do_fade_inout;
+  bool              do_fades;
+  bool              in_scope;
+  bool              is_aborted;
+
+  U8                effect_flags;
+
+  afxXM_Base*       xfm_modifiers[MAX_XFM_MODIFIERS];
+
+  F32               live_scale_factor;
+  F32               live_fade_factor;
+  F32               terrain_altitude;
+  F32               interior_altitude;
+
+  S32               group_index;
+
+public:
+  /*C*/             afxEffectWrapper();
+  virtual           ~afxEffectWrapper();
+
+  void              ew_init(afxChoreographer*, afxEffectWrapperData*, afxConstraintMgr*, 
+                            F32 time_factor);
+
+  F32               getFullLifetime() { return ew_timing.lifetime + ew_timing.fade_out_time; }
+  F32               getTimeFactor() { return time_factor; }
+  afxConstraint*    getPosConstraint() { return cons_mgr->getConstraint(pos_cons_id); }
+  afxConstraint*    getOrientConstraint() { return cons_mgr->getConstraint(orient_cons_id); }
+  afxConstraint*    getAimConstraint() { return cons_mgr->getConstraint(aim_cons_id); }
+  afxConstraint*    getLifeConstraint() { return cons_mgr->getConstraint(life_cons_id); }
+  afxChoreographer* getChoreographer() { return choreographer; }
+
+  virtual bool      isDone();
+  virtual bool      deleteWhenStopped() { return false; }
+  F32               afterStopTime() { return ew_timing.fade_out_time; } 
+  bool              isAborted() const { return is_aborted; }
+
+  void              prestart();
+  bool              start(F32 timestamp);
+  bool              update(F32 dt);
+  void              stop();
+  void              cleanup(bool was_stopped=false);
+  void              setScopeStatus(bool flag);
+
+  virtual void      ea_set_datablock(SimDataBlock*) { }
+  virtual bool      ea_start() { return true; }
+  virtual bool      ea_update(F32 dt) { return true; }
+  virtual void      ea_finish(bool was_stopped) { }
+  virtual void      ea_set_scope_status(bool flag) { }
+  virtual bool      ea_is_enabled() { return true; }
+  virtual SceneObject* ea_get_scene_object() const { return 0; }
+  U32               ea_get_triggers() const { return 0; }
+
+  void              getUpdatedPosition(Point3F& pos) { pos = updated_pos;}
+  void              getUpdatedTransform(MatrixF& xfm) { xfm = updated_xfm; }
+  void              getUpdatedScale(Point3F& scale) { scale = updated_scale; }
+  void              getUpdatedColor(LinearColorF& color) { color = updated_color; }
+  virtual void      getUpdatedBoxCenter(Point3F& pos) { pos = updated_pos;}
+
+  virtual void      getUnconstrainedPosition(Point3F& pos) { pos.zero();}
+  virtual void      getUnconstrainedTransform(MatrixF& xfm) { xfm.identity(); }
+  virtual void      getBaseColor(LinearColorF& color) { color.set(1.0f, 1.0f, 1.0f, 1.0f); }
+
+  SceneObject*      getSceneObject() const { return ea_get_scene_object(); }
+  U32               getTriggers() const { return ea_get_triggers(); }
+
+  F32               getMass() { return datablock->mass; }
+  Point3F           getDirection() { return datablock->direction; }
+  F32               getSpeed() { return datablock->speed; }
+
+  virtual TSShape*          getTSShape() { return 0; }
+  virtual TSShapeInstance*  getTSShapeInstance() { return 0; }
+
+  virtual U32       setAnimClip(const char* clip, F32 pos, F32 rate, F32 trans) { return 0; }
+  virtual void      resetAnimation(U32 tag) { }
+  virtual F32       getAnimClipDuration(const char* clip) { return 0.0f; }
+
+  void              setTerrainAltitude(F32 alt) { terrain_altitude = alt; }
+  void              setInteriorAltitude(F32 alt) { interior_altitude = alt; }
+  void              getAltitudes(F32& terr_alt, F32& inter_alt) const { terr_alt = terrain_altitude; inter_alt = interior_altitude; }
+
+  void              setGroupIndex(S32 idx) { group_index = idx; }
+  S32               getGroupIndex() const { return group_index; }
+
+  bool              inScope() const { return in_scope; }
+
+public:
+  static void       initPersistFields();
+
+  static afxEffectWrapper* ew_create(afxChoreographer*, afxEffectWrapperData*, afxConstraintMgr*, F32 time_factor, S32 group_index=0);
+
+  DECLARE_CONOBJECT(afxEffectWrapper);
+  DECLARE_CATEGORY("AFX");
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#endif // _AFX_EFFECT_WRAPPER_H_

+ 1119 - 0
Engine/source/afx/afxEffectron.cpp

@@ -0,0 +1,1119 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
+// Copyright (C) 2015 Faust Logic, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#include "afx/arcaneFX.h"
+
+#include "console/engineAPI.h"
+#include "T3D/gameBase/gameConnection.h"
+#include "sfx/sfxSystem.h"
+#include "math/mathIO.h"
+
+#include "afx/afxChoreographer.h"
+#include "afx/afxEffectron.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxEffectronData::ewValidator
+//
+// When an effect is added using "addEffect", this validator intercepts the value
+// and adds it to the dynamic effects list. 
+//
+void afxEffectronData::ewValidator::validateType(SimObject* object, void* typePtr)
+{
+  afxEffectronData* eff_data = dynamic_cast<afxEffectronData*>(object);
+  afxEffectBaseData** ew = (afxEffectBaseData**)(typePtr);
+
+  if (eff_data && ew)
+  {
+    eff_data->fx_list.push_back(*ew);
+    *ew = 0;
+  }
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+class EffectronFinishStartupEvent : public SimEvent
+{
+public:
+  void process(SimObject* obj) 
+  { 
+     afxEffectron* eff = dynamic_cast<afxEffectron*>(obj);
+     if (eff)
+       eff->finish_startup();
+  }
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxEffectronData
+
+IMPLEMENT_CO_DATABLOCK_V1(afxEffectronData);
+
+ConsoleDocClass( afxEffectronData,
+   "@brief Defines the properties of an afxEffectron.\n\n"
+
+   "@ingroup afxChoreographers\n"
+   "@ingroup AFX\n"
+   "@ingroup Datablocks\n"
+);
+
+afxEffectronData::afxEffectronData()
+{
+  duration = 0.0f;
+  n_loops = 1;
+
+  // dummy entry holds effect-wrapper pointer while a special validator
+  // grabs it and adds it to an appropriate effects list
+  dummy_fx_entry = NULL;
+
+  // marked true if datablock ids need to
+  // be converted into pointers
+  do_id_convert = false;
+}
+
+afxEffectronData::afxEffectronData(const afxEffectronData& other, bool temp_clone) : afxChoreographerData(other, temp_clone)
+{
+  duration = other.duration;
+  n_loops = other.n_loops;
+  dummy_fx_entry = other.dummy_fx_entry;
+  do_id_convert = other.do_id_convert;
+  fx_list = other.fx_list;
+}
+
+void afxEffectronData::reloadReset()
+{
+  fx_list.clear();
+}
+
+#define myOffset(field) Offset(field, afxEffectronData)
+
+void afxEffectronData::initPersistFields()
+{
+  addField("duration",    TypeF32,      myOffset(duration),
+    "...");
+  addField("numLoops",    TypeS32,      myOffset(n_loops),
+    "...");
+  // effect lists
+  // for each of these, dummy_fx_entry is set and then a validator adds it to the appropriate effects list 
+  static ewValidator emptyValidator(0);
+  
+  addFieldV("addEffect",  TYPEID<afxEffectBaseData>(),  myOffset(dummy_fx_entry), &emptyValidator,
+    "...");
+
+  Parent::initPersistFields();
+
+  // disallow some field substitutions
+  disableFieldSubstitutions("addEffect");
+}
+
+bool afxEffectronData::onAdd()
+{
+  if (Parent::onAdd() == false)
+    return false;
+
+  return true;
+}
+
+void afxEffectronData::pack_fx(BitStream* stream, const afxEffectList& fx, bool packed)
+{
+  stream->writeInt(fx.size(), EFFECTS_PER_PHRASE_BITS);
+  for (int i = 0; i < fx.size(); i++)
+    writeDatablockID(stream, fx[i], packed);
+}
+
+void afxEffectronData::unpack_fx(BitStream* stream, afxEffectList& fx)
+{
+  fx.clear();
+  S32 n_fx = stream->readInt(EFFECTS_PER_PHRASE_BITS);
+  for (int i = 0; i < n_fx; i++)
+    fx.push_back((afxEffectWrapperData*)readDatablockID(stream));
+}
+
+void afxEffectronData::packData(BitStream* stream)
+{
+  Parent::packData(stream);
+
+  stream->write(duration);
+  stream->write(n_loops);
+
+  pack_fx(stream, fx_list, packed);
+}
+
+void afxEffectronData::unpackData(BitStream* stream)
+{
+  Parent::unpackData(stream);
+
+  stream->read(&duration);
+  stream->read(&n_loops);
+
+  do_id_convert = true;
+  unpack_fx(stream, fx_list);
+}
+
+bool afxEffectronData::preload(bool server, String &errorStr)
+{
+  if (!Parent::preload(server, errorStr))
+    return false;
+
+  // Resolve objects transmitted from server
+  if (!server) 
+  {
+    if (do_id_convert)
+    {
+      for (S32 i = 0; i < fx_list.size(); i++)
+      {
+        SimObjectId db_id = SimObjectId((uintptr_t)fx_list[i]);
+        if (db_id != 0)
+        {
+          // try to convert id to pointer
+          if (!Sim::findObject(db_id, fx_list[i]))
+          {
+            Con::errorf(ConsoleLogEntry::General, 
+              "afxEffectronData::preload() -- bad datablockId: 0x%x", 
+              db_id);
+          }
+        }
+      }
+      do_id_convert = false;
+    }
+  }
+
+  return true;
+}
+
+void afxEffectronData::gatherConstraintDefs(Vector<afxConstraintDef>& defs)
+{
+  afxConstraintDef::gather_cons_defs(defs, fx_list);
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//
+
+DefineEngineMethod(afxEffectronData, reset, void, (),,
+                   "Resets an effectron datablock during reload.\n\n"
+                   "@ingroup AFX")
+{
+  object->reloadReset();
+}
+
+DefineEngineMethod(afxEffectronData, addEffect, void, (afxEffectBaseData* effect),,
+                   "Adds an effect (wrapper or group) to an effectron's phase.\n\n"
+                   "@ingroup AFX")
+{
+  if (!effect) 
+  {
+    Con::errorf("afxEffectronData::addEffect() -- missing afxEffectWrapperData.");
+    return;
+  }
+  
+  object->fx_list.push_back(effect);
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxEffectron
+
+IMPLEMENT_CO_NETOBJECT_V1(afxEffectron);
+
+ConsoleDocClass( afxEffectron,
+   "@brief A basic effects choreographer.\n\n"
+
+   "@ingroup afxChoreographers\n"
+   "@ingroup AFX\n"
+);
+
+StringTableEntry  afxEffectron::CAMERA_CONS;
+StringTableEntry  afxEffectron::LISTENER_CONS;
+
+void afxEffectron::init()
+{
+  // setup static predefined constraint names
+  if (CAMERA_CONS == 0)
+  {
+    CAMERA_CONS = StringTable->insert("camera");
+    LISTENER_CONS = StringTable->insert("listener");
+  }
+
+  // afxEffectron is always in scope, however the effects used 
+  // do their own scoping in that they will shut off if their 
+  // position constraint leaves scope.
+  //
+  //   note -- ghosting is delayed until constraint 
+  //           initialization is done.
+  //
+  //mNetFlags.set(Ghostable | ScopeAlways);
+  mNetFlags.clear(Ghostable | ScopeAlways);
+
+  datablock = NULL;
+  exeblock = NULL;
+
+  constraints_initialized = false;
+  scoping_initialized = false;
+
+  effect_state = (U8) INACTIVE_STATE;
+  effect_elapsed = 0;
+
+  // define named constraints
+  constraint_mgr->defineConstraint(CAMERA_CONSTRAINT, CAMERA_CONS);
+  constraint_mgr->defineConstraint(POINT_CONSTRAINT,  LISTENER_CONS);
+
+  active_phrase = NULL;
+  time_factor = 1.0f;
+  camera_cons_obj = 0;
+
+  marks_mask = 0;
+}
+
+afxEffectron::afxEffectron()
+{
+  started_with_newop = true;
+  init();
+}
+
+afxEffectron::afxEffectron(bool not_default)
+{
+  started_with_newop = false;
+  init();
+}
+
+afxEffectron::~afxEffectron()
+{
+  delete active_phrase;
+
+  if (datablock && datablock->isTempClone())
+  {
+    delete datablock;
+    datablock = 0;
+  }
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+// STANDARD OVERLOADED METHODS //
+
+bool afxEffectron::onNewDataBlock(GameBaseData* dptr, bool reload)
+{
+  datablock = dynamic_cast<afxEffectronData*>(dptr);
+  if (!datablock || !Parent::onNewDataBlock(dptr, reload))
+    return false;
+
+  if (isServerObject() && started_with_newop)
+  {
+    // copy dynamic fields from the datablock but
+    // don't replace fields with a value
+    assignDynamicFieldsFrom(dptr, arcaneFX::sParameterFieldPrefix, true);
+  }
+
+  exeblock = datablock;
+
+  if (isClientObject())
+  {
+    // make a temp datablock clone if there are substitutions
+    if (datablock->getSubstitutionCount() > 0)
+    {
+      afxEffectronData* orig_db = datablock;
+      datablock = new afxEffectronData(*orig_db, true);
+      exeblock = orig_db;
+      // Don't perform substitutions yet, the effectrons's dynamic fields haven't
+      // arrived yet and the substitutions may refer to them. Hold off and do
+      // in in the onAdd() method.
+    }  
+  }
+  else if (started_with_newop)
+  {
+    // make a temp datablock clone if there are substitutions
+    if (datablock->getSubstitutionCount() > 0)
+    {
+      afxEffectronData* orig_db = datablock;
+      datablock = new afxEffectronData(*orig_db, true);
+      exeblock = orig_db;
+      orig_db->performSubstitutions(datablock, this, ranking);
+    }
+  }
+
+  return true;
+}
+
+void afxEffectron::processTick(const Move* m)
+{
+	Parent::processTick(m);
+
+  // don't process moves or client ticks
+  if (m != 0 || isClientObject())
+    return;
+
+  process_server();
+}
+
+void afxEffectron::advanceTime(F32 dt)
+{
+  Parent::advanceTime(dt);
+
+  process_client(dt);
+}
+
+bool afxEffectron::onAdd()
+{
+  if (!Parent::onAdd()) 
+    return false;
+
+  if (isClientObject())
+  {
+    if (datablock->isTempClone())
+    {
+      afxEffectronData* orig_db = (afxEffectronData*)exeblock;
+      orig_db->performSubstitutions(datablock, this, ranking);
+    }  
+  }
+  else if (started_with_newop && !postpone_activation)
+  {
+    if (!activationCallInit())
+      return false;
+    activate();
+  }
+
+  return true;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+U32 afxEffectron::packUpdate(NetConnection* conn, U32 mask, BitStream* stream)
+{
+  S32 mark_stream_pos = stream->getCurPos();
+
+  U32 retMask = Parent::packUpdate(conn, mask, stream);
+  
+  // InitialUpdate
+  if (stream->writeFlag(mask & InitialUpdateMask))
+  {
+    // pack extra object's ghost index or scope id if not yet ghosted
+    if (stream->writeFlag(dynamic_cast<NetObject*>(extra) != 0))
+    {
+      NetObject* net_extra = (NetObject*)extra;
+      S32 ghost_idx = conn->getGhostIndex(net_extra);
+      if (stream->writeFlag(ghost_idx != -1))
+         stream->writeRangedU32(U32(ghost_idx), 0, NetConnection::MaxGhostCount);
+      else
+      {
+        if (stream->writeFlag(net_extra->getScopeId() > 0))
+        {
+          stream->writeInt(net_extra->getScopeId(), NetObject::SCOPE_ID_BITS);
+        }
+      }
+    }
+
+    stream->write(time_factor);
+
+    GameConnection* gconn = dynamic_cast<GameConnection*>(conn);
+    bool zoned_in = (gconn) ? gconn->isZonedIn() : false;
+    if (stream->writeFlag(zoned_in))
+      pack_constraint_info(conn, stream);
+  }
+
+  // StateEvent or SyncEvent
+  if (stream->writeFlag((mask & StateEventMask) || (mask & SyncEventMask)))
+  {
+    stream->write(marks_mask);
+    stream->write(effect_state);
+    stream->write(effect_elapsed);
+  }
+
+  // SyncEvent
+  bool do_sync_event = ((mask & SyncEventMask) && !(mask & InitialUpdateMask));
+  if (stream->writeFlag(do_sync_event))
+  {
+    pack_constraint_info(conn, stream);
+  }
+
+  check_packet_usage(conn, stream, mark_stream_pos, "afxEffectron:");
+  AssertISV(stream->isValid(), "afxEffectron::packUpdate(): write failure occurred, possibly caused by packet-size overrun."); 
+  
+  return retMask;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//
+
+void afxEffectron::unpackUpdate(NetConnection * conn, BitStream * stream)
+{
+  Parent::unpackUpdate(conn, stream);
+  
+  bool initial_update = false;
+  bool zoned_in = true;
+  bool do_sync_event = false;
+  U8 new_marks_mask = 0;
+  U8 new_state = INACTIVE_STATE;
+  F32 new_elapsed = 0;
+
+  // InitialUpdate Only
+  if (stream->readFlag())
+  {    
+    initial_update = true;
+
+    // extra sent
+    if (stream->readFlag())
+    {
+      // cleanup?
+      if (stream->readFlag()) // is ghost_idx
+      {
+        S32 ghost_idx = stream->readRangedU32(0, NetConnection::MaxGhostCount);
+        extra = dynamic_cast<SimObject*>(conn->resolveGhost(ghost_idx));
+      }
+      else
+      {
+        if (stream->readFlag()) // has scope_id
+        {
+          // JTF NOTE: U16 extra_scope_id = stream->readInt(NetObject::SCOPE_ID_BITS);
+          stream->readInt(NetObject::SCOPE_ID_BITS);
+        }
+      }
+    }
+
+    stream->read(&time_factor);
+
+    // if client is marked as fully zoned in
+    if ((zoned_in = stream->readFlag()) == true)
+    {
+      unpack_constraint_info(conn, stream);
+      init_constraints();
+    }
+  }
+
+  // StateEvent or SyncEvent
+  // this state data is sent for both state-events and
+  // sync-events
+  if (stream->readFlag())
+  {
+    stream->read(&new_marks_mask);
+    stream->read(&new_state);
+    stream->read(&new_elapsed);
+
+    marks_mask = new_marks_mask;
+  }
+
+  // SyncEvent
+  do_sync_event = stream->readFlag();
+  if (do_sync_event)
+  {
+    unpack_constraint_info(conn, stream);
+    init_constraints();
+  }
+
+  //~~~~~~~~~~~~~~~~~~~~//
+
+  if (!zoned_in)
+    effect_state = LATE_STATE;
+
+  // need to adjust state info to get all synced up with spell on server
+  if (do_sync_event && !initial_update)
+    sync_client(new_marks_mask, new_state, new_elapsed);
+}
+
+void afxEffectron::sync_with_clients()
+{
+  setMaskBits(SyncEventMask);
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// private
+
+bool afxEffectron::state_expired()
+{
+  afxPhrase* phrase = (effect_state == ACTIVE_STATE) ? active_phrase : NULL;
+
+  if (phrase)
+  {
+    if (phrase->expired(effect_elapsed))
+      return (!phrase->recycle(effect_elapsed));
+    return false;
+  }
+
+  return true;
+}
+
+void afxEffectron::init_constraints()
+{
+  if (constraints_initialized)
+  {
+    //Con::printf("CONSTRAINTS ALREADY INITIALIZED");
+    return;
+  }
+
+  Vector<afxConstraintDef> defs;
+  datablock->gatherConstraintDefs(defs);
+
+  constraint_mgr->initConstraintDefs(defs, isServerObject());
+
+  if (isServerObject())
+  {
+    // find local camera
+    camera_cons_obj = get_camera();
+    if (camera_cons_obj)
+      camera_cons_id = constraint_mgr->setReferenceObject(CAMERA_CONS, camera_cons_obj);
+  }
+  else // if (isClientObject())
+  {
+    // find local camera
+    camera_cons_obj = get_camera();
+    if (camera_cons_obj)
+      camera_cons_id = constraint_mgr->setReferenceObject(CAMERA_CONS, camera_cons_obj);
+
+    // find local listener
+    Point3F listener_pos;
+    listener_pos = SFX->getListener().getTransform().getPosition();
+    listener_cons_id = constraint_mgr->setReferencePoint(LISTENER_CONS, listener_pos);
+  }
+
+  constraint_mgr->adjustProcessOrdering(this);
+
+  constraints_initialized = true;
+}
+
+void afxEffectron::init_scoping()
+{
+  if (scoping_initialized)
+  {
+    //Con::printf("SCOPING ALREADY INITIALIZED");
+    return;
+  }
+
+  if (isServerObject())
+  {
+    if (explicit_clients.size() > 0)
+    {
+      for (U32 i = 0; i < explicit_clients.size(); i++)
+        explicit_clients[i]->objectLocalScopeAlways(this);
+    }
+    else
+    {
+      mNetFlags.set(Ghostable);
+      setScopeAlways();
+    }
+    scoping_initialized = true;
+  }
+}
+
+void afxEffectron::setup_active_fx()
+{
+  active_phrase = new afxPhrase(isServerObject(), /*willStop=*/true);
+
+  if (active_phrase)
+  {
+    active_phrase->init(datablock->fx_list, datablock->duration, this, time_factor, datablock->n_loops);
+  }
+}
+
+bool afxEffectron::cleanup_over()
+{
+  if (active_phrase && !active_phrase->isEmpty())
+    return false;
+
+  return true;
+}
+
+void afxEffectron::inflictDamage(const char * label, const char* flavor, SimObjectId target_id,
+                                   F32 amount, U8 n, F32 ad_amount, F32 radius, Point3F pos, F32 impulse)
+{ 
+  char *posArg = Con::getArgBuffer(64);
+  dSprintf(posArg, 64, "%f %f %f", pos.x, pos.y, pos.z);
+  Con::executef(exeblock, "onDamage", 
+                              getIdString(), 
+                              label,
+                              flavor,
+                              Con::getIntArg(target_id),
+                              Con::getFloatArg(amount), 
+                              Con::getIntArg(n),
+                              posArg,
+                              Con::getFloatArg(ad_amount), 
+                              Con::getFloatArg(radius),
+                              Con::getFloatArg(impulse));
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// private
+
+void afxEffectron::process_server()
+{
+  if (effect_state != INACTIVE_STATE)
+    effect_elapsed += TickSec;
+
+  U8 pending_state = effect_state;
+
+  // check for state changes
+  switch (effect_state)
+  {
+  case INACTIVE_STATE:
+    if (marks_mask & MARK_ACTIVATE)
+      pending_state = ACTIVE_STATE;
+    break;
+  case ACTIVE_STATE:
+    if (marks_mask & MARK_INTERRUPT)
+      pending_state = CLEANUP_STATE;
+    else if (marks_mask & MARK_SHUTDOWN)
+      pending_state = CLEANUP_STATE;
+    else if (state_expired())
+      pending_state = CLEANUP_STATE;
+    break;
+  case CLEANUP_STATE:
+    if (cleanup_over())
+      pending_state = DONE_STATE;
+    break;
+  }
+
+  if (effect_state != pending_state)
+    change_state_s(pending_state);
+
+  if (effect_state == INACTIVE_STATE)
+    return;
+
+  //--------------------------//
+
+  // sample the constraints
+  constraint_mgr->sample(TickSec, Platform::getVirtualMilliseconds());
+
+  if (active_phrase)
+    active_phrase->update(TickSec, effect_elapsed);
+}
+
+void afxEffectron::change_state_s(U8 pending_state)
+{ 
+  if (effect_state == pending_state)
+    return;
+
+  switch (effect_state)
+  {
+  case INACTIVE_STATE:
+    break;
+  case ACTIVE_STATE:
+    leave_active_state_s();
+    break;
+  case CLEANUP_STATE:
+    break;
+  case DONE_STATE:
+    break;
+  }
+
+  effect_state = pending_state;
+
+  switch (pending_state)
+  {
+  case INACTIVE_STATE:
+    break;
+  case ACTIVE_STATE:
+    enter_active_state_s();
+    break;
+  case CLEANUP_STATE:
+    enter_cleanup_state_s();
+    break;
+  case DONE_STATE:
+    enter_done_state_s();
+    break;
+  }
+}
+
+void afxEffectron::enter_done_state_s()
+{ 
+  postEvent(DEACTIVATE_EVENT);
+
+  F32 done_time = effect_elapsed;
+
+  if (active_phrase)
+  {
+    F32 phrase_done;
+    if (active_phrase->willStop() && active_phrase->isInfinite())
+      phrase_done = effect_elapsed + active_phrase->calcAfterLife();
+    else
+      phrase_done = active_phrase->calcDoneTime();
+    if (phrase_done > done_time)
+      done_time = phrase_done;
+  }
+
+  F32 time_left = done_time - effect_elapsed;
+  if (time_left < 0)
+    time_left = 0;
+
+  Sim::postEvent(this, new ObjectDeleteEvent, Sim::getCurrentTime() + time_left*1000 + 500);
+
+  // CALL SCRIPT afxEffectronData::onDeactivate(%eff)
+  Con::executef(exeblock, "onDeactivate", getIdString());
+}
+
+void afxEffectron::enter_active_state_s()
+{ 
+  // stamp constraint-mgr starting time
+  constraint_mgr->setStartTime(Platform::getVirtualMilliseconds());
+  effect_elapsed = 0;
+
+  setup_dynamic_constraints();
+
+  // start casting effects
+  setup_active_fx();
+  if (active_phrase)
+    active_phrase->start(effect_elapsed, effect_elapsed);
+}
+
+void afxEffectron::leave_active_state_s()
+{ 
+  if (active_phrase)
+    active_phrase->stop(effect_elapsed);
+}
+
+void afxEffectron::enter_cleanup_state_s()
+{ 
+  postEvent(SHUTDOWN_EVENT);
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// private
+
+void afxEffectron::process_client(F32 dt)
+{
+  effect_elapsed += dt;
+
+  U8 pending_state = effect_state;
+
+  // check for state changes
+  switch (effect_state)
+  {
+  case INACTIVE_STATE:
+    if (marks_mask & MARK_ACTIVATE)
+      pending_state = ACTIVE_STATE;
+    break;
+  case ACTIVE_STATE:
+    if (marks_mask & MARK_INTERRUPT)
+      pending_state = CLEANUP_STATE;
+    else if (marks_mask & MARK_SHUTDOWN)
+      pending_state = CLEANUP_STATE;
+    else if (state_expired())
+      pending_state = CLEANUP_STATE;
+    break;
+  case CLEANUP_STATE:
+    if (cleanup_over())
+      pending_state = DONE_STATE;
+    break;
+  }
+
+  if (effect_state != pending_state)
+    change_state_c(pending_state);
+
+  if (effect_state == INACTIVE_STATE)
+    return;
+
+  //--------------------------//
+
+  // update the listener constraint position
+  if (!listener_cons_id.undefined())
+  {
+    Point3F listener_pos;
+    listener_pos = SFX->getListener().getTransform().getPosition();
+    constraint_mgr->setReferencePoint(listener_cons_id, listener_pos);
+  }
+
+  // find local camera position
+  Point3F cam_pos;
+  SceneObject* current_cam = get_camera(&cam_pos);
+
+  // detect camera changes
+  if (!camera_cons_id.undefined() && current_cam != camera_cons_obj)
+  {
+    constraint_mgr->setReferenceObject(camera_cons_id, current_cam);
+    camera_cons_obj = current_cam;
+  }
+
+  // sample the constraints
+  constraint_mgr->sample(dt, Platform::getVirtualMilliseconds(), (current_cam) ? &cam_pos : 0);
+
+  // update active effects lists
+  if (active_phrase)
+    active_phrase->update(dt, effect_elapsed);
+}
+
+void afxEffectron::change_state_c(U8 pending_state)
+{ 
+  if (effect_state == pending_state)
+    return;
+
+  switch (effect_state)
+  {
+  case INACTIVE_STATE:
+    break;
+  case ACTIVE_STATE:
+    leave_active_state_c();
+    break;
+  case CLEANUP_STATE:
+    break;
+  case DONE_STATE:
+    break;
+  }
+
+  effect_state = pending_state;
+
+  switch (pending_state)
+  {
+  case INACTIVE_STATE:
+    break;
+  case ACTIVE_STATE:
+    enter_active_state_c(effect_elapsed);
+    break;
+  case CLEANUP_STATE:
+    break;
+  case DONE_STATE:
+    break;
+  }
+}
+
+void afxEffectron::enter_active_state_c(F32 starttime)
+{ 
+  // stamp constraint-mgr starting time
+  constraint_mgr->setStartTime(Platform::getVirtualMilliseconds() - (U32)(effect_elapsed*1000));
+  ///effect_elapsed = 0;
+
+  setup_dynamic_constraints();
+
+  setup_active_fx();
+  if (active_phrase)
+    active_phrase->start(starttime, effect_elapsed);
+}
+
+void afxEffectron::leave_active_state_c()
+{ 
+  if (active_phrase)
+    active_phrase->stop(effect_elapsed);
+}
+
+void afxEffectron::sync_client(U16 marks, U8 state, F32 elapsed)
+{
+  //Con::printf("SYNC marks=%d old_state=%d state=%d elapsed=%g", 
+  //            marks, effect_state, state, elapsed);
+
+  if (effect_state != LATE_STATE)
+    return;
+
+  marks_mask = marks;
+
+  // don't want to be started on late zoning clients
+  if (!datablock->exec_on_new_clients)
+  {
+    effect_state = DONE_STATE;
+  }
+
+  // it looks like we're ghosting pretty late and
+  // should just return to the inactive state.
+  else if (marks & (MARK_INTERRUPT | MARK_DEACTIVATE | MARK_SHUTDOWN))
+  {
+    effect_state = DONE_STATE;
+  }
+
+  // it looks like we should be in the active state.
+  else if (marks & MARK_ACTIVATE)
+  {
+    effect_state = ACTIVE_STATE;
+    effect_elapsed = elapsed;
+    enter_active_state_c(0.0);
+  }
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// public:
+
+void afxEffectron::postEvent(U8 event) 
+{ 
+  setMaskBits(StateEventMask);
+  
+  switch (event)
+  {
+  case ACTIVATE_EVENT:
+    marks_mask |= MARK_ACTIVATE;
+    break;
+  case SHUTDOWN_EVENT:
+    marks_mask |= MARK_SHUTDOWN;
+    break;
+  case DEACTIVATE_EVENT:
+    marks_mask |= MARK_DEACTIVATE;
+    break;
+  case INTERRUPT_EVENT:
+    marks_mask |= MARK_INTERRUPT;
+    break;
+  }
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+void afxEffectron::finish_startup()
+{
+  init_constraints();
+  init_scoping();
+  postEvent(afxEffectron::ACTIVATE_EVENT);
+}
+
+// static
+afxEffectron* 
+afxEffectron::start_effect(afxEffectronData* datablock, SimObject* extra) 
+{
+  AssertFatal(datablock != NULL, "Datablock is missing.");
+
+  afxEffectronData* exeblock = datablock;
+
+  SimObject* param_holder = new SimObject();
+  if (!param_holder->registerObject())
+  {
+    Con::errorf("afxEffectron: failed to register parameter object.");
+    delete param_holder;
+    return 0;
+  }
+
+  param_holder->assignDynamicFieldsFrom(datablock, arcaneFX::sParameterFieldPrefix);
+  if (extra)
+  {
+    // copy dynamic fields from the extra object to the param holder
+    param_holder->assignDynamicFieldsFrom(extra, arcaneFX::sParameterFieldPrefix);
+  }
+
+  // CALL SCRIPT afxEffectronData::onPreactivate(%params, %extra)
+  const char* result = Con::executef(datablock, "onPreactivate", 
+                                     Con::getIntArg(param_holder->getId()), 
+                                     (extra) ? Con::getIntArg(extra->getId()) : "");
+  if (result && result[0] != '\0' && !dAtob(result))
+  {
+#if defined(TORQUE_DEBUG)
+    Con::warnf("afxEffectron: onPreactivate() returned false, effect aborted.");
+#endif
+    Sim::postEvent(param_holder, new ObjectDeleteEvent, Sim::getCurrentTime());
+    return 0;
+  }
+
+  // make a temp datablock clone if there are substitutions
+  if (datablock->getSubstitutionCount() > 0)
+  {
+    datablock = new afxEffectronData(*exeblock, true);
+    exeblock->performSubstitutions(datablock, param_holder);
+  }
+
+  // create a new effectron instance
+  afxEffectron* eff = new afxEffectron(true);
+  eff->setDataBlock(datablock);
+  eff->exeblock = exeblock;
+  eff->setExtra(extra);
+
+  // copy dynamic fields from the param holder to the effectron
+  eff->assignDynamicFieldsFrom(param_holder, arcaneFX::sParameterFieldPrefix);
+  Sim::postEvent(param_holder, new ObjectDeleteEvent, Sim::getCurrentTime());
+
+  // register
+  if (!eff->registerObject())
+  {
+    Con::errorf("afxEffectron: failed to register effectron instance.");
+    Sim::postEvent(eff, new ObjectDeleteEvent, Sim::getCurrentTime());
+    return 0;
+  }
+  registerForCleanup(eff);
+
+  eff->activate();
+
+  return eff;
+}
+
+bool afxEffectron::activationCallInit(bool postponed) 
+{
+  if (postponed && (!started_with_newop || !postpone_activation))
+  {
+    Con::errorf("afxEffectron::activate() -- activate() is only required when creating an effectron with the \"new\" operator "
+                "and the postponeActivation field is set to \"true\".");
+    return false;
+  }
+
+  return true;
+}
+
+void afxEffectron::activate() 
+{
+  // separating the final part of startup allows the calling script
+  // to make certain types of calls on the returned effectron that  
+  // need to happen prior to constraint initialization.
+  Sim::postEvent(this, new EffectronFinishStartupEvent, Sim::getCurrentTime());
+
+  // CALL SCRIPT afxEffectronData::onActivate(%eff)
+  Con::executef(exeblock, "onActivate", getIdString());
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// console functions
+
+DefineEngineMethod(afxEffectron, setTimeFactor, void, (float factor),,
+                   "Sets the time-factor for the effectron.\n\n"
+                   "@ingroup AFX") 
+{
+  object->setTimeFactor(factor);
+}
+
+DefineEngineMethod(afxEffectron, interrupt, void, (),,
+                   "Interrupts and deletes a running effectron.\n\n"
+                   "@ingroup AFX") 
+{
+  object->postEvent(afxEffectron::INTERRUPT_EVENT);
+}
+
+DefineEngineMethod(afxEffectron, activate, void, (),,
+                   "Activates an effectron that was started with postponeActivation=true.\n\n"
+                   "@ingroup AFX") 
+{
+  if (object->activationCallInit(true))
+    object->activate();
+}
+
+DefineEngineFunction(startEffectron, S32,	(afxEffectronData* datablock, const char* constraintSource, const char* constraintName, SimObject* extra),
+											(nullAsType<afxEffectronData*>(), nullAsType<const char*>(), nullAsType<const char*>(), nullAsType<SimObject*>()),
+
+                     "Instantiates the effectron defined by datablock.\n\n"
+                     "@ingroup AFX")
+{
+  if (!datablock)
+  {
+    Con::errorf("startEffectron() -- missing valid datablock.");
+    return 0;
+  }
+
+  //
+  // Start the Effectron
+  //
+  afxEffectron* eff = afxEffectron::start_effect(datablock, extra);
+
+  //
+  // Create a constraint from arguments (if specified).
+  //
+  if (eff)
+  {
+    if (constraintSource && constraintName)
+    {
+      if (!eff->addConstraint(constraintSource, constraintName))
+        Con::errorf("startEffectron() -- failed to find constraint object [%s].", constraintSource);
+    }
+  }
+
+  //
+  // Return the ID (or 0 if start failed).
+  //
+  return (eff) ? eff->getId() : 0;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+

+ 216 - 0
Engine/source/afx/afxEffectron.h

@@ -0,0 +1,216 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
+// Copyright (C) 2015 Faust Logic, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _AFX_COMPOSITE_EFFECT_H_
+#define _AFX_COMPOSITE_EFFECT_H_
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#include "console/typeValidators.h"
+
+#include "afxChoreographer.h"
+#include "afxEffectWrapper.h"
+#include "afxPhrase.h"
+
+class afxChoreographerData;
+class afxEffectWrapperData;
+
+class afxEffectronData : public afxChoreographerData
+{
+  typedef afxChoreographerData Parent;
+
+  class ewValidator : public TypeValidator
+  {
+    U32 id;
+  public:
+    ewValidator(U32 id) { this->id = id; }
+    void validateType(SimObject *object, void *typePtr);
+  };
+
+  bool          do_id_convert;
+
+public:
+  F32           duration;
+  S32           n_loops;
+
+  afxEffectBaseData* dummy_fx_entry;
+
+  afxEffectList fx_list;
+  
+private:
+  void          pack_fx(BitStream* stream, const afxEffectList& fx, bool packed);
+  void          unpack_fx(BitStream* stream, afxEffectList& fx);
+
+public:
+  /*C*/         afxEffectronData();
+  /*C*/         afxEffectronData(const afxEffectronData&, bool = false);
+
+  virtual void  reloadReset();
+
+  virtual bool  onAdd();
+  virtual void  packData(BitStream*);
+  virtual void  unpackData(BitStream*);
+
+  bool          preload(bool server, String &errorStr);
+
+  void          gatherConstraintDefs(Vector<afxConstraintDef>&); 
+
+  virtual bool  allowSubstitutions() const { return true; }
+
+  static void   initPersistFields();
+
+  DECLARE_CONOBJECT(afxEffectronData);
+  DECLARE_CATEGORY("AFX");
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxEffectron
+
+class afxEffectron : public afxChoreographer
+{
+  typedef afxChoreographer Parent;
+
+public:
+  enum MaskBits 
+  {
+    StateEventMask    = Parent::NextFreeMask << 0,
+    SyncEventMask     = Parent::NextFreeMask << 1,
+    NextFreeMask      = Parent::NextFreeMask << 2
+  };
+
+  enum
+  {
+    NULL_EVENT,
+    ACTIVATE_EVENT,
+    SHUTDOWN_EVENT,
+    DEACTIVATE_EVENT,
+    INTERRUPT_EVENT
+  };
+
+  enum
+  {
+    INACTIVE_STATE,
+    ACTIVE_STATE,
+    CLEANUP_STATE,
+    DONE_STATE,
+    LATE_STATE
+  };
+
+  enum {
+    MARK_ACTIVATE   = BIT(0),
+    MARK_SHUTDOWN   = BIT(1),
+    MARK_DEACTIVATE = BIT(2),
+    MARK_INTERRUPT  = BIT(3),
+  };
+
+  class ObjectDeleteEvent : public SimEvent
+  {
+  public:
+    void process(SimObject *obj) { if (obj) obj->deleteObject(); }
+  };
+
+private:
+  static StringTableEntry  CAMERA_CONS;
+  static StringTableEntry  LISTENER_CONS;
+
+private:
+  afxEffectronData*  datablock;
+  SimObject*         exeblock;
+
+  bool          constraints_initialized;
+  bool          scoping_initialized;
+
+  U8            effect_state;
+  F32           effect_elapsed;
+  U8            marks_mask;
+  afxConstraintID listener_cons_id;
+  afxConstraintID camera_cons_id;
+  SceneObject*  camera_cons_obj;
+  afxPhrase*    active_phrase;
+  F32           time_factor;
+
+private:
+  void          init();
+  bool          state_expired();
+  void          init_constraints();
+  void          init_scoping();
+  void          setup_active_fx();
+  bool          cleanup_over();
+
+public:
+  /*C*/         afxEffectron();
+  /*C*/         afxEffectron(bool not_default);
+  /*D*/         ~afxEffectron();
+
+    // STANDARD OVERLOADED METHODS //
+  virtual bool  onNewDataBlock(GameBaseData* dptr, bool reload);
+  virtual void  processTick(const Move*);
+  virtual void  advanceTime(F32 dt);
+  virtual bool  onAdd();
+  virtual U32   packUpdate(NetConnection*, U32, BitStream*);
+  virtual void  unpackUpdate(NetConnection*, BitStream*);
+
+  virtual void  inflictDamage(const char * label, const char* flavor, SimObjectId target,
+                              F32 amt, U8 count, F32 ad_amt, F32 rad, Point3F pos, F32 imp);
+  virtual void  sync_with_clients();
+  void          finish_startup();
+
+  DECLARE_CONOBJECT(afxEffectron);
+  DECLARE_CATEGORY("AFX");
+
+private:
+  void          process_server();
+  //
+  void          change_state_s(U8 pending_state);
+  //
+  void          enter_active_state_s();
+  void          leave_active_state_s();
+  void          enter_cleanup_state_s();
+  void          enter_done_state_s();
+
+private:
+  void          process_client(F32 dt);
+  //
+  void          change_state_c(U8 pending_state);
+  //
+  void          enter_active_state_c(F32 starttime);
+  void          leave_active_state_c();
+
+  void          sync_client(U16 marks, U8 state, F32 elapsed);
+
+public:
+  void          postEvent(U8 event);
+  void          setTimeFactor(F32 f) { time_factor = (f > 0) ? f : 1.0f; }
+  F32           getTimeFactor() { return time_factor; }
+
+  bool          activationCallInit(bool postponed=false);
+  void          activate();
+
+public:
+  static afxEffectron*  start_effect(afxEffectronData*, SimObject* extra);
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+#endif // _AFX_EFFECTRON_H_

+ 2116 - 0
Engine/source/afx/afxMagicMissile.cpp

@@ -0,0 +1,2116 @@
+//-----------------------------------------------------------------------------
+// Copyright (c) 2012 GarageGames, LLC
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//-----------------------------------------------------------------------------
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
+// Copyright (C) 2015 Faust Logic, Inc.
+//
+// afxMagicMissile is a heavily modified variation of the stock Projectile class. In 
+// addition to numerous AFX customizations, it also incorporates functionality based on
+// the following TGE resources:
+//
+// Guided or Seeker Projectiles by Derk Adams
+//   http://www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=6778
+//
+// Projectile Ballistic Coefficients (drag factors) by Mark Owen
+//   http://www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=5128
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#include "afx/arcaneFX.h"
+
+#include "scene/sceneRenderState.h"
+#include "scene/sceneManager.h"
+#include "core/resourceManager.h"
+#include "ts/tsShapeInstance.h"
+#include "sfx/sfxTrack.h"
+#include "sfx/sfxSource.h"
+#include "sfx/sfxSystem.h"
+#include "sfx/sfxTypes.h"
+#include "math/mathUtils.h"
+#include "math/mathIO.h"
+#include "sim/netConnection.h"
+#include "T3D/fx/particleEmitter.h"
+#include "T3D/fx/splash.h"
+#include "T3D/physics/physicsPlugin.h"
+#include "T3D/physics/physicsWorld.h"
+#include "gfx/gfxTransformSaver.h"
+#include "T3D/containerQuery.h"
+#include "T3D/lightDescription.h"
+#include "console/engineAPI.h"
+#include "lighting/lightManager.h"
+
+#include "afx/util/afxEase.h"
+#include "afx/afxMagicMissile.h"
+#include "afx/afxMagicSpell.h"
+#include "afx/afxChoreographer.h"
+
+class ObjectDeleteEvent : public SimEvent
+{
+public:
+  void process(SimObject *object)
+  {
+    object->deleteObject();
+  }
+};
+
+IMPLEMENT_CO_DATABLOCK_V1(afxMagicMissileData);
+
+ConsoleDocClass( afxMagicMissileData,
+   "@brief Defines a particular magic-missile type. (Use with afxMagicSpellData.)\n"
+   "@tsexample\n"
+   "datablock afxMagicMissileData(Fireball_MM)\n"
+   "{\n"
+   "  muzzleVelocity = 50;\n"
+   "  velInheritFactor = 0;\n"
+   "  lifetime = 20000;\n"
+   "  isBallistic = true;\n"
+   "  ballisticCoefficient = 0.85;\n"
+   "  gravityMod = 0.05;\n"
+   "  isGuided = true;\n"
+   "  precision = 30;\n"
+   "  trackDelay = 7;\n"
+   "  launchOffset = \"0 0 43.7965\";\n"
+   "  launchOnServerSignal = true;\n"
+   "};\n"
+   "@endtsexample\n"
+   "@ingroup AFX\n"
+);
+
+IMPLEMENT_CO_NETOBJECT_V1(afxMagicMissile);
+
+ConsoleDocClass( afxMagicMissile,
+   "@brief Magic-missile class used internally by afxMagicSpell. Properties of individual missile types are defined using afxMagicMissileData.\n"
+   "@ingroup AFX\n"
+);
+
+/* From stock Projectile code...
+IMPLEMENT_CALLBACK( ProjectileData, onExplode, void, ( Projectile* proj, Point3F pos, F32 fade ), 
+                   ( proj, pos, fade ),
+					"Called when a projectile explodes.\n"
+               "@param proj The projectile exploding.\n"
+					"@param pos The position of the explosion.\n"
+					"@param fade The currently fadeValue of the projectile, affects its visibility.\n"
+					"@see Projectile, ProjectileData\n"
+				  );
+
+IMPLEMENT_CALLBACK( ProjectileData, onCollision, void, ( Projectile* proj, SceneObject* col, F32 fade, Point3F pos, Point3F normal ),
+                   ( proj, col, fade, pos, normal ),
+					"Called when a projectile collides with another object.\n"
+					"@param proj The projectile colliding.\n"
+					"@param col The object hit by the projectile.\n"
+					"@param fade The current fadeValue of the projectile, affects its visibility.\n"
+					"@param pos The collision position.\n"
+               "@param normal The collision normal.\n"
+					"@see Projectile, ProjectileData\n"
+				  );
+
+const U32 Projectile::csmStaticCollisionMask =  TerrainObjectType    |
+                                                InteriorObjectType   |
+                                                StaticObjectType;
+
+const U32 Projectile::csmDynamicCollisionMask = PlayerObjectType        |
+                                                VehicleObjectType       |
+                                                DamagableItemObjectType;
+
+const U32 Projectile::csmDamageableMask = Projectile::csmDynamicCollisionMask;
+
+U32 Projectile::smProjectileWarpTicks = 5;
+*/
+
+//--------------------------------------------------------------------------
+//
+afxMagicMissileData::afxMagicMissileData()
+{
+   projectileShapeName = ST_NULLSTRING;
+
+   sound = NULL;
+
+   /* From stock Projectile code...
+   explosion = NULL;
+   explosionId = 0;
+
+   waterExplosion = NULL;
+   waterExplosionId = 0;
+   */
+
+   /* From stock Projectile code...
+   faceViewer = false;
+   */
+   scale.set( 1.0f, 1.0f, 1.0f );
+
+   isBallistic = false;
+
+   /* From stock Projectile code...
+	velInheritFactor = 1.0f;
+   */
+	muzzleVelocity = 50;
+   /* From stock Projectile code...
+   impactForce = 0.0f;
+
+	armingDelay = 0;
+   fadeDelay = 20000 / 32;
+   lifetime = 20000 / 32;
+
+   activateSeq = -1;
+   maintainSeq = -1;
+   */
+
+   gravityMod = 1.0;
+   /* From stock Projectile code...
+   bounceElasticity = 0.999f;
+   bounceFriction = 0.3f;
+   */
+
+   particleEmitter = NULL;
+   particleEmitterId = 0;
+
+   particleWaterEmitter = NULL;
+   particleWaterEmitterId = 0;
+
+   splash = NULL;
+   splashId = 0;
+
+   /* From stock Projectile code...
+   decal = NULL;
+   decalId = 0;
+   */
+
+   lightDesc = NULL;
+   lightDescId = 0;
+   
+  starting_vel_vec.zero();
+
+  isGuided = false;
+  precision = 0; 
+  trackDelay = 0;
+  ballisticCoefficient = 1.0f; 
+
+  followTerrain = false;
+  followTerrainHeight = 0.1f;
+  followTerrainAdjustRate = 20.0f;
+  followTerrainAdjustDelay = 0;
+
+  lifetime = MaxLifetimeTicks;
+  collision_mask = arcaneFX::sMissileCollisionMask;
+
+  acceleration = 0;
+  accelDelay = 0;
+  accelLifetime = 0;
+
+  launch_node = ST_NULLSTRING;
+  launch_offset.zero();
+  launch_offset_server.zero();
+  launch_offset_client.zero();
+  launch_node_offset.zero();
+  launch_pitch = 0;
+  launch_pan = 0;
+  launch_cons_s_spec = ST_NULLSTRING;
+  launch_cons_c_spec = ST_NULLSTRING;
+
+  echo_launch_offset = false;
+
+  wiggle_axis_string = ST_NULLSTRING;
+  wiggle_num_axis = 0;
+  wiggle_axis = 0;
+
+  hover_altitude = 0;
+  hover_attack_distance = 0;
+  hover_attack_gradient = 0;
+  hover_time = 0;
+
+  reverse_targeting = false;
+
+  caster_safety_time = U32_MAX;
+}
+
+afxMagicMissileData::afxMagicMissileData(const afxMagicMissileData& other, bool temp_clone) : GameBaseData(other, temp_clone)
+{
+  projectileShapeName = other.projectileShapeName;
+  projectileShape = other.projectileShape; // -- TSShape loads using projectileShapeName
+  sound = other.sound;
+  splash = other.splash;
+  splashId = other.splashId; // -- for pack/unpack of splash ptr
+  lightDesc = other.lightDesc;
+  lightDescId = other.lightDescId; // -- for pack/unpack of lightDesc ptr
+  scale = other.scale;
+  isBallistic = other.isBallistic;
+  muzzleVelocity = other.muzzleVelocity;
+  gravityMod = other.gravityMod;
+  particleEmitter = other.particleEmitter;
+  particleEmitterId = other.particleEmitterId; // -- for pack/unpack of particleEmitter ptr
+  particleWaterEmitter = other.particleWaterEmitter;
+  particleWaterEmitterId = other.particleWaterEmitterId; // -- for pack/unpack of particleWaterEmitter ptr
+
+  collision_mask = other.collision_mask;
+  starting_vel_vec = other.starting_vel_vec;
+  isGuided = other.isGuided;
+  precision = other.precision;
+  trackDelay = other.trackDelay;
+  ballisticCoefficient = other.ballisticCoefficient;
+  followTerrain  = other.followTerrain;
+  followTerrainHeight = other.followTerrainHeight;
+  followTerrainAdjustRate = other.followTerrainAdjustRate;
+  followTerrainAdjustDelay = other.followTerrainAdjustDelay;
+  lifetime = other.lifetime;
+  fadeDelay = other.fadeDelay;
+  acceleration = other.acceleration;
+  accelDelay = other.accelDelay;
+  accelLifetime = other.accelLifetime;
+  launch_node  = other.launch_node;
+  launch_offset = other.launch_offset;
+  launch_offset_server = other.launch_offset_server;
+  launch_offset_client = other.launch_offset_client;
+  launch_node_offset = other.launch_node_offset;
+  launch_pitch = other.launch_pitch;
+  launch_pan = other.launch_pan;
+  launch_cons_s_spec = other.launch_cons_s_spec;
+  launch_cons_c_spec = other.launch_cons_c_spec;
+  launch_cons_s_def = other.launch_cons_s_def;
+  launch_cons_c_def = other.launch_cons_c_def;
+  echo_launch_offset = other.echo_launch_offset;
+  wiggle_magnitudes = other.wiggle_magnitudes;
+  wiggle_speeds = other.wiggle_speeds;
+  wiggle_axis_string = other.wiggle_axis_string;
+  wiggle_num_axis = other.wiggle_num_axis;
+  wiggle_axis = other.wiggle_axis;
+  hover_altitude = other.hover_altitude;
+  hover_attack_distance = other.hover_attack_distance;
+  hover_attack_gradient = other.hover_attack_gradient;
+  hover_time = other.hover_time;
+  reverse_targeting = other.reverse_targeting;
+  caster_safety_time = other.caster_safety_time;
+}
+
+afxMagicMissileData::~afxMagicMissileData()
+{
+  if (wiggle_axis)
+    delete [] wiggle_axis;
+}
+
+afxMagicMissileData* afxMagicMissileData::cloneAndPerformSubstitutions(const SimObject* owner, S32 index)
+{
+  if (!owner || getSubstitutionCount() == 0)
+    return this;
+
+  afxMagicMissileData* sub_missile_db = new afxMagicMissileData(*this, true);
+  performSubstitutions(sub_missile_db, owner, index);
+
+  return sub_missile_db;
+}
+
+//--------------------------------------------------------------------------
+
+#define myOffset(field) Offset(field, afxMagicMissileData)
+
+FRangeValidator muzzleVelocityValidator(0, 10000);
+FRangeValidator missilePrecisionValidator(0.f, 100.f);
+FRangeValidator missileTrackDelayValidator(0, 100000);
+FRangeValidator missileBallisticCoefficientValidator(0, 1);
+
+void afxMagicMissileData::initPersistFields()
+{
+   static IRangeValidatorScaled ticksFromMS(TickMs, 0, MaxLifetimeTicks);
+
+   addField("particleEmitter", TYPEID<ParticleEmitterData>(), Offset(particleEmitter, afxMagicMissileData));
+   addField("particleWaterEmitter", TYPEID<ParticleEmitterData>(), Offset(particleWaterEmitter, afxMagicMissileData));
+
+   addField("projectileShapeName", TypeFilename, Offset(projectileShapeName, afxMagicMissileData));
+   addField("scale", TypePoint3F, Offset(scale, afxMagicMissileData));
+
+   addField("sound", TypeSFXTrackName, Offset(sound, afxMagicMissileData));
+
+   /* From stock Projectile code...
+   addField("explosion", TYPEID< ExplosionData >(), Offset(explosion, ProjectileData));
+   addField("waterExplosion", TYPEID< ExplosionData >(), Offset(waterExplosion, ProjectileData));
+   */
+
+   addField("splash", TYPEID<SplashData>(), Offset(splash, afxMagicMissileData));
+   /* From stock Projectile code...
+   addField("decal", TYPEID< DecalData >(), Offset(decal, ProjectileData));
+   */
+
+   addField("lightDesc", TYPEID< LightDescription >(), Offset(lightDesc, afxMagicMissileData));
+
+   addField("isBallistic", TypeBool,   Offset(isBallistic, afxMagicMissileData));
+   /* From stock Projectile code...
+   addField("velInheritFactor", TypeF32, Offset(velInheritFactor, ProjectileData));
+   */
+   addNamedFieldV(muzzleVelocity,    TypeF32,      afxMagicMissileData,  &muzzleVelocityValidator);
+   /* From stock Projectile code...
+   addField("impactForce", TypeF32, Offset(impactForce, ProjectileData));
+   */
+   addNamedFieldV(lifetime,    TypeS32,              afxMagicMissileData,  &ticksFromMS);
+   /* From stock Projectile code...
+   addProtectedField("armingDelay", TypeS32, Offset(armingDelay, ProjectileData), &setArmingDelay, &getScaledValue, 
+      "The time in milliseconds before the projectile is armed and will cause damage or explode on impact." );
+
+   addProtectedField("fadeDelay", TypeS32, Offset(fadeDelay, ProjectileData), &setFadeDelay, &getScaledValue,
+      "The time in milliseconds when the projectile begins to fade out.  Must be less than the lifetime to have an effect." );
+
+   addField("bounceElasticity", TypeF32, Offset(bounceElasticity, ProjectileData));
+   addField("bounceFriction", TypeF32, Offset(bounceFriction, ProjectileData));
+   */
+   addField("gravityMod", TypeF32, Offset(gravityMod, afxMagicMissileData));
+
+   // FIELDS ADDED BY MAGIC-MISSILE
+
+   addField("missileShapeName",    TypeFilename, myOffset(projectileShapeName));
+   addField("missileShapeScale",   TypePoint3F,  myOffset(scale));
+
+   addField("startingVelocityVector",TypePoint3F,  myOffset(starting_vel_vec));
+
+   addNamedField(isGuided,               TypeBool,   afxMagicMissileData);
+   addNamedFieldV(precision,             TypeF32,    afxMagicMissileData,  &missilePrecisionValidator); 
+   addNamedFieldV(trackDelay,            TypeS32,    afxMagicMissileData,  &missileTrackDelayValidator); 
+   addNamedFieldV(ballisticCoefficient,  TypeF32,    afxMagicMissileData,  &missileBallisticCoefficientValidator);
+
+   addField("collisionMask",         TypeS32,      myOffset(collision_mask));
+
+   addField("followTerrain",             TypeBool, myOffset(followTerrain));
+   addField("followTerrainHeight",       TypeF32,  myOffset(followTerrainHeight));
+   addField("followTerrainAdjustRate",   TypeF32,  myOffset(followTerrainAdjustRate));
+   addFieldV("followTerrainAdjustDelay", TypeS32,  myOffset(followTerrainAdjustDelay), &ticksFromMS); 
+
+   addNamedField(acceleration,     TypeF32,  afxMagicMissileData);
+   addNamedFieldV(accelDelay,      TypeS32,  afxMagicMissileData,  &ticksFromMS);
+   addNamedFieldV(accelLifetime,   TypeS32,  afxMagicMissileData,  &ticksFromMS);
+
+   addField("launchNode",        TypeString,   myOffset(launch_node));
+   addField("launchOffset",      TypePoint3F,  myOffset(launch_offset));
+   addField("launchOffsetServer",TypePoint3F,  myOffset(launch_offset_server));
+   addField("launchOffsetClient",TypePoint3F,  myOffset(launch_offset_client));
+   addField("launchNodeOffset",  TypePoint3F,  myOffset(launch_node_offset));
+   addField("launchAimPitch",    TypeF32,      myOffset(launch_pitch));
+   addField("launchAimPan",      TypeF32,      myOffset(launch_pan));
+   addField("launchConstraintServer",  TypeString,   myOffset(launch_cons_s_spec));
+   addField("launchConstraintClient",  TypeString,   myOffset(launch_cons_c_spec));
+   //
+   addField("echoLaunchOffset",  TypeBool,     myOffset(echo_launch_offset));
+
+   addField("wiggleMagnitudes", TypeF32Vector, myOffset(wiggle_magnitudes));
+   addField("wiggleSpeeds",     TypeF32Vector, myOffset(wiggle_speeds));
+   addField("wiggleAxis",       TypeString,    myOffset(wiggle_axis_string));
+
+   addField("hoverAltitude",       TypeF32,    myOffset(hover_altitude));
+   addField("hoverAttackDistance", TypeF32,    myOffset(hover_attack_distance));
+   addField("hoverAttackGradient", TypeF32,    myOffset(hover_attack_gradient));
+   addFieldV("hoverTime",          TypeS32,    myOffset(hover_time), &ticksFromMS); 
+
+   addField("reverseTargeting",  TypeBool,     myOffset(reverse_targeting));
+
+   addFieldV("casterSafetyTime", TypeS32,      myOffset(caster_safety_time), &ticksFromMS); 
+
+   Parent::initPersistFields();
+
+   // disallow some field substitutions
+   onlyKeepClearSubstitutions("particleEmitter"); // subs resolving to "~~", or "~0" are OK
+   onlyKeepClearSubstitutions("particleWaterEmitter");
+   onlyKeepClearSubstitutions("sound");
+   onlyKeepClearSubstitutions("splash");
+}
+
+
+//--------------------------------------------------------------------------
+bool afxMagicMissileData::onAdd()
+{
+   if(!Parent::onAdd())
+      return false;
+
+   // ADDED BY MAGIC-MISSILE
+
+   // Wiggle axes ////////////////////////////////////////////////////////////  
+   if (wiggle_axis_string != ST_NULLSTRING && wiggle_num_axis == 0)   
+   {
+      // Tokenize input string and convert to Point3F array
+      //
+      Vector<char*> dataBlocks(__FILE__, __LINE__);
+
+      // make a copy of points_string
+      char* tokCopy = new char[dStrlen(wiggle_axis_string) + 1];
+      dStrcpy(tokCopy, wiggle_axis_string);
+
+      // extract tokens one by one, adding them to dataBlocks
+      char* currTok = dStrtok(tokCopy, " \t");
+      while (currTok != NULL) 
+      {
+         dataBlocks.push_back(currTok);
+         currTok = dStrtok(NULL, " \t");
+      }
+
+      // bail if there were no tokens in the string
+      if (dataBlocks.size() == 0) 
+      {
+         Con::warnf(ConsoleLogEntry::General, "afxMagicMissileData(%s) invalid wiggle axis string. No tokens found", getName());
+         delete [] tokCopy;
+         return false;
+      }
+
+      // Find wiggle_num_axis (round up to multiple of 3) // WARNING here if not multiple of 3?
+      for (U32 i = 0; i < dataBlocks.size()%3; i++) 
+      {
+         dataBlocks.push_back("0.0");
+      }
+
+      wiggle_num_axis = dataBlocks.size()/3;
+      wiggle_axis = new Point3F[wiggle_num_axis];
+
+      U32 p_i = 0;
+      for (U32 i = 0; i < dataBlocks.size(); i+=3, p_i++)
+      {
+         F32 x,y,z;
+         x = dAtof(dataBlocks[i]);  // What about overflow?
+         y = dAtof(dataBlocks[i+1]);
+         z = dAtof(dataBlocks[i+2]);
+         wiggle_axis[p_i].set(x,y,z);
+
+         wiggle_axis[p_i].normalizeSafe(); // sufficient????
+      }
+
+      delete [] tokCopy; 
+   }
+
+   launch_cons_s_def.parseSpec(launch_cons_s_spec, true, false);
+   launch_cons_c_def.parseSpec(launch_cons_c_spec, false, true);
+
+   return true;
+}
+
+
+bool afxMagicMissileData::preload(bool server, String &errorStr)
+{
+   if (Parent::preload(server, errorStr) == false)
+      return false;
+      
+   if( !server )
+   {
+      if (!particleEmitter && particleEmitterId != 0)
+         if (Sim::findObject(particleEmitterId, particleEmitter) == false)
+            Con::errorf(ConsoleLogEntry::General, "afxMagicMissileData::preload: Invalid packet, bad datablockId(particleEmitter): %d", particleEmitterId);
+
+      if (!particleWaterEmitter && particleWaterEmitterId != 0)
+         if (Sim::findObject(particleWaterEmitterId, particleWaterEmitter) == false)
+            Con::errorf(ConsoleLogEntry::General, "afxMagicMissileData::preload: Invalid packet, bad datablockId(particleWaterEmitter): %d", particleWaterEmitterId);
+
+      /* From stock Projectile code...
+      if (!explosion && explosionId != 0)
+         if (Sim::findObject(explosionId, explosion) == false)
+            Con::errorf(ConsoleLogEntry::General, "ProjectileData::preload: Invalid packet, bad datablockId(explosion): %d", explosionId);
+
+      if (!waterExplosion && waterExplosionId != 0)
+         if (Sim::findObject(waterExplosionId, waterExplosion) == false)
+            Con::errorf(ConsoleLogEntry::General, "ProjectileData::preload: Invalid packet, bad datablockId(waterExplosion): %d", waterExplosionId);
+      */
+
+      if (!splash && splashId != 0)
+         if (Sim::findObject(splashId, splash) == false)
+            Con::errorf(ConsoleLogEntry::General, "afxMagicMissileData::preload: Invalid packet, bad datablockId(splash): %d", splashId);
+
+      /* From stock Projectile code...
+      if (!decal && decalId != 0)
+         if (Sim::findObject(decalId, decal) == false)
+            Con::errorf(ConsoleLogEntry::General, "ProjectileData::preload: Invalid packet, bad datablockId(decal): %d", decalId);
+      */
+
+      String errorStr;
+      if( !sfxResolve( &sound, errorStr ) )
+         Con::errorf(ConsoleLogEntry::General, "afxMagicMissileData::preload: Invalid packet: %s", errorStr.c_str());
+
+      if (!lightDesc && lightDescId != 0)
+         if (Sim::findObject(lightDescId, lightDesc) == false)
+            Con::errorf(ConsoleLogEntry::General, "afxMagicMissileData::preload: Invalid packet, bad datablockid(lightDesc): %d", lightDescId);   
+   }
+
+   if (projectileShapeName != ST_NULLSTRING) 
+   {
+      projectileShape = ResourceManager::get().load(projectileShapeName);
+      if (bool(projectileShape) == false)
+      {
+         errorStr = String::ToString("afxMagicMissileData::load: Couldn't load shape \"%s\"", projectileShapeName);
+         return false;
+      }
+      /* From stock Projectile code...
+      activateSeq = projectileShape->findSequence("activate");
+      maintainSeq = projectileShape->findSequence("maintain");
+      */
+   }
+
+   if (bool(projectileShape)) // create an instance to preload shape data
+   {
+      TSShapeInstance* pDummy = new TSShapeInstance(projectileShape, !server);
+      delete pDummy;
+   }
+
+   return true;
+}
+
+//--------------------------------------------------------------------------
+// Modified from floorPlanRes.cc
+// Read a vector of items
+template <class T>
+bool readVector(Vector<T> & vec, Stream & stream, const char * msg)
+{
+   U32   num, i;
+   bool  Ok = true;
+   stream.read( & num );
+   vec.setSize( num );
+   for( i = 0; i < num && Ok; i++ ){
+      Ok = stream.read(& vec[i]);
+      AssertISV( Ok, avar("math vec read error (%s) on elem %d", msg, i) );
+   }
+   return Ok;
+}
+// Write a vector of items
+template <class T>
+bool writeVector(const Vector<T> & vec, Stream & stream, const char * msg)
+{
+   bool  Ok = true;
+   stream.write( vec.size() );
+   for( U32 i = 0; i < vec.size() && Ok; i++ ) {
+      Ok = stream.write(vec[i]);
+      AssertISV( Ok, avar("vec write error (%s) on elem %d", msg, i) );
+   }
+   return Ok;
+}
+//--------------------------------------------------------------------------
+
+void afxMagicMissileData::packData(BitStream* stream)
+{
+   Parent::packData(stream);
+
+   stream->writeString(projectileShapeName);
+   /* From stock Projectile code...
+   stream->writeFlag(faceViewer);
+   */
+   if(stream->writeFlag(scale.x != 1 || scale.y != 1 || scale.z != 1))
+   {
+      stream->write(scale.x);
+      stream->write(scale.y);
+      stream->write(scale.z);
+   }
+
+   stream->write(collision_mask);
+
+   if (stream->writeFlag(particleEmitter != NULL))
+      stream->writeRangedU32(particleEmitter->getId(), DataBlockObjectIdFirst,
+                                                   DataBlockObjectIdLast);
+
+   if (stream->writeFlag(particleWaterEmitter != NULL))
+      stream->writeRangedU32(particleWaterEmitter->getId(), DataBlockObjectIdFirst,
+                                                   DataBlockObjectIdLast);
+
+   /* From stock Projectile code...
+   if (stream->writeFlag(explosion != NULL))
+      stream->writeRangedU32(explosion->getId(), DataBlockObjectIdFirst,
+                                                 DataBlockObjectIdLast);
+
+   if (stream->writeFlag(waterExplosion != NULL))
+      stream->writeRangedU32(waterExplosion->getId(), DataBlockObjectIdFirst,
+                                                      DataBlockObjectIdLast);
+   */
+
+   if (stream->writeFlag(splash != NULL))
+      stream->writeRangedU32(splash->getId(), DataBlockObjectIdFirst,
+                                              DataBlockObjectIdLast);
+
+   /* From stock Projectile code...
+   if (stream->writeFlag(decal != NULL))
+      stream->writeRangedU32(decal->getId(), DataBlockObjectIdFirst,
+                                              DataBlockObjectIdLast);
+   */
+
+   sfxWrite( stream, sound );
+
+   if ( stream->writeFlag(lightDesc != NULL))
+      stream->writeRangedU32(lightDesc->getId(), DataBlockObjectIdFirst,
+                                                 DataBlockObjectIdLast);
+
+   /* From stock Projectile code...
+   stream->write(impactForce);
+   */
+
+   stream->write(lifetime);
+   /* From stock Projectile code...
+   stream->write(armingDelay);
+   stream->write(fadeDelay);
+   */
+
+   if(stream->writeFlag(isBallistic))
+   {
+      stream->write(gravityMod);
+      /* From stock Projectile code...
+      stream->write(bounceElasticity);
+      stream->write(bounceFriction);
+      */
+      stream->write(ballisticCoefficient);
+   }
+
+   if(stream->writeFlag(isGuided))
+   {
+      stream->write(precision);
+      stream->write(trackDelay);
+   }
+
+   stream->write(muzzleVelocity);
+   mathWrite(*stream, starting_vel_vec);
+   stream->write(acceleration);
+   stream->write(accelDelay);
+   stream->write(accelLifetime);
+
+   stream->writeString(launch_node);
+   mathWrite(*stream, launch_offset);
+   mathWrite(*stream, launch_offset_server);
+   mathWrite(*stream, launch_offset_client);
+   mathWrite(*stream, launch_node_offset);
+   stream->write(launch_pitch);
+   stream->write(launch_pan);
+   stream->writeString(launch_cons_c_spec);
+   stream->writeFlag(echo_launch_offset);
+
+   writeVector(wiggle_magnitudes, *stream, "afxMagicMissile: wiggle_magnitudes");
+   writeVector(wiggle_speeds, *stream, "afxMagicMissile: wiggle_speeds");
+
+   stream->write(wiggle_num_axis);
+   for (U32 i = 0; i < wiggle_num_axis; i++)
+      mathWrite(*stream, wiggle_axis[i]);
+
+   stream->write(hover_altitude);
+   stream->write(hover_attack_distance);
+   stream->write(hover_attack_gradient);
+   stream->writeRangedU32(hover_time, 0, MaxLifetimeTicks);
+
+   stream->writeFlag(reverse_targeting);
+
+   stream->write(caster_safety_time);
+}
+
+void afxMagicMissileData::unpackData(BitStream* stream)
+{
+   Parent::unpackData(stream);
+
+   projectileShapeName = stream->readSTString();
+   /* From stock Projectile code...
+   faceViewer = stream->readFlag();
+   */
+   if(stream->readFlag())
+   {
+      stream->read(&scale.x);
+      stream->read(&scale.y);
+      stream->read(&scale.z);
+   }
+   else
+      scale.set(1,1,1);
+
+   stream->read(&collision_mask);
+
+   if (stream->readFlag())
+      particleEmitterId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast);
+
+   if (stream->readFlag())
+      particleWaterEmitterId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast);
+
+   /* From stock Projectile code...
+   if (stream->readFlag())
+      explosionId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast);
+
+   if (stream->readFlag())
+      waterExplosionId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast);
+   */
+   
+   if (stream->readFlag())
+      splashId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast);
+
+   /* From stock Projectile code...
+   if (stream->readFlag())
+      decalId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast);
+   */
+   
+   sfxRead( stream, &sound );
+
+   if (stream->readFlag())
+      lightDescId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast);
+
+   /* From stock Projectile code...
+   stream->read(&impactForce);
+   */
+
+   stream->read(&lifetime);
+   /* From stock Projectile code...
+   stream->read(&armingDelay);
+   stream->read(&fadeDelay);
+   */
+
+   isBallistic = stream->readFlag();
+   if(isBallistic)
+   {
+      stream->read(&gravityMod);
+      /* From stock Projectile code...
+      stream->read(&bounceElasticity);
+      stream->read(&bounceFriction);
+      */
+      stream->read(&ballisticCoefficient);
+   }
+
+   isGuided = stream->readFlag();
+   if(isGuided)
+   {
+      stream->read(&precision);
+      stream->read(&trackDelay);
+   }
+
+   stream->read(&muzzleVelocity);
+   mathRead(*stream, &starting_vel_vec);
+   stream->read(&acceleration);
+   stream->read(&accelDelay);
+   stream->read(&accelLifetime);
+
+   launch_node = stream->readSTString();
+   mathRead(*stream, &launch_offset);
+   mathRead(*stream, &launch_offset_server);
+   mathRead(*stream, &launch_offset_client);
+   mathRead(*stream, &launch_node_offset);
+   stream->read(&launch_pitch);
+   stream->read(&launch_pan);
+   launch_cons_c_spec = stream->readSTString();
+   echo_launch_offset = stream->readFlag();
+
+   readVector(wiggle_magnitudes, *stream, "afxMagicMissile: wiggle_magnitudes");
+   readVector(wiggle_speeds, *stream, "afxMagicMissile: wiggle_speeds");
+
+   if (wiggle_axis)
+      delete [] wiggle_axis;
+   wiggle_axis = 0;
+   wiggle_num_axis = 0;
+
+   stream->read(&wiggle_num_axis);
+   if (wiggle_num_axis > 0)
+   {
+      wiggle_axis = new Point3F[wiggle_num_axis];
+      for (U32 i = 0; i < wiggle_num_axis; i++)
+         mathRead(*stream, &wiggle_axis[i]);
+   }
+
+   stream->read(&hover_altitude);
+   stream->read(&hover_attack_distance);
+   stream->read(&hover_attack_gradient);
+   hover_time = stream->readRangedU32(0, MaxLifetimeTicks);
+
+   reverse_targeting = stream->readFlag();
+
+   stream->read(&caster_safety_time);
+}
+
+/* From stock Projectile code...
+bool ProjectileData::setLifetime( void *obj, const char *index, const char *data )
+{
+	S32 value = dAtoi(data);
+   value = scaleValue(value);
+   
+   ProjectileData *object = static_cast<ProjectileData*>(obj);
+   object->lifetime = value;
+
+   return false;
+}
+
+bool ProjectileData::setArmingDelay( void *obj, const char *index, const char *data )
+{
+	S32 value = dAtoi(data);
+   value = scaleValue(value);
+
+   ProjectileData *object = static_cast<ProjectileData*>(obj);
+   object->armingDelay = value;
+
+   return false;
+}
+
+bool ProjectileData::setFadeDelay( void *obj, const char *index, const char *data )
+{
+	S32 value = dAtoi(data);
+   value = scaleValue(value);
+
+   ProjectileData *object = static_cast<ProjectileData*>(obj);
+   object->fadeDelay = value;
+
+   return false;
+}
+
+const char *ProjectileData::getScaledValue( void *obj, const char *data)
+{
+
+	S32 value = dAtoi(data);
+   value = scaleValue(value, false);
+
+   String stringData = String::ToString(value);
+   char *strBuffer = Con::getReturnBuffer(stringData.size());
+   dMemcpy( strBuffer, stringData, stringData.size() );
+
+   return strBuffer;
+}
+
+S32 ProjectileData::scaleValue( S32 value, bool down )
+{
+   S32 minV = 0;
+   S32 maxV = Projectile::MaxLivingTicks;
+   
+   // scale down to ticks before we validate
+   if( down )
+      value /= TickMs;
+   
+   if(value < minV || value > maxV)
+	{
+      Con::errorf("ProjectileData::scaleValue(S32 value = %d, bool down = %b) - Scaled value must be between %d and %d", value, down, minV, maxV);
+		if(value < minV)
+			value = minV;
+		else if(value > maxV)
+			value = maxV;
+	}
+
+   // scale up from ticks after we validate
+   if( !down )
+      value *= TickMs;
+
+   return value;
+}
+*/
+
+void afxMagicMissileData::gather_cons_defs(Vector<afxConstraintDef>& defs)
+{ 
+  if (launch_cons_s_def.isDefined())
+    defs.push_back(launch_cons_s_def);
+  if (launch_cons_c_def.isDefined())
+    defs.push_back(launch_cons_c_def);
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxMagicMissile
+
+afxMagicMissile::afxMagicMissile()
+{
+  init(true, true);
+}
+
+afxMagicMissile::afxMagicMissile(bool on_server, bool on_client)
+{
+  init(on_server, on_client);
+}
+
+//--------------------------------------------------------------------------
+//--------------------------------------
+//
+void afxMagicMissile::init(bool on_server, bool on_client)
+{
+  mPhysicsWorld = NULL;
+
+  mTypeMask |= ProjectileObjectType | LightObjectType;
+
+  mLight = LightManager::createLightInfo();
+  mLight->setType( LightInfo::Point );   
+
+  mLightState.clear();
+  mLightState.setLightInfo( mLight );
+
+  mCurrPosition.zero();
+  mCurrVelocity.set(0, 0, 1);
+
+  mCurrTick = 0;
+
+  mParticleEmitter   = NULL;
+  mParticleWaterEmitter = NULL;
+  mSound = 0;
+
+  mProjectileShape   = NULL;
+
+  mDataBlock = NULL;
+
+  choreographer = NULL;
+
+  if (on_server != on_client)
+  {
+    client_only = on_client;
+    server_only = on_server;
+    mNetFlags.clear(Ghostable | ScopeAlways);
+    if (client_only)
+      mNetFlags.set(IsGhost);
+  }
+  else
+  {
+    // note -- setting neither server or client makes no sense so we
+    // treat as if both are set.
+    mNetFlags.set(Ghostable | ScopeAlways);
+    client_only = server_only = false;
+  }
+
+  mCurrDeltaBase.zero();
+  mCurrBackDelta.zero();
+  collision_mask = 0;
+  prec_inc = 0.0f;
+
+  did_launch = false;
+  did_impact = false;
+
+  missile_target = NULL;
+  collide_exempt = NULL;
+
+  use_accel = false;
+
+  hover_attack_go = false;
+  hover_attack_tick = 0;
+
+  starting_velocity = 0.0;
+  starting_vel_vec.zero();
+
+  ss_object = 0;
+  ss_index = 0;
+}
+
+afxMagicMissile::~afxMagicMissile()
+{
+   SAFE_DELETE(mLight);
+
+   delete mProjectileShape;
+   mProjectileShape = NULL;
+}
+
+//--------------------------------------------------------------------------
+void afxMagicMissile::initPersistFields()
+{
+   addGroup("Physics");
+   addField("initialPosition", TypePoint3F, Offset(mCurrPosition, afxMagicMissile) ,
+     "Initial starting position for this missile.");
+   addField("initialVelocity", TypePoint3F, Offset(mCurrVelocity, afxMagicMissile) ,
+     "Initial starting velocity for this missile.");
+   endGroup("Physics");
+
+   /* From stock Projectile code...
+   addGroup("Source");
+   addField("sourceObject",     TypeS32,     Offset(mSourceObjectId, Projectile) ,"The object that fires this projectile. If this projectile was fired by a WeaponImage, it will be the object that owns the WeaponImage, usually the player.");
+   addField("sourceSlot",       TypeS32,     Offset(mSourceObjectSlot, Projectile) ,"Which weapon slot on the sourceObject that this projectile originates from.");
+   endGroup("Source");
+  */
+
+   Parent::initPersistFields();
+}
+
+/* From stock Projectile code...
+bool Projectile::calculateImpact(float,
+                                 Point3F& pointOfImpact,
+                                 float&   impactTime)
+{
+   Con::warnf(ConsoleLogEntry::General, "Projectile::calculateImpact: Should never be called");
+
+   impactTime = 0;
+   pointOfImpact.set(0, 0, 0);
+   return false;
+}
+
+
+//--------------------------------------------------------------------------
+F32 Projectile::getUpdatePriority(CameraScopeQuery *camInfo, U32 updateMask, S32 updateSkips)
+{
+   F32 ret = Parent::getUpdatePriority(camInfo, updateMask, updateSkips);
+   // if the camera "owns" this object, it should have a slightly higher priority
+   if(mSourceObject == camInfo->camera)
+      return ret + 0.2;
+   return ret;
+}
+*/
+
+bool afxMagicMissile::onAdd()
+{
+   if(!Parent::onAdd())
+      return false;
+
+   if (isServerObject())
+   {
+      /* From stock Projectile code...
+      ShapeBase* ptr;
+      if (Sim::findObject(mSourceObjectId, ptr))
+      {
+         mSourceObject = ptr;
+
+         // Since we later do processAfter( mSourceObject ) we must clearProcessAfter
+         // if it is deleted. SceneObject already handles this in onDeleteNotify so
+         // all we need to do is register for the notification.
+         deleteNotify( ptr );
+      }
+      else
+      {
+         if (mSourceObjectId != -1)
+            Con::errorf(ConsoleLogEntry::General, "Projectile::onAdd: mSourceObjectId is invalid");
+         mSourceObject = NULL;
+      }
+
+      // If we're on the server, we need to inherit some of our parent's velocity
+      //
+      mCurrTick = 0;
+     */
+   }
+   else
+   {
+      if (bool(mDataBlock->projectileShape))
+      {
+         mProjectileShape = new TSShapeInstance(mDataBlock->projectileShape, true);
+         /* From stock Projectile code...
+         if (mDataBlock->activateSeq != -1)
+         {
+            mActivateThread = mProjectileShape->addThread();
+            mProjectileShape->setTimeScale(mActivateThread, 1);
+            mProjectileShape->setSequence(mActivateThread, mDataBlock->activateSeq, 0);
+         }
+         */
+      }
+      if (mDataBlock->particleEmitter != NULL)
+      {
+         ParticleEmitter* pEmitter = new ParticleEmitter;
+         //pEmitter->setDataBlock(mDataBlock->particleEmitter->cloneAndPerformSubstitutions(ss_object, ss_index));
+         pEmitter->onNewDataBlock(mDataBlock->particleEmitter->cloneAndPerformSubstitutions(ss_object, ss_index), false);
+         if (pEmitter->registerObject() == false)
+         {
+            Con::warnf(ConsoleLogEntry::General, "Could not register particle emitter for particle of class: %s", mDataBlock->getName());
+            delete pEmitter;
+            pEmitter = NULL;
+         }
+         mParticleEmitter = pEmitter;
+      }
+
+      if (mDataBlock->particleWaterEmitter != NULL)
+      {
+         ParticleEmitter* pEmitter = new ParticleEmitter;
+         pEmitter->onNewDataBlock(mDataBlock->particleWaterEmitter->cloneAndPerformSubstitutions(ss_object, ss_index), false);
+         if (pEmitter->registerObject() == false)
+         {
+            Con::warnf(ConsoleLogEntry::General, "Could not register particle emitter for particle of class: %s", mDataBlock->getName());
+            delete pEmitter;
+            pEmitter = NULL;
+         }
+         mParticleWaterEmitter = pEmitter;
+      }
+   }
+   /* From stock Projectile code...
+   if (mSourceObject.isValid())
+      processAfter(mSourceObject);
+  */
+
+   // detect for acceleration
+   use_accel = (mDataBlock->acceleration != 0 && mDataBlock->accelLifetime > 0);
+
+   // Setup our bounding box
+   if (bool(mDataBlock->projectileShape) == true)
+      mObjBox = mDataBlock->projectileShape->bounds;
+   else
+      mObjBox = Box3F(Point3F(0, 0, 0), Point3F(0, 0, 0));
+   resetWorldBox();
+   addToScene();
+
+   if ( PHYSICSMGR )
+      mPhysicsWorld = PHYSICSMGR->getWorld( isServerObject() ? "server" : "client" );
+
+   return true;
+}
+
+
+void afxMagicMissile::onRemove()
+{
+   if(mParticleEmitter)
+   {
+      mParticleEmitter->deleteWhenEmpty();
+      mParticleEmitter = NULL;
+   }
+
+   if(mParticleWaterEmitter)
+   {
+      mParticleWaterEmitter->deleteWhenEmpty();
+      mParticleWaterEmitter = NULL;
+   }
+
+   SFX_DELETE( mSound );
+
+   removeFromScene();
+   Parent::onRemove();
+}
+
+
+bool afxMagicMissile::onNewDataBlock(GameBaseData* dptr, bool reload)
+{
+   mDataBlock = dynamic_cast<afxMagicMissileData*>(dptr);
+   if (!mDataBlock || !Parent::onNewDataBlock(dptr, reload))
+      return false;
+
+   starting_velocity = mDataBlock->muzzleVelocity;
+   starting_vel_vec = mDataBlock->starting_vel_vec;
+   collision_mask = mDataBlock->collision_mask;
+
+   if ( isGhost() )
+   {
+      // Create the sound ahead of time.  This reduces runtime
+      // costs and makes the system easier to understand.
+
+      SFX_DELETE( mSound );
+
+      if ( mDataBlock->sound )
+         mSound = SFX->createSource( mDataBlock->sound );
+   }
+
+   return true;
+}
+
+
+//--------------------------------------------------------------------------
+
+void afxMagicMissile::submitLights( LightManager *lm, bool staticLighting )
+{
+   if ( staticLighting || isHidden() || !mDataBlock->lightDesc )
+      return;
+   
+   mDataBlock->lightDesc->submitLight( &mLightState, getRenderTransform(), lm, this );   
+}
+
+bool afxMagicMissile::pointInWater(const Point3F &point)
+{
+   // This is pretty much a hack so we can use the existing ContainerQueryInfo
+   // and findObject router.
+   
+   // We only care if we intersect with water at all 
+   // so build a box at the point that has only 1 z extent.
+   // And test if water coverage is anything other than zero.
+
+   Box3F boundsBox( point, point );
+   boundsBox.maxExtents.z += 1.0f;
+
+   ContainerQueryInfo info;
+   info.box = boundsBox;
+   info.mass = 0.0f;
+   
+   // Find and retreive physics info from intersecting WaterObject(s)
+   if(mContainer != NULL)
+   {
+      mContainer->findObjects( boundsBox, WaterObjectType, findRouter, &info );
+   }
+   else
+   {
+      // Handle special case where the projectile has exploded prior to having
+      // called onAdd() on the client.  This occurs when the projectile on the
+      // server is created and then explodes in the same network update tick.
+      // On the client end in NetConnection::ghostReadPacket() the ghost is
+      // created and then Projectile::unpackUpdate() is called prior to the
+      // projectile being registered.  Within unpackUpdate() the explosion
+      // is triggered, but without being registered onAdd() isn't called and
+      // the container is not set.  As all we're doing is checking if the
+      // given explosion point is within water, we should be able to use the
+      // global container here.  We could likely always get away with this,
+      // but using the actual defined container when possible is the right
+      // thing to do.  DAW
+      AssertFatal(isClientObject(), "Server projectile has not been properly added");
+      gClientContainer.findObjects( boundsBox, WaterObjectType, findRouter, &info );
+   }
+
+   return ( info.waterCoverage > 0.0f );
+}
+
+//----------------------------------------------------------------------------
+
+void afxMagicMissile::emitParticles(const Point3F& from, const Point3F& to, const Point3F& vel, const U32 ms)
+{
+  /* From stock Projectile code...
+   if ( isHidden() )
+      return;
+  */
+
+   Point3F axis = -vel;
+
+   if( axis.isZero() )
+      axis.set( 0.0, 0.0, 1.0 );
+   else
+      axis.normalize();
+
+   bool fromWater = pointInWater(from);
+   bool toWater   = pointInWater(to);
+
+   if (!fromWater && !toWater && bool(mParticleEmitter))                                        // not in water
+      mParticleEmitter->emitParticles(from, to, axis, vel, ms);
+   else if (fromWater && toWater && bool(mParticleWaterEmitter))                                // in water
+      mParticleWaterEmitter->emitParticles(from, to, axis, vel, ms);
+   else if (!fromWater && toWater)     // entering water
+   {
+      // cast the ray to get the surface point of the water
+      RayInfo rInfo;
+      if (gClientContainer.castRay(from, to, WaterObjectType, &rInfo))
+      {
+         create_splash(rInfo.point);
+
+         // create an emitter for the particles out of water and the particles in water
+         if (mParticleEmitter)
+            mParticleEmitter->emitParticles(from, rInfo.point, axis, vel, ms);
+
+         if (mParticleWaterEmitter)
+            mParticleWaterEmitter->emitParticles(rInfo.point, to, axis, vel, ms);
+      }
+   }
+   else if (fromWater && !toWater)     // leaving water
+   {
+      // cast the ray in the opposite direction since that point is out of the water, otherwise
+      //  we hit water immediately and wont get the appropriate surface point
+      RayInfo rInfo;
+      if (gClientContainer.castRay(to, from, WaterObjectType, &rInfo))
+      {
+      create_splash(rInfo.point);
+
+         // create an emitter for the particles out of water and the particles in water
+         if (mParticleEmitter)
+            mParticleEmitter->emitParticles(rInfo.point, to, axis, vel, ms);
+
+         if (mParticleWaterEmitter)
+            mParticleWaterEmitter->emitParticles(from, rInfo.point, axis, vel, ms);
+      }
+   }
+}
+
+void afxMagicMissile::processTick(const Move* move)
+{
+   Parent::processTick( move );
+   
+   // only active from launch to impact
+   if (!is_active())
+      return;
+
+   mCurrTick++;
+
+   // missile fizzles out by exceeding lifetime
+   if ((isServerObject() || client_only) && mCurrTick >= mDataBlock->lifetime)
+   {
+      did_impact = true;
+      setMaskBits(ImpactMask);
+      if (choreographer)
+      {
+         Point3F n = mCurrVelocity; n.normalizeSafe();
+         choreographer->impactNotify(mCurrPosition, n, 0);
+      }
+      Sim::postEvent(this, new ObjectDeleteEvent, Sim::getCurrentTime() + 500);
+      return;
+   }
+
+   static F32 dT = F32(TickMs)*0.001f;
+
+   Point3F old_pos = mCurrPosition;
+
+   // adjust missile velocity from gravity and drag influences
+   if (mDataBlock->isBallistic)
+   {
+      F32 dV = (1 - mDataBlock->ballisticCoefficient)*dT;
+      Point3F d(mCurrVelocity.x*dV, mCurrVelocity.y*dV, 9.81f*mDataBlock->gravityMod*dT);
+      mCurrVelocity -= d;
+   }
+
+   // adjust missile velocity from acceleration
+   if (use_accel)
+   {
+      if (mCurrTick > mDataBlock->accelDelay && 
+         mCurrTick <= mDataBlock->accelDelay + mDataBlock->accelLifetime)
+      {
+         Point3F d = mCurrVelocity; d.normalizeSafe();
+         mCurrVelocity += d*mDataBlock->acceleration*dT;
+      }
+   }
+
+   // adjust mCurrVelocity from guidance system influences
+   if (mDataBlock->isGuided && missile_target && mCurrTick > mDataBlock->trackDelay) 
+   {
+      // get the position tracked by the guidance system
+      Point3F target_pos = missile_target->getBoxCenter(); 
+
+      Point3F target_vec = target_pos - mCurrPosition;
+
+      F32 target_dist_sq = target_vec.lenSquared();
+      if (target_dist_sq < 4.0f)
+         prec_inc += 1.0f;
+
+      // hover
+      if (mDataBlock->hover_altitude > 0.0f)
+      {
+         Point3F target_vec_xy(target_vec.x, target_vec.y, 0);
+         F32 xy_dist = target_vec_xy.len();
+
+         if (xy_dist > mDataBlock->hover_attack_distance)
+         {          
+            hover_attack_go = false;
+
+            if (xy_dist > mDataBlock->hover_attack_distance + mDataBlock->hover_attack_gradient)
+            {
+               target_pos.z += mDataBlock->hover_altitude;          
+            }
+            else
+            {
+               target_pos.z += afxEase::eq( (xy_dist-mDataBlock->hover_attack_distance)/mDataBlock->hover_attack_gradient, 
+                  0.0f, mDataBlock->hover_altitude, 
+                  0.25f, 0.75f);
+            }          			
+            target_vec = target_pos - mCurrPosition;
+         }
+
+         else
+         {
+            if (!hover_attack_go) 
+            {
+               hover_attack_go = true;
+               hover_attack_tick = 0;
+            }
+            hover_attack_tick++;
+
+            if (hover_attack_tick < mDataBlock->hover_time)
+            {
+               target_pos.z += mDataBlock->hover_altitude;
+               target_vec = target_pos - mCurrPosition;
+            }
+         }
+      }
+
+      // apply precision 
+
+      // extract speed
+      F32 speed = mCurrVelocity.len(); 
+
+      // normalize vectors
+      target_vec.normalizeSafe();
+      mCurrVelocity.normalize();
+
+      F32 prec = mDataBlock->precision;
+
+      // fade in precision gradually to avoid sudden turn
+      if (mCurrTick < mDataBlock->trackDelay + 16)
+         prec *= (mCurrTick - mDataBlock->trackDelay)/16.0f;
+
+      prec += prec_inc;
+      if (prec > 100)
+         prec = 100;
+
+      // apply precision weighting
+      target_vec *= prec;
+      mCurrVelocity *= (100 - prec);
+
+      mCurrVelocity += target_vec;
+      mCurrVelocity.normalize();
+      mCurrVelocity *= speed;
+   } 
+
+   // wiggle
+   for (U32 i = 0; i < mDataBlock->wiggle_num_axis; i++)
+   {
+      if (i >= mDataBlock->wiggle_magnitudes.size() || i >= mDataBlock->wiggle_speeds.size()) 
+         break;
+
+      F32 wiggle_mag   = mDataBlock->wiggle_magnitudes[i];
+      F32 wiggle_speed = mDataBlock->wiggle_speeds[i];
+      Point3F wiggle_axis = mDataBlock->wiggle_axis[i];
+      //wiggle_axis.normalizeSafe(); // sufficient????
+
+      F32 theta = wiggle_mag * mSin(wiggle_speed*(mCurrTick*TickSec));
+      //Con::printf( "theta: %f", theta );    
+      AngAxisF thetaRot(wiggle_axis, theta);
+      MatrixF temp(true);
+      thetaRot.setMatrix(&temp);
+      temp.mulP(mCurrVelocity);
+   }
+
+   Point3F new_pos = old_pos + mCurrVelocity*dT;
+
+   // conform to terrain
+   if (mDataBlock->followTerrain && mCurrTick >= mDataBlock->followTerrainAdjustDelay) 
+   {
+      U32 mask = TerrainObjectType | TerrainLikeObjectType; //  | InteriorObjectType;
+
+      F32 ht = mDataBlock->followTerrainHeight;
+      F32 ht_rate = mDataBlock->followTerrainAdjustRate;
+      F32 ht_min = 0.05f;
+      if (ht < ht_min)
+         ht = ht_min;
+
+      SceneContainer* container = (isServerObject()) ? &gServerContainer : &gClientContainer;
+      Point3F above_pos = new_pos; above_pos.z += 10000;
+      Point3F below_pos = new_pos; below_pos.z -= 10000;
+      RayInfo rInfo;
+      if (container && container->castRay(above_pos, below_pos, mask, &rInfo)) 
+      {
+         F32 terrain_z = rInfo.point.z;
+         F32 seek_z = terrain_z + ht;
+         if (new_pos.z < seek_z)
+         {
+            new_pos.z += ht_rate*dT;
+            if (new_pos.z > seek_z)
+               new_pos.z = seek_z;
+         }
+         else if (new_pos.z > seek_z)
+         {
+            new_pos.z -= ht_rate*dT;
+            if (new_pos.z < seek_z)
+               new_pos.z = seek_z;
+         }
+
+         if (new_pos.z < terrain_z + ht_min)
+            new_pos.z = terrain_z + ht_min;
+      }
+   }
+
+   // only check for impacts on server
+   if (isServerObject())
+   {
+      // avoid collision with the spellcaster
+      if (collide_exempt && mCurrTick <= mDataBlock->caster_safety_time)
+         collide_exempt->disableCollision();
+
+      // check for collision along ray from old to new position
+      RayInfo rInfo;
+      bool did_hit = false;
+
+      if (mPhysicsWorld)
+      {
+         // did_hit = mPhysicsWorld->castRay(old_pos, new_pos, &rInfo, Point3F(new_pos - old_pos) * mDataBlock->impactForce );
+         // Impulse currently hardcoded for testing purposes
+         did_hit = mPhysicsWorld->castRay(old_pos, new_pos, &rInfo, Point3F(new_pos - old_pos) * 1000.0f );
+      }
+      else
+      {
+         did_hit = getContainer()->castRay(old_pos, new_pos, collision_mask, &rInfo);
+      }
+
+      // restore collisions on spellcaster 
+      if (collide_exempt && mCurrTick <= mDataBlock->caster_safety_time)
+         collide_exempt->enableCollision();
+
+      // process impact
+      if (did_hit)
+      {
+         MatrixF xform(true);
+         xform.setColumn(3, rInfo.point);
+         setTransform(xform);
+         mCurrPosition = rInfo.point;
+         mCurrVelocity = Point3F(0, 0, 0);
+         did_impact = true;
+         setMaskBits(ImpactMask);
+         if (choreographer)
+         {
+            choreographer->impactNotify(rInfo.point, rInfo.normal, rInfo.object);
+            Sim::postEvent(this, new ObjectDeleteEvent, Sim::getCurrentTime() + 500);
+         }
+      }
+   }
+   else // if (isClientObject())
+   {
+      emitParticles(mCurrPosition, new_pos, mCurrVelocity, TickMs);
+      updateSound();
+   }
+
+   // interp values used in interpolateTick()
+   mCurrDeltaBase = new_pos;
+   mCurrBackDelta = mCurrPosition - new_pos;
+
+   mCurrPosition = new_pos;
+
+   MatrixF xform(true);
+   xform.setColumn(3, mCurrPosition);
+   setTransform(xform);
+}
+
+void afxMagicMissile::interpolateTick(F32 delta)
+{
+   Parent::interpolateTick(delta);
+
+   // only active from launch to impact
+   if (!is_active())
+      return;
+
+   Point3F interpPos = mCurrDeltaBase + mCurrBackDelta * delta;
+   Point3F dir = mCurrVelocity;
+   if(dir.isZero())
+      dir.set(0,0,1);
+   else
+      dir.normalize();
+
+   MatrixF xform(true);
+	xform = MathUtils::createOrientFromDir(dir);
+   xform.setPosition(interpPos);
+   setRenderTransform(xform);
+
+   updateSound();
+}
+
+//--------------------------------------------------------------------------
+U32 afxMagicMissile::packUpdate(NetConnection* con, U32 mask, BitStream* stream)
+{
+   U32 retMask = Parent::packUpdate( con, mask, stream );
+
+   const bool isInitalUpdate = mask & GameBase::InitialUpdateMask;
+
+   // InitialUpdateMask
+   if ( stream->writeFlag( isInitalUpdate ) )
+   {
+      Point3F pos;
+      getTransform().getColumn( 3, &pos );
+      stream->writeCompressedPoint( pos );
+      F32 len = mCurrVelocity.len();
+
+      if ( stream->writeFlag( len > 0.02 ) )
+      {
+         Point3F outVel = mCurrVelocity;
+         outVel *= 1 / len;
+         stream->writeNormalVector( outVel, 10 );
+         len *= 32.0; // 5 bits for fraction
+
+         if ( len > 8191 )
+             len = 8191;
+
+         stream->writeInt( (S32)len, 13 );
+      }
+
+      stream->writeRangedU32( mCurrTick, 0, afxMagicMissileData::MaxLifetimeTicks );
+
+      if (choreographer)
+      {
+         S32 ghostIndex = con->getGhostIndex( choreographer );
+         if ( stream->writeFlag( ghostIndex != -1 ) )
+         {
+            stream->writeRangedU32( U32(ghostIndex), 
+                                    0, 
+                                    NetConnection::MaxGhostCount );
+         }
+         else 
+            // have not recieved the ghost for the source object yet, try again later
+            retMask |= GameBase::InitialUpdateMask;
+      }
+      else
+         stream->writeFlag( false );
+   }
+
+   // impact update
+   if (stream->writeFlag(mask & ImpactMask))
+   {
+      mathWrite(*stream, mCurrPosition);
+      mathWrite(*stream, mCurrVelocity);
+      stream->writeFlag(did_impact);
+   }
+
+   // guided update
+   if (stream->writeFlag(mask & GuideMask))
+   {
+      mathWrite(*stream, mCurrPosition);
+      mathWrite(*stream, mCurrVelocity);
+   }
+
+   return retMask;
+}
+
+void afxMagicMissile::unpackUpdate(NetConnection* con, BitStream* stream)
+{
+   Parent::unpackUpdate(con, stream);
+   
+   if ( stream->readFlag() ) // InitialUpdateMask
+   {
+      Point3F pos;
+      stream->readCompressedPoint( &pos );
+      if ( stream->readFlag() )
+      {
+         stream->readNormalVector( &mCurrVelocity, 10 );
+         mCurrVelocity *= stream->readInt( 13 ) / 32.0f;
+      }
+      else
+         mCurrVelocity.zero();
+
+      mCurrDeltaBase = pos;
+      mCurrBackDelta = mCurrPosition - pos;
+      mCurrPosition  = pos;
+      setPosition( mCurrPosition );
+
+      mCurrTick = stream->readRangedU32(0, afxMagicMissileData::MaxLifetimeTicks);
+      if ( stream->readFlag() )
+      {
+         U32 id   = stream->readRangedU32(0, NetConnection::MaxGhostCount);
+         choreographer = dynamic_cast<afxChoreographer*>(con->resolveGhost(id));
+         if (choreographer)
+         {
+            deleteNotify(choreographer);
+         }
+      }
+      else
+      {
+         if (choreographer)
+            clearNotify(choreographer);
+         choreographer = 0;
+      }
+   }
+   
+   // impact update
+   if (stream->readFlag())
+   {
+      mathRead(*stream, &mCurrPosition);
+      mathRead(*stream, &mCurrVelocity);
+      did_impact = stream->readFlag();
+
+   }
+
+   if (stream->readFlag()) // guided update
+   {
+      mathRead( *stream, &mCurrPosition );
+      mathRead( *stream, &mCurrVelocity );
+   }
+}
+
+//--------------------------------------------------------------------------
+void afxMagicMissile::prepRenderImage(SceneRenderState* state)
+{
+   if (!is_active())
+      return;
+
+   /*
+   if (isHidden() || mFadeValue <= (1.0/255.0))
+      return;
+   */
+
+   if ( mDataBlock->lightDesc )
+   {
+     mDataBlock->lightDesc->prepRender( state, &mLightState, getRenderTransform() );
+   }
+
+   /*
+   if ( mFlareData )
+   {
+     mFlareState.fullBrightness = mDataBlock->lightDesc->mBrightness;
+     mFlareState.scale = mFlareScale;
+     mFlareState.lightInfo = mLight;
+     mFlareState.lightMat = getTransform();
+
+     mFlareData->prepRender( state, &mFlareState );
+   }
+   */
+
+   prepBatchRender( state );
+}
+
+void afxMagicMissile::prepBatchRender(SceneRenderState* state)
+{
+   if ( !mProjectileShape )
+      return;
+
+   GFXTransformSaver saver;
+
+   // Set up our TS render state.
+   TSRenderState rdata;
+   rdata.setSceneState( state );
+
+   // We might have some forward lit materials
+   // so pass down a query to gather lights.
+   LightQuery query;
+   query.init( getWorldSphere() );
+   rdata.setLightQuery( &query );
+
+   MatrixF mat = getRenderTransform();
+   mat.scale( mObjScale );
+   mat.scale( mDataBlock->scale );
+   GFX->setWorldMatrix( mat );
+
+   mProjectileShape->setDetailFromPosAndScale( state, mat.getPosition(), mObjScale );
+   mProjectileShape->animate();
+
+   mProjectileShape->render( rdata );
+}
+
+void afxMagicMissile::onDeleteNotify(SimObject* obj)
+{
+  ShapeBase* shape_test = dynamic_cast<ShapeBase*>(obj);
+  if (shape_test == collide_exempt)
+  {
+    collide_exempt = NULL;
+    Parent::onDeleteNotify(obj);
+    return;
+  }
+
+  SceneObject* target_test = dynamic_cast<SceneObject*>(obj);
+  if (target_test == missile_target)
+  {
+    missile_target = NULL;
+    Parent::onDeleteNotify(obj);
+    return;
+  }
+
+  afxChoreographer* ch = dynamic_cast<afxChoreographer*>(obj);
+  if (ch == choreographer)
+  {
+    choreographer = NULL;
+    Parent::onDeleteNotify(obj);
+    return;
+  }
+
+  Parent::onDeleteNotify(obj);
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// private:
+
+void afxMagicMissile::create_splash(const Point3F& pos)
+{
+  if (!mDataBlock || !mDataBlock->splash)
+    return;
+
+  MatrixF xfm = getTransform();
+  xfm.setPosition(pos);
+
+  Splash* splash = new Splash();
+  splash->onNewDataBlock(mDataBlock->splash, false);
+  splash->setTransform(xfm);
+  splash->setInitialState(xfm.getPosition(), Point3F(0.0, 0.0, 1.0));
+  if (!splash->registerObject())
+  {
+    delete splash;
+    splash = NULL;
+  }
+}
+
+void afxMagicMissile::get_launch_constraint_data(Point3F& pos, Point3F& vel)
+{
+  // need a choreographer
+  if (!choreographer)
+  {
+    Con::errorf("afxMagicMissile::get_launch_constraint_data(): missing choreographer.");
+    return;
+  }
+
+  // need a constraint manager
+  afxConstraintMgr* cons_mgr = choreographer->getConstraintMgr();
+  if (!cons_mgr)
+  {
+    Con::errorf("afxMagicMissile::get_launch_constraint_data(): missing constriant manager.");
+    return;
+  }
+
+  // need a valid constraint
+  afxConstraintID launch_cons_id;
+  if (isServerObject())
+    launch_cons_id = cons_mgr->getConstraintId(mDataBlock->launch_cons_s_def);
+  else
+    launch_cons_id = cons_mgr->getConstraintId(mDataBlock->launch_cons_c_def);
+
+  afxConstraint* launch_cons = cons_mgr->getConstraint(launch_cons_id);
+  if (!launch_cons)
+  {
+    Con::errorf("afxMagicMissile::get_launch_constraint_data(): constraint undefined.");
+    return;
+  }
+
+  MatrixF launch_xfm;
+  launch_cons->getTransform(launch_xfm);
+
+  Point3F launch_pos;
+  launch_cons->getPosition(launch_pos);
+
+  pos = launch_pos;
+
+  // echo the actual launch position to the console
+  if (mDataBlock->echo_launch_offset)
+  {
+    SceneObject* default_launcher = get_default_launcher();
+    if (default_launcher)
+    {
+      MatrixF launcher_xfm_inv = default_launcher->getWorldTransform();
+      VectorF offset = pos - default_launcher->getRenderPosition();
+      launcher_xfm_inv.mulV(offset);
+      if (isServerObject())
+        Con::printf("launchOffsetServer = \"%g %g %g\";", offset.x, offset.y, offset.z);
+      else
+        Con::printf("launchOffsetClient = \"%g %g %g\";", offset.x, offset.y, offset.z);
+    }
+  }
+
+  // setup aiming matrix to straight forward and level
+  MatrixF aim_mtx;
+  AngAxisF aim_aa(Point3F(0,1,0),0);
+  aim_aa.setMatrix(&aim_mtx);
+
+  // calculate final aiming vector
+  MatrixF aim2_mtx;
+  aim2_mtx.mul(launch_xfm, aim_mtx);
+
+  VectorF aim_vec;
+  aim2_mtx.getColumn(1,&aim_vec);
+  aim_vec.normalizeSafe();
+
+  // give velocity vector a magnitude
+  vel = aim_vec*mDataBlock->muzzleVelocity;
+}
+
+// resolve the launch constraint object. normally it's the caster, but for
+// reverse_targeting the target object us used.
+SceneObject* afxMagicMissile::get_default_launcher() const
+{
+  SceneObject* launch_cons_obj = 0;
+  if (mDataBlock->reverse_targeting)
+  {
+    if (dynamic_cast<afxMagicSpell*>(choreographer))
+      launch_cons_obj = ((afxMagicSpell*)choreographer)->target;
+    if (!launch_cons_obj)
+    {
+      Con::errorf("afxMagicMissile::get_launch_data(): missing target constraint object for reverse targeted missile.");
+      return 0;
+    }
+  }
+  else
+  {
+    if (dynamic_cast<afxMagicSpell*>(choreographer))
+      launch_cons_obj = ((afxMagicSpell*)choreographer)->caster;
+    if (!launch_cons_obj)
+    {
+      Con::errorf("afxMagicMissile::get_launch_data(): missing launch constraint object missile.");
+      return 0;
+    }
+  }
+
+  return launch_cons_obj;
+}
+
+void afxMagicMissile::get_launch_data(Point3F& pos, Point3F& vel)
+{
+  bool use_constraint = (isServerObject()) ? mDataBlock->launch_cons_s_def.isDefined() : mDataBlock->launch_cons_c_def.isDefined();
+  if (use_constraint)
+  {
+    get_launch_constraint_data(pos, vel);
+    return;
+  }  
+  
+  // a choreographer pointer is required
+  if (!choreographer)
+  {
+    Con::errorf("afxMagicMissile::get_launch_data(): missing choreographer.");
+    return;
+  }
+
+  SceneObject* launch_cons_obj = get_default_launcher();
+  if (!launch_cons_obj)
+    return;
+
+  MatrixF launch_xfm = launch_cons_obj->getRenderTransform();
+
+  // calculate launch position
+  Point3F offset_override = (isClientObject()) ?  mDataBlock->launch_offset_client : 
+                                                  mDataBlock->launch_offset_server;
+  // override
+  if (!offset_override.isZero())
+  {
+    launch_xfm.mulV(offset_override);
+    pos = launch_cons_obj->getRenderPosition() + offset_override;
+  }
+  // no override 
+  else
+  {
+    // get transformed launch offset
+    VectorF launch_offset = mDataBlock->launch_offset;
+    launch_xfm.mulV(launch_offset);
+    
+    StringTableEntry launch_node = mDataBlock->launch_node;
+    
+    // calculate position of missile at launch
+    if (launch_node != ST_NULLSTRING)
+    {
+      ShapeBase* launch_cons_shape = dynamic_cast<ShapeBase*>(launch_cons_obj);
+      TSShapeInstance* shape_inst = (launch_cons_shape) ? launch_cons_shape->getShapeInstance() : 0;
+      if (!shape_inst || !shape_inst->getShape())
+        launch_node = ST_NULLSTRING;
+      else
+      {
+        S32 node_ID = shape_inst->getShape()->findNode(launch_node);
+        MatrixF node_xfm = launch_cons_obj->getRenderTransform();
+        node_xfm.scale(launch_cons_obj->getScale());
+        if (node_ID >= 0)
+          node_xfm.mul(shape_inst->mNodeTransforms[node_ID]);
+        
+        VectorF node_offset = mDataBlock->launch_node_offset;
+        node_xfm.mulV(node_offset);
+        
+        pos = node_xfm.getPosition() + launch_offset + node_offset;
+      }
+    }   
+    // calculate launch position without launch node
+    else
+      pos = launch_cons_obj->getRenderPosition() + launch_offset;
+  }
+
+  // echo the actual launch position to the console
+  if (mDataBlock->echo_launch_offset)
+  {
+    VectorF offset = pos - launch_cons_obj->getRenderPosition();
+    MatrixF caster_xfm_inv = launch_xfm;
+    caster_xfm_inv.affineInverse();
+    caster_xfm_inv.mulV(offset);
+    if (isServerObject())
+      Con::printf("launchOffsetServer = \"%g %g %g\";", offset.x, offset.y, offset.z);
+    else
+      Con::printf("launchOffsetClient = \"%g %g %g\";", offset.x, offset.y, offset.z);
+  }
+
+  // calculate launch velocity vector
+  if (starting_vel_vec.isZero())
+  {
+    // setup aiming matrix to straight forward and level
+    MatrixF aim_mtx;
+    AngAxisF aim_aa(Point3F(0,1,0),0);
+    aim_aa.setMatrix(&aim_mtx);
+    
+    // setup pitch matrix
+    MatrixF pitch_mtx;
+    AngAxisF pitch_aa(Point3F(1,0,0),mDegToRad(mDataBlock->launch_pitch));
+    pitch_aa.setMatrix(&pitch_mtx);
+    
+    // setup pan matrix
+    MatrixF pan_mtx;
+    AngAxisF pan_aa(Point3F(0,0,1),mDegToRad(mDataBlock->launch_pan));
+    pan_aa.setMatrix(&pan_mtx);
+    
+    // calculate adjusted aiming matrix
+    aim_mtx.mul(pitch_mtx);
+    aim_mtx.mul(pan_mtx);
+    
+    // calculate final aiming vector
+    MatrixF aim2_mtx;
+    aim2_mtx.mul(launch_xfm, aim_mtx);
+    VectorF aim_vec;
+    aim2_mtx.getColumn(1,&aim_vec);
+    aim_vec.normalizeSafe();
+    
+    // give velocity vector a magnitude
+    vel = aim_vec*mDataBlock->muzzleVelocity;
+  }
+  else
+  {
+    vel = starting_vel_vec*starting_velocity;
+  }
+}
+
+void afxMagicMissile::updateSound()
+{
+  if (!mDataBlock->sound)
+    return;
+
+  if ( mSound )
+  {
+    if ( !mSound->isPlaying() )
+      mSound->play();
+
+    mSound->setVelocity( getVelocity() );
+    mSound->setTransform( getRenderTransform() );
+  }
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// public:
+
+void afxMagicMissile::launch()
+{
+  get_launch_data(mCurrPosition, mCurrVelocity);
+
+  mCurrDeltaBase = mCurrPosition;
+  mCurrBackDelta.zero();
+
+  did_launch = true;
+
+  setPosition(mCurrPosition);
+
+  afxMagicSpell* spell = dynamic_cast<afxMagicSpell*>(choreographer);
+  if (spell)
+  {
+    if (mDataBlock->reverse_targeting)
+    {
+      missile_target = spell->caster;
+      collide_exempt = spell->target;
+    }
+    else
+    {
+      missile_target = spell->target;
+      collide_exempt = spell->caster;
+    }
+
+    if (spell->caster)
+      processAfter(spell->caster);
+    if (missile_target)
+      deleteNotify(missile_target);
+    if (collide_exempt)
+      deleteNotify(collide_exempt);
+  }
+  else
+  {
+    missile_target = 0;
+    collide_exempt = 0;
+  }
+}
+
+void afxMagicMissile::setChoreographer(afxChoreographer* chor)
+{
+  if (choreographer)
+    clearNotify(choreographer);
+  choreographer = chor;
+  if (choreographer)
+    deleteNotify(choreographer);
+}
+
+void afxMagicMissile::setStartingVelocityVector(const Point3F& vel_vec)
+{
+  starting_vel_vec = vel_vec;
+}
+
+void afxMagicMissile::setStartingVelocity(const F32 vel)
+{
+  starting_velocity = vel;
+}
+
+void afxMagicMissile::getStartingVelocityValues(F32& vel, Point3F& vel_vec)
+{
+  vel = starting_velocity;
+  vel_vec = starting_vel_vec;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+/* From stock Projectile code...
+DefineEngineMethod(Projectile, presimulate, void, (F32 seconds), (1.0f), "Updates velocity and position, and performs collision testing.\n"
+													"@param seconds Amount of time, in seconds, since the simulation began, to start the simulation at.\n"
+													"@tsexample\n"
+														"// Tell the projectile object to process a simulation event, and provide the amount of time\n"
+														"   in seconds that has passed since the simulation began.\n"
+														"%seconds = 2000;\n"
+														"%projectile.presimulate(%seconds);\n"
+													"@endtsexample\n")
+{
+	object->simulate( seconds );
+}
+*/
+
+DefineEngineMethod(afxMagicMissile, setStartingVelocityVector, void, (Point3F velocityVec),,
+                   "Set the starting velocity-vector for a magic-missile.\n\n"
+                   "@ingroup AFX")
+{
+  object->setStartingVelocityVector(velocityVec);
+}
+
+DefineEngineMethod(afxMagicMissile, setStartingVelocity, void, (float velocity),,
+                   "Set the starting velocity for a magic-missile.\n\n"
+                   "@ingroup AFX")
+{
+  object->setStartingVelocity(velocity);
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//

+ 435 - 0
Engine/source/afx/afxMagicMissile.h

@@ -0,0 +1,435 @@
+//-----------------------------------------------------------------------------
+// Copyright (c) 2012 GarageGames, LLC
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//-----------------------------------------------------------------------------
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
+// Copyright (C) 2015 Faust Logic, Inc.
+//
+// afxMagicMissile is a heavily modified variation of the stock Projectile class. In 
+// addition to numerous AFX customizations, it also incorporates functionality based on
+// the following TGE resources:
+//
+// Guided or Seeker Projectiles by Derk Adams
+//   http://www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=6778
+//
+// Projectile Ballistic Coefficients (drag factors) by Mark Owen
+//   http://www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=5128
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _AFX_MAGIC_MISSILE_H_
+#define _AFX_MAGIC_MISSILE_H_
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#include "T3D/lightDescription.h"
+#include "T3D/fx/particleEmitter.h"
+
+#include "afx/afxConstraint.h"
+
+class SplashData;
+class ShapeBase;
+class TSShapeInstance;
+class PhysicsWorld;
+class SFXTrack;
+class SFXSource;
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxMagicMissileData
+
+class afxMagicMissileData : public GameBaseData
+{
+  typedef GameBaseData Parent;
+  
+protected:
+  bool onAdd();
+
+public:
+  enum { MaxLifetimeTicks = 4095 };
+  
+public:
+   // variables set in datablock definition:
+   // Shape related
+  StringTableEntry      projectileShapeName;
+
+  //bool                  hasLight;
+  //F32                   lightRadius;
+  //LinearColorF                lightColor;
+
+  //bool                  hasWaterLight;
+  //LinearColorF                waterLightColor;
+
+  /*
+  /// Set to true if it is a billboard and want it to always face the viewer, false otherwise
+  bool faceViewer;
+  */
+  Point3F               scale;
+
+  /*
+  /// [0,1] scale of how much velocity should be inherited from the parent object
+  F32 velInheritFactor;
+  /// Speed of the projectile when fired
+  */
+  F32                   muzzleVelocity;
+
+   /// Should it arc?
+  bool                  isBallistic;
+
+  /*
+  /// How HIGH should it bounce (parallel to normal), [0,1]
+  F32 bounceElasticity;
+  /// How much momentum should be lost when it bounces (perpendicular to normal), [0,1]
+  F32 bounceFriction;
+  */
+
+   /// Should this projectile fall/rise different than a default object?
+  F32                   gravityMod;
+
+   /// How long the projectile should exist before deleting itself
+  U32                   lifetime;     // ticks
+  /*
+  /// How long it should not detonate on impact
+  S32 armingDelay;  // the values are converted on initialization with
+  */
+  S32                   fadeDelay;    // ticks
+
+  /*
+  ExplosionData* explosion;           // Explosion Datablock
+  S32 explosionId;                    // Explosion ID
+  ExplosionData* waterExplosion;      // Water Explosion Datablock
+  S32 waterExplosionId;               // Water Explosion ID
+  */
+
+  SplashData* splash;                 // Water Splash Datablock
+  S32 splashId;                       // Water splash ID
+
+  SFXTrack* sound;                    // Projectile Sound
+
+  LightDescription *lightDesc;
+  S32 lightDescId;   
+
+  /*
+  enum DecalConstants {               // Number of decals constant
+    NumDecals = 6,
+  };
+  DecalData* decals[NumDecals];       // Decal Datablocks
+  S32 decalId[NumDecals];             // Decal IDs
+  U32 decalCount;                     // # of loaded Decal Datablocks
+  */
+
+   // variables set on preload:
+  Resource<TSShape>     projectileShape;
+  /*
+  S32 activateSeq;
+  S32 maintainSeq;
+  */
+
+  ParticleEmitterData*  particleEmitter;
+  S32                   particleEmitterId;
+  ParticleEmitterData*  particleWaterEmitter;
+  S32                   particleWaterEmitterId;
+
+  U32                   collision_mask;
+
+  Point3F               starting_vel_vec;
+
+                        // guidance behavior
+  bool                  isGuided;
+  F32                   precision;
+  S32                   trackDelay;
+
+                        // simple physics
+  F32                   ballisticCoefficient;
+
+                        // terrain following
+  bool                  followTerrain;
+  F32                   followTerrainHeight;
+  F32                   followTerrainAdjustRate;
+  S32                   followTerrainAdjustDelay;
+
+  F32                   acceleration;
+  S32                   accelDelay;
+  U32                   accelLifetime;
+
+  StringTableEntry      launch_node;
+  Point3F               launch_offset;
+  Point3F               launch_offset_server;
+  Point3F               launch_offset_client;
+  Point3F               launch_node_offset;
+  F32                   launch_pitch;
+  F32                   launch_pan;
+  bool                  echo_launch_offset;
+
+  StringTableEntry      launch_cons_s_spec;
+  afxConstraintDef      launch_cons_s_def;
+  StringTableEntry      launch_cons_c_spec;
+  afxConstraintDef      launch_cons_c_def;
+
+                        // wiggle behavior
+  Vector<F32>           wiggle_magnitudes;
+  Vector<F32>           wiggle_speeds;
+  StringTableEntry      wiggle_axis_string;
+  Point3F*              wiggle_axis;
+  U32                   wiggle_num_axis;
+
+                        // hover behavior
+  F32                   hover_altitude;
+  F32                   hover_attack_distance;
+  F32                   hover_attack_gradient;
+  U32                   hover_time;
+
+  bool                  reverse_targeting;
+
+  U32                   caster_safety_time;
+
+public:
+  /*C*/                 afxMagicMissileData();
+  /*D*/                 ~afxMagicMissileData();
+  
+  void                  packData(BitStream*);
+  void                  unpackData(BitStream*);
+
+  bool                  preload(bool server, String &errorStr);
+  
+  static void           initPersistFields();
+  
+  DECLARE_CONOBJECT(afxMagicMissileData);
+  DECLARE_CATEGORY("AFX");
+
+public:
+  /*C*/                 afxMagicMissileData(const afxMagicMissileData&, bool = false);
+
+  afxMagicMissileData*  cloneAndPerformSubstitutions(const SimObject*, S32 index=0);
+  virtual bool          allowSubstitutions() const { return true; }
+  void                  gather_cons_defs(Vector<afxConstraintDef>& defs);
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxMagicMissile
+
+//class afxMagicSpell;
+class afxChoreographer;
+
+class afxMagicMissile : public GameBase, public ISceneLight
+{
+  typedef GameBase Parent;
+
+public:
+  /*
+  // Initial conditions
+  enum ProjectileConstants {
+    SourceIdTimeoutTicks = 7,   // = 231 ms
+    DeleteWaitTime       = 500, ///< 500 ms delete timeout (for network transmission delays)
+    ExcessVelDirBits     = 7,
+    MaxLivingTicks       = 4095,
+  };
+  */
+  enum UpdateMasks {
+    /*
+    BounceMask    = Parent::NextFreeMask,
+    ExplosionMask = Parent::NextFreeMask << 1,
+    */
+    GuideMask     = Parent::NextFreeMask << 0,
+    LaunchMask    = Parent::NextFreeMask << 1,
+    ImpactMask    = Parent::NextFreeMask << 2,
+    NextFreeMask  = Parent::NextFreeMask << 3
+  }; 
+protected:
+  PhysicsWorld *mPhysicsWorld;
+
+  afxMagicMissileData* mDataBlock;
+
+  ParticleEmitter*  mParticleEmitter;
+  ParticleEmitter*  mParticleWaterEmitter;
+  SFXSource* mSound;
+
+  Point3F  mCurrPosition;
+  Point3F  mCurrVelocity;
+  /*
+  S32      mSourceObjectId;
+  S32      mSourceObjectSlot;
+  */
+
+   // Time related variables common to all projectiles, managed by processTick
+
+  U32 mCurrTick;                         ///< Current time in ticks
+  /*
+  SimObjectPtr<ShapeBase> mSourceObject; ///< Actual pointer to the source object, times out after SourceIdTimeoutTicks
+  */
+
+   // Rendering related variables
+  TSShapeInstance* mProjectileShape;
+  /*
+  TSThread*        mActivateThread;
+  TSThread*        mMaintainThread;
+
+  Point3F          mLastRenderPos;
+  */
+
+  // ISceneLight
+  virtual void submitLights( LightManager *lm, bool staticLighting );
+  virtual LightInfo* getLight() { return mLight; }
+
+  LightInfo *mLight;
+  LightState mLightState;
+
+  /*
+  bool             mHidden;        ///< set by the derived class, if true, projectile doesn't render
+  F32              mFadeValue;     ///< set in processTick, interpolation between fadeDelay and lifetime
+                                 ///< in data block
+  */
+
+  /*
+  // Warping and back delta variables.  Only valid on the client
+  //
+  Point3F mWarpStart;
+  Point3F mWarpEnd;
+  U32     mWarpTicksRemaining;
+  */
+
+  Point3F mCurrDeltaBase;
+  Point3F mCurrBackDelta;
+
+  /*
+  Point3F mExplosionPosition;
+  Point3F mExplosionNormal;
+  U32     mCollideHitType;
+  */
+  
+  bool onAdd();
+  void onRemove();
+  bool onNewDataBlock(GameBaseData *dptr, bool reload);
+
+  // Rendering
+  virtual void prepRenderImage(SceneRenderState*);
+  void prepBatchRender( SceneRenderState *state); 
+
+  void processTick(const Move *move);
+  /*
+  void advanceTime(F32 dt);
+  */
+  void interpolateTick(F32 delta);
+
+  /*
+  /// What to do once this projectile collides with something
+  virtual void onCollision(const Point3F& p, const Point3F& n, SceneObject*);
+
+  /// What to do when this projectile explodes
+  virtual void explode(const Point3F& p, const Point3F& n, const U32 collideType );
+
+  /// Returns the velocity of the projectile
+  Point3F getVelocity() const;
+  */
+
+  void              emitParticles(const Point3F&, const Point3F&, const Point3F&, const U32);
+  void              updateSound();
+
+  // Rendering
+  /*
+  void prepModelView    ( SceneRenderState *state);
+  */
+
+   // These are stolen from the player class ..
+  bool              pointInWater(const Point3F &point);
+
+  U32  packUpdate(NetConnection *conn, U32 mask, BitStream *stream);
+  void unpackUpdate(NetConnection *conn, BitStream *stream);
+
+  afxChoreographer* choreographer;
+
+  bool              client_only;
+  bool              server_only;
+  bool              use_accel;
+  U32               collision_mask;
+  F32               prec_inc;
+
+  bool              did_launch;
+  bool              did_impact; 
+  
+  SceneObject*      missile_target;
+  SceneObject*      collide_exempt;
+
+  bool              hover_attack_go;
+  U32               hover_attack_tick;
+  
+  F32               starting_velocity;
+  Point3F           starting_vel_vec;
+
+  SimObject*        ss_object;
+  S32               ss_index;
+
+private:
+  void              init(bool on_server, bool on_client);
+  void              create_splash(const Point3F& pos);
+  SceneObject*      get_default_launcher() const;
+  void              get_launch_constraint_data(Point3F& pos, Point3F& vel);
+  void              get_launch_data(Point3F& pos, Point3F& vel);
+  bool              is_active() const { return (did_launch && !did_impact); }
+
+public:
+  /*
+  F32 getUpdatePriority(CameraScopeQuery *focusObject, U32 updateMask, S32 updateSkips);
+  */
+  /*C*/             afxMagicMissile();
+  /*C*/             afxMagicMissile(bool on_server, bool on_client);
+  /*D*/             ~afxMagicMissile();
+  virtual void      onDeleteNotify(SimObject*);
+
+  DECLARE_CONOBJECT(afxMagicMissile);
+  DECLARE_CATEGORY("AFX");
+
+  static void       initPersistFields();  
+
+  /*
+  virtual bool calculateImpact(float    simTime,
+                               Point3F& pointOfImpact,
+                               float&   impactTime);
+
+  static U32 smProjectileWarpTicks;
+
+protected:
+  static const U32 csmStaticCollisionMask;
+  static const U32 csmDynamicCollisionMask;
+  static const U32 csmDamageableMask;
+  */
+  
+  void              launch();
+  void              setChoreographer(afxChoreographer*); 
+  void              setStartingVelocityVector(const Point3F& vel_vec);
+  void              setStartingVelocity(const F32 vel);
+  void              getStartingVelocityValues(F32& vel, Point3F& vel_vec); 
+  void              setSubstitutionData(SimObject* obj, S32 idx=0) { ss_object = obj; ss_index = idx; }
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxMagicMissileCallback
+
+class afxMagicMissileCallback
+{
+public:
+  virtual void impactNotify(const Point3F& p, const Point3F& n, SceneObject*)=0;
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#endif // _AFX_MAGIC_MISSILE_H_
+

+ 2709 - 0
Engine/source/afx/afxMagicSpell.cpp

@@ -0,0 +1,2709 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
+// Copyright (C) 2015 Faust Logic, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#include "afx/arcaneFX.h"
+
+#include "console/engineAPI.h"
+#include "T3D/gameBase/gameConnection.h"
+#include "sfx/sfxSystem.h"
+#include "math/mathIO.h"
+#include "T3D/containerQuery.h"
+
+#include "afx/afxChoreographer.h"
+#include "afx/afxPhrase.h"
+#include "afx/afxMagicSpell.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxMagicSpellData::ewValidator
+//
+// When any of the effect list fields (addCastingEffect, etc.) are set, this validator
+// intercepts the value and adds it to the appropriate effects list. One validator is
+// created for each effect list and an id is used to identify which list to add the effect
+// to.
+//
+void afxMagicSpellData::ewValidator::validateType(SimObject* object, void* typePtr)
+{
+  afxMagicSpellData* spelldata = dynamic_cast<afxMagicSpellData*>(object);
+  afxEffectBaseData** ew = (afxEffectBaseData**)(typePtr);
+
+  if (spelldata && ew)
+  {
+    switch (id)
+    {
+    case CASTING_PHRASE:
+      spelldata->casting_fx_list.push_back(*ew);
+      break;
+    case LAUNCH_PHRASE:
+      spelldata->launch_fx_list.push_back(*ew);
+      break;
+    case DELIVERY_PHRASE:
+      spelldata->delivery_fx_list.push_back(*ew);
+      break;
+    case IMPACT_PHRASE:
+      spelldata->impact_fx_list.push_back(*ew);
+      break;
+    case LINGER_PHRASE:
+      spelldata->linger_fx_list.push_back(*ew);
+      break;
+    }
+    *ew = 0;
+  }
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+class SpellFinishStartupEvent : public SimEvent
+{
+public:
+  void process(SimObject* obj)
+  {
+     afxMagicSpell* spell = dynamic_cast<afxMagicSpell*>(obj);
+     if (spell)
+       spell->finish_startup();
+  }
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxMagicSpellData
+
+IMPLEMENT_CO_DATABLOCK_V1(afxMagicSpellData);
+
+ConsoleDocClass( afxMagicSpellData,
+   "@brief Defines the properties of an afxMagicSpell.\n\n"
+
+   "@ingroup afxChoreographers\n"
+   "@ingroup AFX\n"
+   "@ingroup Datablocks\n"
+);
+
+IMPLEMENT_CALLBACK( afxMagicSpellData, onDamage, void,
+   (afxMagicSpell* spell, const char* label, const char* flaver, U32 target_id, F32 amount, U8 n, Point3F pos, F32 ad_amount, F32 radius, F32 impulse),
+   (spell, label, flaver, target_id, amount, n, pos, ad_amount, radius, impulse),
+   "Called when the spell deals damage.\n"
+   "@param spell the spell object\n" );
+
+IMPLEMENT_CALLBACK( afxMagicSpellData, onDeactivate, void, (afxMagicSpell* spell), (spell),
+   "Called when the spell ends naturally.\n"
+   "@param spell the spell object\n" );
+
+IMPLEMENT_CALLBACK( afxMagicSpellData, onInterrupt, void, (afxMagicSpell* spell, ShapeBase* caster), (spell, caster),
+   "Called when the spell ends unnaturally due to an interruption.\n"
+   "@param spell the spell object\n" );
+
+IMPLEMENT_CALLBACK( afxMagicSpellData, onLaunch, void,
+   (afxMagicSpell* spell, ShapeBase* caster, SceneObject* target, afxMagicMissile* missile),
+   (spell, caster, target, missile),
+   "Called when the spell's casting stage ends and the delivery stage begins.\n"
+   "@param spell the spell object\n" );
+
+IMPLEMENT_CALLBACK( afxMagicSpellData, onImpact, void,
+   (afxMagicSpell* spell, ShapeBase* caster, SceneObject* impacted, Point3F pos, Point3F normal),
+   (spell, caster, impacted, pos, normal),
+   "Called at the spell's missile impact marking the end of the deliver stage and the start of the linger stage.\n"
+   "@param spell the spell object\n" );
+
+IMPLEMENT_CALLBACK( afxMagicSpellData, onPreactivate, bool,
+   (SimObject* param_holder, ShapeBase* caster, SceneObject* target, SimObject* extra),
+   (param_holder, caster, target, extra),
+   "Called during spell casting before spell instance is fully created.\n");
+
+IMPLEMENT_CALLBACK( afxMagicSpellData, onActivate, void,
+   (afxMagicSpell* spell, ShapeBase* caster, SceneObject* target),
+   (spell, caster, target),
+   "Called when the spell starts.\n"
+   "@param spell the spell object\n" );
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+afxMagicSpellData::afxMagicSpellData()
+{
+  casting_dur = 0.0f;
+  delivery_dur = 0.0f;
+  linger_dur = 0.0f;
+
+  n_casting_loops = 1;
+  n_delivery_loops = 1;
+  n_linger_loops = 1;
+
+  extra_casting_time = 0.0f;
+  extra_delivery_time = 0.0f;
+  extra_linger_time = 0.0f;
+
+  // interrupt flags
+  do_move_interrupts = true;
+  move_interrupt_speed = 2.0f;
+
+  // delivers projectile spells
+  missile_db = 0;
+  launch_on_server_signal = false;
+  primary_target_types = PlayerObjectType;
+
+  // dummy entry holds effect-wrapper pointer while a special validator
+  // grabs it and adds it to an appropriate effects list
+  dummy_fx_entry = NULL;
+
+  // marked true if datablock ids need to
+  // be converted into pointers
+  do_id_convert = false;
+}
+
+afxMagicSpellData::afxMagicSpellData(const afxMagicSpellData& other, bool temp_clone) : afxChoreographerData(other, temp_clone)
+{
+  casting_dur = other.casting_dur;
+  delivery_dur = other.delivery_dur;
+  linger_dur = other.linger_dur;
+  n_casting_loops = other.n_casting_loops;
+  n_delivery_loops = other.n_delivery_loops;
+  n_linger_loops = other.n_linger_loops;
+  extra_casting_time = other.extra_casting_time;
+  extra_delivery_time = other.extra_delivery_time;
+  extra_linger_time = other.extra_linger_time;
+  do_move_interrupts = other.do_move_interrupts;
+  move_interrupt_speed = other.move_interrupt_speed;
+  missile_db = other.missile_db;
+  launch_on_server_signal = other.launch_on_server_signal;
+  primary_target_types = other.primary_target_types;
+
+  dummy_fx_entry = other.dummy_fx_entry;
+  do_id_convert = other.do_id_convert;
+
+  casting_fx_list = other.casting_fx_list;
+  launch_fx_list = other.launch_fx_list;
+  delivery_fx_list = other.delivery_fx_list;
+  impact_fx_list = other.impact_fx_list;
+  linger_fx_list = other.linger_fx_list;
+}
+
+void afxMagicSpellData::reloadReset()
+{
+  casting_fx_list.clear();
+  launch_fx_list.clear();
+  delivery_fx_list.clear();
+  impact_fx_list.clear();
+  linger_fx_list.clear();
+}
+
+#define myOffset(field) Offset(field, afxMagicSpellData)
+
+void afxMagicSpellData::initPersistFields()
+{
+  static ewValidator _castingPhrase(CASTING_PHRASE);
+  static ewValidator _launchPhrase(LAUNCH_PHRASE);
+  static ewValidator _deliveryPhrase(DELIVERY_PHRASE);
+  static ewValidator _impactPhrase(IMPACT_PHRASE);
+  static ewValidator _lingerPhrase(LINGER_PHRASE);
+
+  // for each effect list, dummy_fx_entry is set and then a validator adds it to the appropriate effects list
+
+  addGroup("Casting Stage");
+  addField("castingDur",            TypeF32,        myOffset(casting_dur),
+    "...");
+  addField("numCastingLoops",       TypeS32,        myOffset(n_casting_loops),
+    "...");
+  addField("extraCastingTime",      TypeF32,        myOffset(extra_casting_time),
+    "...");
+  addFieldV("addCastingEffect", TYPEID<afxEffectBaseData>(), Offset(dummy_fx_entry, afxMagicSpellData), &_castingPhrase,
+    "...");
+  endGroup("Casting Stage");
+
+  addGroup("Delivery Stage");
+  addField("deliveryDur",           TypeF32,        myOffset(delivery_dur),
+    "...");
+  addField("numDeliveryLoops",      TypeS32,        myOffset(n_delivery_loops),
+    "...");
+  addField("extraDeliveryTime",     TypeF32,        myOffset(extra_delivery_time),
+    "...");
+  addFieldV("addLaunchEffect", TYPEID<afxEffectBaseData>(), Offset(dummy_fx_entry, afxMagicSpellData), &_launchPhrase,
+    "...");
+  addFieldV("addDeliveryEffect", TYPEID<afxEffectBaseData>(), Offset(dummy_fx_entry, afxMagicSpellData), &_deliveryPhrase,
+    "...");
+  endGroup("Delivery Stage");
+
+  addGroup("Linger Stage");
+  addField("lingerDur",             TypeF32,        myOffset(linger_dur),
+    "...");
+  addField("numLingerLoops",        TypeS32,        myOffset(n_linger_loops),
+    "...");
+  addField("extraLingerTime",       TypeF32,        myOffset(extra_linger_time),
+    "...");
+  addFieldV("addImpactEffect", TYPEID<afxEffectBaseData>(), Offset(dummy_fx_entry, afxMagicSpellData), &_impactPhrase,
+    "...");
+  addFieldV("addLingerEffect", TYPEID<afxEffectBaseData>(), Offset(dummy_fx_entry, afxMagicSpellData), &_lingerPhrase,
+    "...");
+  endGroup("Linger Stage");
+
+  // interrupt flags
+  addField("allowMovementInterrupts", TypeBool,     myOffset(do_move_interrupts),
+    "...");
+  addField("movementInterruptSpeed",  TypeF32,      myOffset(move_interrupt_speed),
+    "...");
+
+  // delivers projectile spells
+  addField("missile",               TYPEID<afxMagicMissileData>(), myOffset(missile_db),
+    "...");
+  addField("launchOnServerSignal",  TypeBool,                   myOffset(launch_on_server_signal),
+    "...");
+  addField("primaryTargetTypes",    TypeS32,                    myOffset(primary_target_types),
+    "...");
+
+
+  Parent::initPersistFields();
+
+  // disallow some field substitutions
+  onlyKeepClearSubstitutions("missile"); // subs resolving to "~~", or "~0" are OK
+  disableFieldSubstitutions("addCastingEffect");
+  disableFieldSubstitutions("addLaunchEffect");
+  disableFieldSubstitutions("addDeliveryEffect");
+  disableFieldSubstitutions("addImpactEffect");
+  disableFieldSubstitutions("addLingerEffect");
+}
+
+bool afxMagicSpellData::onAdd()
+{
+  if (Parent::onAdd() == false)
+    return false;
+
+  if (missile_db != NULL && delivery_dur == 0.0)
+    delivery_dur = -1;
+
+  return true;
+}
+
+void afxMagicSpellData::pack_fx(BitStream* stream, const afxEffectList& fx, bool packed)
+{
+  stream->writeInt(fx.size(), EFFECTS_PER_PHRASE_BITS);
+  for (int i = 0; i < fx.size(); i++)
+    writeDatablockID(stream, fx[i], packed);
+}
+
+void afxMagicSpellData::unpack_fx(BitStream* stream, afxEffectList& fx)
+{
+  fx.clear();
+  S32 n_fx = stream->readInt(EFFECTS_PER_PHRASE_BITS);
+  for (int i = 0; i < n_fx; i++)
+    fx.push_back((afxEffectWrapperData*)readDatablockID(stream));
+}
+
+void afxMagicSpellData::packData(BitStream* stream)
+{
+	Parent::packData(stream);
+
+  stream->write(casting_dur);
+  stream->write(delivery_dur);
+  stream->write(linger_dur);
+  //
+  stream->write(n_casting_loops);
+  stream->write(n_delivery_loops);
+  stream->write(n_linger_loops);
+  //
+  stream->write(extra_casting_time);
+  stream->write(extra_delivery_time);
+  stream->write(extra_linger_time);
+
+  stream->writeFlag(do_move_interrupts);
+  stream->write(move_interrupt_speed);
+
+  writeDatablockID(stream, missile_db, packed);
+  stream->write(launch_on_server_signal);
+  stream->write(primary_target_types);
+
+  pack_fx(stream, casting_fx_list, packed);
+  pack_fx(stream, launch_fx_list, packed);
+  pack_fx(stream, delivery_fx_list, packed);
+  pack_fx(stream, impact_fx_list, packed);
+  pack_fx(stream, linger_fx_list, packed);
+}
+
+void afxMagicSpellData::unpackData(BitStream* stream)
+{
+  Parent::unpackData(stream);
+
+  stream->read(&casting_dur);
+  stream->read(&delivery_dur);
+  stream->read(&linger_dur);
+  //
+  stream->read(&n_casting_loops);
+  stream->read(&n_delivery_loops);
+  stream->read(&n_linger_loops);
+  //
+  stream->read(&extra_casting_time);
+  stream->read(&extra_delivery_time);
+  stream->read(&extra_linger_time);
+
+  do_move_interrupts = stream->readFlag();
+  stream->read(&move_interrupt_speed);
+
+  missile_db = (afxMagicMissileData*) readDatablockID(stream);
+  stream->read(&launch_on_server_signal);
+  stream->read(&primary_target_types);
+
+  do_id_convert = true;
+  unpack_fx(stream, casting_fx_list);
+  unpack_fx(stream, launch_fx_list);
+  unpack_fx(stream, delivery_fx_list);
+  unpack_fx(stream, impact_fx_list);
+  unpack_fx(stream, linger_fx_list);
+}
+
+bool afxMagicSpellData::writeField(StringTableEntry fieldname, const char* value)
+{
+   if (!Parent::writeField(fieldname, value))
+      return false;
+
+   // don't write the dynamic array fields
+   if( fieldname == StringTable->insert("addCastingEffect") )
+      return false;
+   if( fieldname == StringTable->insert("addLaunchEffect") )
+      return false;
+   if( fieldname == StringTable->insert("addDeliveryEffect") )
+      return false;
+   if( fieldname == StringTable->insert("addImpactEffect") )
+      return false;
+   if( fieldname == StringTable->insert("addLingerEffect") )
+      return false;
+
+   return true;
+}
+
+inline void expand_fx_list(afxEffectList& fx_list, const char* tag)
+{
+  for (S32 i = 0; i < fx_list.size(); i++)
+  {
+    SimObjectId db_id = SimObjectId((uintptr_t)fx_list[i]);
+    if (db_id != 0)
+    {
+      // try to convert id to pointer
+      if (!Sim::findObject(db_id, fx_list[i]))
+      {
+        Con::errorf(ConsoleLogEntry::General,
+          "afxMagicSpellData::preload() -- bad datablockId: 0x%x (%s)",
+          db_id, tag);
+      }
+    }
+  }
+}
+
+bool afxMagicSpellData::preload(bool server, String &errorStr)
+{
+  if (!Parent::preload(server, errorStr))
+    return false;
+
+  // Resolve objects transmitted from server
+  if (!server)
+  {
+    if (do_id_convert)
+    {
+      SimObjectId missile_id = SimObjectId((uintptr_t)missile_db);
+      if (missile_id != 0)
+      {
+        // try to convert id to pointer
+        if (!Sim::findObject(missile_id, missile_db))
+        {
+          Con::errorf(ConsoleLogEntry::General,
+            "afxMagicSpellData::preload() -- bad datablockId: 0x%x (missile)",
+            missile_id);
+        }
+      }
+      expand_fx_list(casting_fx_list, "casting");
+      expand_fx_list(launch_fx_list, "launch");
+      expand_fx_list(delivery_fx_list, "delivery");
+      expand_fx_list(impact_fx_list, "impact");
+      expand_fx_list(linger_fx_list, "linger");
+      do_id_convert = false;
+    }
+  }
+
+  return true;
+}
+
+void afxMagicSpellData::gatherConstraintDefs(Vector<afxConstraintDef>& defs)
+{
+  afxConstraintDef::gather_cons_defs(defs, casting_fx_list);
+  afxConstraintDef::gather_cons_defs(defs, launch_fx_list);
+  afxConstraintDef::gather_cons_defs(defs, delivery_fx_list);
+  afxConstraintDef::gather_cons_defs(defs, impact_fx_list);
+  afxConstraintDef::gather_cons_defs(defs, linger_fx_list);
+
+  if (missile_db)
+    missile_db->gather_cons_defs(defs);
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//
+
+DefineEngineMethod(afxMagicSpellData, reset, void, (),,
+                   "Resets a spell datablock during reload.\n\n"
+                   "@ingroup AFX")
+{
+  object->reloadReset();
+}
+
+DefineEngineMethod(afxMagicSpellData, addCastingEffect, void, (afxEffectBaseData* effect),,
+                   "Adds an effect (wrapper or group) to a spell's casting phase.\n\n"
+                   "@ingroup AFX")
+{
+  if (!effect)
+  {
+    Con::errorf(ConsoleLogEntry::General,
+                "afxMagicSpellData::addCastingEffect() -- "
+                "missing afxEffectWrapperData.");
+    return;
+  }
+
+  object->casting_fx_list.push_back(effect);
+}
+
+DefineEngineMethod(afxMagicSpellData, addLaunchEffect, void, (afxEffectBaseData* effect),,
+                   "Adds an effect (wrapper or group) to a spell's launch phase.\n\n"
+                   "@ingroup AFX")
+
+{
+  if (!effect)
+  {
+    Con::errorf(ConsoleLogEntry::General,
+                "afxMagicSpellData::addLaunchEffect() -- "
+                "failed to find afxEffectWrapperData.");
+    return;
+  }
+
+  object->launch_fx_list.push_back(effect);
+}
+
+DefineEngineMethod(afxMagicSpellData, addDeliveryEffect, void, (afxEffectBaseData* effect),,
+                   "Adds an effect (wrapper or group) to a spell's delivery phase.\n\n"
+                   "@ingroup AFX")
+
+{
+  if (!effect)
+  {
+    Con::errorf(ConsoleLogEntry::General,
+                "afxMagicSpellData::addDeliveryEffect() -- "
+                "missing afxEffectWrapperData.");
+    return;
+  }
+
+  object->delivery_fx_list.push_back(effect);
+}
+
+DefineEngineMethod(afxMagicSpellData, addImpactEffect, void, (afxEffectBaseData* effect),,
+                   "Adds an effect (wrapper or group) to a spell's impact phase.\n\n"
+                   "@ingroup AFX")
+
+{
+  if (!effect)
+  {
+    Con::errorf(ConsoleLogEntry::General,
+                "afxMagicSpellData::addImpactEffect() -- "
+                "missing afxEffectWrapperData.");
+    return;
+  }
+
+  object->impact_fx_list.push_back(effect);
+}
+
+DefineEngineMethod(afxMagicSpellData, addLingerEffect, void, (afxEffectBaseData* effect),,
+                   "Adds an effect (wrapper or group) to a spell's linger phase.\n\n"
+                   "@ingroup AFX")
+
+{
+  if (!effect)
+  {
+    Con::errorf(ConsoleLogEntry::General,
+                "afxMagicSpellData::addLingerEffect() -- "
+                "missing afxEffectWrapperData.");
+    return;
+  }
+
+  object->linger_fx_list.push_back(effect);
+}
+
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxMagicSpell
+
+IMPLEMENT_GLOBAL_CALLBACK( onCastingStart, void, (), (),
+   "A callout called on clients by spells when the casting stage begins.\n"
+   "@ingroup AFX\n" );
+
+IMPLEMENT_GLOBAL_CALLBACK( onCastingProgressUpdate, void, (F32 frac), (frac),
+   "A callout called periodically on clients by spells to indicate casting progress.\n"
+   "@ingroup AFX\n" );
+
+IMPLEMENT_GLOBAL_CALLBACK( onCastingEnd, void, (), (),
+   "A callout called on clients by spells when the casting stage ends.\n"
+   "@ingroup AFX\n" );
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// CastingPhrase_C
+//    Subclass of afxPhrase for the client casting phrase.
+//    This subclass adds handling of the casting progress
+//    bar in cases where the caster is the client's control
+//    object.
+//
+
+class CastingPhrase_C : public afxPhrase
+{
+  typedef afxPhrase Parent;
+  ShapeBase*    caster;
+  bool          notify_castbar;
+  F32           castbar_progress;
+public:
+  /*C*/         CastingPhrase_C(ShapeBase* caster, bool notify_castbar);
+  virtual void  start(F32 startstamp, F32 timestamp);
+  virtual void  update(F32 dt, F32 timestamp);
+  virtual void  stop(F32 timestamp);
+  virtual void  interrupt(F32 timestamp);
+};
+
+CastingPhrase_C::CastingPhrase_C(ShapeBase* c, bool notify)
+  : afxPhrase(false, true)
+{
+  caster = c;
+  notify_castbar = notify;
+  castbar_progress = 0.0f;
+}
+
+void CastingPhrase_C::start(F32 startstamp, F32 timestamp)
+{
+  Parent::start(startstamp, timestamp); //START
+  if (notify_castbar)
+  {
+    castbar_progress = 0.0f;
+    onCastingStart_callback();
+  }
+}
+
+void CastingPhrase_C::update(F32 dt, F32 timestamp)
+{
+  Parent::update(dt, timestamp);
+
+  if (!notify_castbar)
+    return;
+
+  if (dur > 0 && n_loops > 0)
+  {
+    F32 nfrac = (timestamp - starttime)/(dur*n_loops);
+    if (nfrac - castbar_progress > 1.0f/200.0f)
+    {
+      castbar_progress = (nfrac < 1.0f) ? nfrac : 1.0f;
+      onCastingProgressUpdate_callback(castbar_progress);
+    }
+  }
+}
+
+void CastingPhrase_C::stop(F32 timestamp)
+{
+  Parent::stop(timestamp);
+  if (notify_castbar)
+  {
+    onCastingEnd_callback();
+    notify_castbar = false;
+  }
+}
+
+void CastingPhrase_C::interrupt(F32 timestamp)
+{
+  Parent::interrupt(timestamp);
+  if (notify_castbar)
+  {
+    onCastingEnd_callback();
+    notify_castbar = false;
+  }
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// some enum to name converters for debugging purposes
+
+#ifdef USE_FOR_DEBUG_MESSAGES
+static char* name_from_state(U8 s)
+{
+  switch (s)
+  {
+  case afxMagicSpell::INACTIVE_STATE:
+    return "inactive";
+  case afxMagicSpell::CASTING_STATE:
+    return "casting";
+  case afxMagicSpell::DELIVERY_STATE:
+    return "delivery";
+  case afxMagicSpell::LINGER_STATE:
+    return "linger";
+  case afxMagicSpell::CLEANUP_STATE:
+    return "cleanup";
+  case afxMagicSpell::DONE_STATE:
+    return "done";
+  }
+
+  return "unknown";
+}
+
+static char* name_from_event(U8 e)
+{
+  switch (e)
+  {
+  case afxMagicSpell::NULL_EVENT:
+    return "null";
+  case afxMagicSpell::ACTIVATE_EVENT:
+    return "activate";
+  case afxMagicSpell::LAUNCH_EVENT:
+    return "launch";
+  case afxMagicSpell::IMPACT_EVENT:
+    return "impact";
+  case afxMagicSpell::SHUTDOWN_EVENT:
+    return "shutdown";
+  case afxMagicSpell::DEACTIVATE_EVENT:
+    return "deactivate";
+  case afxMagicSpell::INTERRUPT_PHASE_EVENT:
+    return "interrupt_phase";
+  case afxMagicSpell::INTERRUPT_SPELL_EVENT:
+    return "interrupt_spell";
+  }
+
+  return "unknown";
+}
+#endif
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxMagicSpell
+
+IMPLEMENT_CO_NETOBJECT_V1(afxMagicSpell);
+
+ConsoleDocClass( afxMagicSpell,
+   "@brief A magic spell effects choreographer.\n\n"
+
+   "@ingroup afxChoreographers\n"
+   "@ingroup AFX\n"
+);
+
+// static
+StringTableEntry  afxMagicSpell::CASTER_CONS;
+StringTableEntry  afxMagicSpell::TARGET_CONS;
+StringTableEntry  afxMagicSpell::MISSILE_CONS;
+StringTableEntry  afxMagicSpell::CAMERA_CONS;
+StringTableEntry  afxMagicSpell::LISTENER_CONS;
+StringTableEntry  afxMagicSpell::IMPACT_POINT_CONS;
+StringTableEntry  afxMagicSpell::IMPACTED_OBJECT_CONS;
+
+void afxMagicSpell::init()
+{
+  // setup static predefined constraint names
+  if (CASTER_CONS == 0)
+  {
+    CASTER_CONS = StringTable->insert("caster");
+    TARGET_CONS = StringTable->insert("target");
+    MISSILE_CONS = StringTable->insert("missile");
+    CAMERA_CONS = StringTable->insert("camera");
+    LISTENER_CONS = StringTable->insert("listener");
+    IMPACT_POINT_CONS = StringTable->insert("impactPoint");
+    IMPACTED_OBJECT_CONS = StringTable->insert("impactedObject");
+  }
+
+  // afxMagicSpell is always in scope, however effects
+  // do their own scoping in that they will shut off if
+  // their position constraint leaves scope.
+  //
+  //   note -- ghosting is delayed until constraint
+  //           initialization is done.
+  //
+  //mNetFlags.set(Ghostable | ScopeAlways);
+  mNetFlags.clear(Ghostable | ScopeAlways);
+
+  datablock = NULL;
+  exeblock = NULL;
+  missile_db = NULL;
+
+  caster = NULL;
+  target = NULL;
+
+  caster_field = NULL;
+  target_field = NULL;
+
+  caster_scope_id = 0;
+  target_scope_id = 0;
+  target_is_shape = false;
+
+  constraints_initialized = false;
+  scoping_initialized = false;
+
+  spell_state = (U8) INACTIVE_STATE;
+  spell_elapsed = 0;
+
+  // define named constraints
+  constraint_mgr->defineConstraint(OBJECT_CONSTRAINT,  CASTER_CONS);
+  constraint_mgr->defineConstraint(OBJECT_CONSTRAINT,  TARGET_CONS);
+  constraint_mgr->defineConstraint(OBJECT_CONSTRAINT, MISSILE_CONS);
+  constraint_mgr->defineConstraint(CAMERA_CONSTRAINT, CAMERA_CONS);
+  constraint_mgr->defineConstraint(POINT_CONSTRAINT,  LISTENER_CONS);
+  constraint_mgr->defineConstraint(POINT_CONSTRAINT,  IMPACT_POINT_CONS);
+  constraint_mgr->defineConstraint(OBJECT_CONSTRAINT,  IMPACTED_OBJECT_CONS);
+
+  for (S32 i = 0; i < NUM_PHRASES; i++)
+  {
+    phrases[i] = NULL;
+    tfactors[i] = 1.0f;
+  }
+
+  notify_castbar = false;
+  overall_time_factor = 1.0f;
+
+  camera_cons_obj = 0;
+
+  marks_mask = 0;
+
+  missile = NULL;
+  missile_is_armed = false;
+  impacted_obj = NULL;
+  impact_pos.zero();
+  impact_norm.set(0,0,1);
+  impacted_scope_id = 0;
+  impacted_is_shape = false;
+}
+
+afxMagicSpell::afxMagicSpell()
+{
+  started_with_newop = true;
+  init();
+}
+
+afxMagicSpell::afxMagicSpell(ShapeBase* caster, SceneObject* target)
+{
+  started_with_newop = false;
+  init();
+
+  this->caster = caster;
+  if (caster)
+  {
+    caster_field = caster;
+    deleteNotify(caster);
+    processAfter(caster);
+  }
+
+  this->target = target;
+  if (target)
+  {
+    target_field = target;
+    deleteNotify(target);
+  }
+}
+
+afxMagicSpell::~afxMagicSpell()
+{
+  for (S32 i = 0; i < NUM_PHRASES; i++)
+  {
+    if (phrases[i])
+    {
+      phrases[i]->interrupt(spell_elapsed);
+      delete phrases[i];
+    }
+  }
+
+  if (missile)
+    missile->deleteObject();
+
+  if (missile_db && missile_db->isTempClone())
+  {
+    delete missile_db;
+    missile_db = 0;
+  }
+
+   if (datablock && datablock->isTempClone())
+   {
+     delete datablock;
+     datablock = 0;
+   }
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+// STANDARD OVERLOADED METHODS //
+
+bool afxMagicSpell::onNewDataBlock(GameBaseData* dptr, bool reload)
+{
+  datablock = dynamic_cast<afxMagicSpellData*>(dptr);
+  if (!datablock || !Parent::onNewDataBlock(dptr, reload))
+    return false;
+
+  if (isServerObject() && started_with_newop)
+  {
+    // copy dynamic fields from the datablock but
+    // don't replace fields with a value
+    assignDynamicFieldsFrom(dptr, arcaneFX::sParameterFieldPrefix, true);
+  }
+
+  exeblock = datablock;
+  missile_db = datablock->missile_db;
+
+  if (isClientObject())
+  {
+    // make a temp datablock clone if there are substitutions
+    if (datablock->getSubstitutionCount() > 0)
+    {
+      afxMagicSpellData* orig_db = datablock;
+      datablock = new afxMagicSpellData(*orig_db, true);
+      exeblock = orig_db;
+      missile_db = datablock->missile_db;
+      // Don't perform substitutions yet, the spell's dynamic fields haven't
+      // arrived yet and the substitutions may refer to them. Hold off and do
+      // in in the onAdd() method.
+    }
+  }
+  else if (started_with_newop)
+  {
+    // make a temp datablock clone if there are substitutions
+    if (datablock->getSubstitutionCount() > 0)
+    {
+      afxMagicSpellData* orig_db = datablock;
+      datablock = new afxMagicSpellData(*orig_db, true);
+      exeblock = orig_db;
+      orig_db->performSubstitutions(datablock, this, ranking);
+      missile_db = datablock->missile_db;
+    }
+  }
+
+  return true;
+}
+
+void afxMagicSpell::processTick(const Move* m)
+{
+  Parent::processTick(m);
+
+  // don't process moves or client ticks
+  if (m != 0 || isClientObject())
+    return;
+
+  process_server();
+}
+
+void afxMagicSpell::advanceTime(F32 dt)
+{
+  Parent::advanceTime(dt);
+
+  process_client(dt);
+}
+
+bool afxMagicSpell::onAdd()
+{
+  if (!Parent::onAdd())
+    return false ;
+
+  if (isClientObject())
+  {
+    if (datablock->isTempClone())
+    {
+      afxMagicSpellData* orig_db = (afxMagicSpellData*)exeblock;
+      orig_db->performSubstitutions(datablock, this, ranking);
+      missile_db = datablock->missile_db;
+      notify_castbar = (notify_castbar && (datablock->casting_dur > 0.0f));
+    }
+  }
+  else if (started_with_newop && !postpone_activation)
+  {
+    if (!activationCallInit())
+      return false;
+    activate();
+  }
+
+  return true ;
+}
+
+void afxMagicSpell::onRemove()
+{
+  Parent::onRemove();
+}
+
+void afxMagicSpell::onDeleteNotify(SimObject* obj)
+{
+  // caster deleted?
+  ShapeBase* shape = dynamic_cast<ShapeBase*>(obj);
+  if (shape == caster)
+  {
+    clearProcessAfter();
+    caster = NULL;
+    caster_field = NULL;
+    caster_scope_id = 0;
+  }
+
+  // target deleted?
+  SceneObject* scene_obj = dynamic_cast<SceneObject*>(obj);
+  if (scene_obj == target)
+  {
+    target = NULL;
+    target_field = NULL;
+    target_scope_id = 0;
+    target_is_shape = false;
+  }
+
+  // impacted_obj deleted?
+  if (scene_obj == impacted_obj)
+  {
+    impacted_obj = NULL;
+    impacted_scope_id = 0;
+    impacted_is_shape = false;
+  }
+
+  // missile deleted?
+  afxMagicMissile* missile = dynamic_cast<afxMagicMissile*>(obj);
+  if (missile != NULL && missile == this->missile)
+  {
+    this->missile = NULL;
+  }
+
+  // something else
+  Parent::onDeleteNotify(obj);
+}
+
+// static
+void afxMagicSpell::initPersistFields()
+{
+  addField("caster", TYPEID<SimObject>(), Offset(caster_field, afxMagicSpell),
+    "...");
+  addField("target", TYPEID<SimObject>(), Offset(target_field, afxMagicSpell),
+    "...");
+
+  Parent::initPersistFields();
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+void afxMagicSpell::pack_constraint_info(NetConnection* conn, BitStream* stream)
+{
+  // pack caster's ghost index or scope id if not yet ghosted
+  if (stream->writeFlag(caster != NULL))
+  {
+    S32 ghost_idx = conn->getGhostIndex(caster);
+    if (stream->writeFlag(ghost_idx != -1))
+      stream->writeRangedU32(U32(ghost_idx), 0, NetConnection::MaxGhostCount);
+    else
+    {
+      bool bit = (caster) ? (caster->getScopeId() > 0) : false;
+      if (stream->writeFlag(bit))
+        stream->writeInt(caster->getScopeId(), NetObject::SCOPE_ID_BITS);
+    }
+  }
+
+  // pack target's ghost index or scope id if not yet ghosted
+  if (stream->writeFlag(target != NULL))
+  {
+    S32 ghost_idx = conn->getGhostIndex(target);
+    if (stream->writeFlag(ghost_idx != -1))
+      stream->writeRangedU32(U32(ghost_idx), 0, NetConnection::MaxGhostCount);
+    else
+    {
+      if (stream->writeFlag(target->getScopeId() > 0))
+      {
+        stream->writeInt(target->getScopeId(), NetObject::SCOPE_ID_BITS);
+        stream->writeFlag(dynamic_cast<ShapeBase*>(target) != NULL); // is shape?
+      }
+    }
+  }
+
+  Parent::pack_constraint_info(conn, stream);
+}
+
+void afxMagicSpell::unpack_constraint_info(NetConnection* conn, BitStream* stream)
+{
+  caster = NULL;
+  caster_field = NULL;
+  caster_scope_id = 0;
+  if (stream->readFlag()) // has caster
+  {
+    if (stream->readFlag()) // has ghost_idx
+    {
+      S32 ghost_idx = stream->readRangedU32(0, NetConnection::MaxGhostCount);
+      caster = dynamic_cast<ShapeBase*>(conn->resolveGhost(ghost_idx));
+      if (caster)
+      {
+        caster_field = caster;
+        deleteNotify(caster);
+        processAfter(caster);
+      }
+    }
+    else
+    {
+      if (stream->readFlag()) // has scope_id (is always a shape)
+        caster_scope_id = stream->readInt(NetObject::SCOPE_ID_BITS);
+    }
+  }
+
+  target = NULL;
+  target_field = NULL;
+  target_scope_id = 0;
+  target_is_shape = false;
+  if (stream->readFlag()) // has target
+  {
+    if (stream->readFlag()) // has ghost_idx
+    {
+      S32 ghost_idx = stream->readRangedU32(0, NetConnection::MaxGhostCount);
+      target = dynamic_cast<SceneObject*>(conn->resolveGhost(ghost_idx));
+      if (target)
+      {
+        target_field = target;
+        deleteNotify(target);
+      }
+    }
+    else
+    {
+      if (stream->readFlag()) // has scope_id
+      {
+        target_scope_id = stream->readInt(NetObject::SCOPE_ID_BITS);
+        target_is_shape = stream->readFlag(); // is shape?
+      }
+    }
+  }
+
+  Parent::unpack_constraint_info(conn, stream);
+}
+
+U32 afxMagicSpell::packUpdate(NetConnection* conn, U32 mask, BitStream* stream)
+{
+  S32 mark_stream_pos = stream->getCurPos();
+
+  U32 retMask = Parent::packUpdate(conn, mask, stream);
+
+  // InitialUpdate
+  if (stream->writeFlag(mask & InitialUpdateMask))
+  {
+    // pack extra object's ghost index or scope id if not yet ghosted
+    if (stream->writeFlag(dynamic_cast<NetObject*>(extra) != 0))
+    {
+      NetObject* net_extra = (NetObject*)extra;
+      S32 ghost_idx = conn->getGhostIndex(net_extra);
+      if (stream->writeFlag(ghost_idx != -1))
+         stream->writeRangedU32(U32(ghost_idx), 0, NetConnection::MaxGhostCount);
+      else
+      {
+        if (stream->writeFlag(net_extra->getScopeId() > 0))
+        {
+          stream->writeInt(net_extra->getScopeId(), NetObject::SCOPE_ID_BITS);
+        }
+      }
+    }
+
+    // pack initial exec conditions
+    stream->write(exec_conds_mask);
+
+    // flag if this client owns the spellcaster
+    bool client_owns_caster = is_caster_client(caster, dynamic_cast<GameConnection*>(conn));
+    stream->writeFlag(client_owns_caster);
+
+    // pack per-phrase time-factor values
+    for (S32 i = 0; i < NUM_PHRASES; i++)
+      stream->write(tfactors[i]);
+
+    // flag if this conn is zoned-in yet
+    bool zoned_in = client_owns_caster;
+    if (!zoned_in)
+    {
+      GameConnection* gconn = dynamic_cast<GameConnection*>(conn);
+      zoned_in = (gconn) ? gconn->isZonedIn() : false;
+    }
+    if (stream->writeFlag(zoned_in))
+      pack_constraint_info(conn, stream);
+  }
+
+  // StateEvent or SyncEvent
+  if (stream->writeFlag((mask & StateEventMask) || (mask & SyncEventMask)))
+  {
+    stream->write(marks_mask);
+    stream->write(spell_state);
+    stream->write(state_elapsed());
+    stream->write(spell_elapsed);
+  }
+
+  // SyncEvent
+  if (stream->writeFlag((mask & SyncEventMask) && !(mask & InitialUpdateMask)))
+  {
+    pack_constraint_info(conn, stream);
+  }
+
+  // LaunchEvent
+  if (stream->writeFlag((mask & LaunchEventMask) && (marks_mask & MARK_LAUNCH) && missile))
+  {
+    F32 vel; Point3F vel_vec;
+    missile->getStartingVelocityValues(vel, vel_vec);
+    // pack launch vector and velocity
+    stream->write(vel);
+    mathWrite(*stream, vel_vec);
+  }
+
+  // ImpactEvent
+  if (stream->writeFlag(((mask & ImpactEventMask) || (mask & SyncEventMask)) && (marks_mask & MARK_IMPACT)))
+  {
+    // pack impact objects's ghost index or scope id if not yet ghosted
+    if (stream->writeFlag(impacted_obj != NULL))
+    {
+      S32 ghost_idx = conn->getGhostIndex(impacted_obj);
+      if (stream->writeFlag(ghost_idx != -1))
+        stream->writeRangedU32(U32(ghost_idx), 0, NetConnection::MaxGhostCount);
+      else
+      {
+        if (stream->writeFlag(impacted_obj->getScopeId() > 0))
+        {
+          stream->writeInt(impacted_obj->getScopeId(), NetObject::SCOPE_ID_BITS);
+          stream->writeFlag(dynamic_cast<ShapeBase*>(impacted_obj) != NULL);
+        }
+      }
+    }
+
+    // pack impact position and normal
+    mathWrite(*stream, impact_pos);
+    mathWrite(*stream, impact_norm);
+    stream->write(exec_conds_mask);
+
+    ShapeBase* temp_shape;
+    stream->writeFlag(caster != 0 && caster->getDamageState() == ShapeBase::Enabled);
+    temp_shape = dynamic_cast<ShapeBase*>(target);
+    stream->writeFlag(temp_shape != 0 && temp_shape->getDamageState() == ShapeBase::Enabled);
+    temp_shape = dynamic_cast<ShapeBase*>(impacted_obj);
+    stream->writeFlag(temp_shape != 0 && temp_shape->getDamageState() == ShapeBase::Enabled);
+  }
+
+  check_packet_usage(conn, stream, mark_stream_pos, "afxMagicSpell:");
+
+  AssertISV(stream->isValid(), "afxMagicSpell::packUpdate(): write failure occurred, possibly caused by packet-size overrun.");
+
+  return retMask;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//
+
+ // CONSTRAINT REMAPPING <<
+bool afxMagicSpell::remap_builtin_constraint(SceneObject* obj, const char* cons_name)
+{
+  StringTableEntry cons_name_ste = StringTable->insert(cons_name);
+
+  if (cons_name_ste == CASTER_CONS)
+    return true;
+  if (cons_name_ste == TARGET_CONS)
+  {
+    if (obj && target && obj != target && !target_cons_id.undefined())
+    {
+      target = obj;
+      constraint_mgr->setReferenceObject(target_cons_id, target);
+      if (isServerObject())
+      {
+        if (target->isScopeable())
+          constraint_mgr->addScopeableObject(target);
+      }
+    }
+    return true;
+  }
+  if (cons_name_ste == MISSILE_CONS)
+    return true;
+  if (cons_name_ste == CAMERA_CONS)
+    return true;
+  if (cons_name_ste == LISTENER_CONS)
+    return true;
+  if (cons_name_ste == IMPACT_POINT_CONS)
+    return true;
+  if (cons_name_ste == IMPACTED_OBJECT_CONS)
+    return true;
+
+  return false;
+}
+ // CONSTRAINT REMAPPING >>
+
+void afxMagicSpell::unpackUpdate(NetConnection * conn, BitStream * stream)
+{
+  Parent::unpackUpdate(conn, stream);
+
+  bool initial_update = false;
+  bool zoned_in = true;
+  bool do_sync_event = false;
+  U16 new_marks_mask = 0;
+  U8 new_spell_state = INACTIVE_STATE;
+  F32 new_state_elapsed = 0;
+  F32 new_spell_elapsed = 0;;
+
+  // InitialUpdate
+  if (stream->readFlag())
+  {
+    initial_update = true;
+
+    // extra sent
+    if (stream->readFlag())
+    {
+      // cleanup?
+      if (stream->readFlag()) // is ghost_idx
+      {
+        S32 ghost_idx = stream->readRangedU32(0, NetConnection::MaxGhostCount);
+        extra = dynamic_cast<SimObject*>(conn->resolveGhost(ghost_idx));
+      }
+      else
+      {
+        if (stream->readFlag()) // has scope_id
+        {
+          // JTF NOTE: U16 extra_scope_id = stream->readInt(NetObject::SCOPE_ID_BITS);
+          stream->readInt(NetObject::SCOPE_ID_BITS);
+        }
+      }
+    }
+
+    // unpack initial exec conditions
+    stream->read(&exec_conds_mask);
+
+    // if this is controlling client for the caster,
+    // enable castbar updates
+    bool client_owns_caster = stream->readFlag();
+    if (client_owns_caster)
+      notify_castbar = Con::isFunction("onCastingStart");
+
+    // unpack per-phrase time-factor values
+    for (S32 i = 0; i < NUM_PHRASES; i++)
+      stream->read(&tfactors[i]);
+
+    // if client is marked as fully zoned in
+    if ((zoned_in = stream->readFlag()) == true)
+    {
+      unpack_constraint_info(conn, stream);
+      init_constraints();
+    }
+  }
+
+  // StateEvent or SyncEvent
+  // this state data is sent for both state-events and
+  // sync-events
+  if (stream->readFlag())
+  {
+    stream->read(&new_marks_mask);
+    stream->read(&new_spell_state);
+    stream->read(&new_state_elapsed);
+    stream->read(&new_spell_elapsed);
+    marks_mask = new_marks_mask;
+  }
+
+  // SyncEvent
+  if ((do_sync_event = stream->readFlag()) == true)
+  {
+    unpack_constraint_info(conn, stream);
+    init_constraints();
+  }
+
+  // LaunchEvent
+  if (stream->readFlag())
+  {
+    F32 vel; Point3F vel_vec;
+    stream->read(&vel);
+    mathRead(*stream, &vel_vec);
+    if (missile)
+    {
+      missile->setStartingVelocity(vel);
+      missile->setStartingVelocityVector(vel_vec);
+    }
+  }
+
+  // ImpactEvent
+  if (stream->readFlag())
+  {
+    if (impacted_obj)
+      clearNotify(impacted_obj);
+    impacted_obj = NULL;
+    impacted_scope_id = 0;
+    impacted_is_shape = false;
+    if (stream->readFlag()) // is impacted_obj
+    {
+      if (stream->readFlag()) // is ghost_idx
+      {
+        S32 ghost_idx = stream->readRangedU32(0, NetConnection::MaxGhostCount);
+        impacted_obj = dynamic_cast<SceneObject*>(conn->resolveGhost(ghost_idx));
+        if (impacted_obj)
+          deleteNotify(impacted_obj);
+      }
+      else
+      {
+        if (stream->readFlag()) // has scope_id
+        {
+          impacted_scope_id = stream->readInt(NetObject::SCOPE_ID_BITS);
+          impacted_is_shape = stream->readFlag(); // is shape?
+        }
+      }
+    }
+
+    mathRead(*stream, &impact_pos);
+    mathRead(*stream, &impact_norm);
+    stream->read(&exec_conds_mask);
+
+    bool caster_alive = stream->readFlag();
+    bool target_alive = stream->readFlag();
+    bool impacted_alive = stream->readFlag();
+
+    afxConstraint* cons;
+    if ((cons = constraint_mgr->getConstraint(caster_cons_id)) != 0)
+      cons->setLivingState(caster_alive);
+    if ((cons = constraint_mgr->getConstraint(target_cons_id)) != 0)
+      cons->setLivingState(target_alive);
+    if ((cons = constraint_mgr->getConstraint(impacted_cons_id)) != 0)
+      cons->setLivingState(impacted_alive);
+  }
+
+  //~~~~~~~~~~~~~~~~~~~~//
+
+  if (!zoned_in)
+    spell_state = LATE_STATE;
+
+  // need to adjust state info to get all synced up with spell on server
+  if (do_sync_event && !initial_update)
+    sync_client(new_marks_mask, new_spell_state, new_state_elapsed, new_spell_elapsed);
+}
+
+void afxMagicSpell::sync_with_clients()
+{
+  setMaskBits(SyncEventMask);
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// private
+
+bool afxMagicSpell::state_expired()
+{
+  afxPhrase* phrase = NULL;
+
+  switch (spell_state)
+  {
+  case CASTING_STATE:
+    phrase = phrases[CASTING_PHRASE];
+    break;
+  case DELIVERY_STATE:
+    phrase = phrases[DELIVERY_PHRASE];
+    break;
+  case LINGER_STATE:
+    phrase = phrases[LINGER_PHRASE];
+    break;
+  }
+
+  if (phrase)
+  {
+    if (phrase->expired(spell_elapsed))
+      return (!phrase->recycle(spell_elapsed));
+    return false;
+  }
+
+  return true;
+}
+
+F32 afxMagicSpell::state_elapsed()
+{
+  afxPhrase* phrase = NULL;
+
+  switch (spell_state)
+  {
+  case CASTING_STATE:
+    phrase = phrases[CASTING_PHRASE];
+    break;
+  case DELIVERY_STATE:
+    phrase = phrases[DELIVERY_PHRASE];
+    break;
+  case LINGER_STATE:
+    phrase = phrases[LINGER_PHRASE];
+    break;
+  }
+
+  return (phrase) ? phrase->elapsed(spell_elapsed) : 0.0f;
+}
+
+void afxMagicSpell::init_constraints()
+{
+  if (constraints_initialized)
+  {
+    //Con::printf("CONSTRAINTS ALREADY INITIALIZED");
+    return;
+  }
+
+  Vector<afxConstraintDef> defs;
+  datablock->gatherConstraintDefs(defs);
+
+  constraint_mgr->initConstraintDefs(defs, isServerObject());
+
+  if (isServerObject())
+  {
+    caster_cons_id = constraint_mgr->setReferenceObject(CASTER_CONS, caster);
+    target_cons_id = constraint_mgr->setReferenceObject(TARGET_CONS, target);
+#if defined(AFX_CAP_SCOPE_TRACKING)
+    if (caster && caster->isScopeable())
+      constraint_mgr->addScopeableObject(caster);
+
+    if (target && target->isScopeable())
+      constraint_mgr->addScopeableObject(target);
+#endif
+
+    // find local camera
+    camera_cons_obj = get_camera();
+    if (camera_cons_obj)
+      camera_cons_id = constraint_mgr->setReferenceObject(CAMERA_CONS, camera_cons_obj);
+  }
+  else // if (isClientObject())
+  {
+    if (caster)
+      caster_cons_id = constraint_mgr->setReferenceObject(CASTER_CONS, caster);
+    else if (caster_scope_id > 0)
+      caster_cons_id = constraint_mgr->setReferenceObjectByScopeId(CASTER_CONS, caster_scope_id, true);
+
+    if (target)
+      target_cons_id = constraint_mgr->setReferenceObject(TARGET_CONS, target);
+    else if (target_scope_id > 0)
+      target_cons_id = constraint_mgr->setReferenceObjectByScopeId(TARGET_CONS, target_scope_id, target_is_shape);
+
+    // find local camera
+    camera_cons_obj = get_camera();
+    if (camera_cons_obj)
+      camera_cons_id = constraint_mgr->setReferenceObject(CAMERA_CONS, camera_cons_obj);
+
+    // find local listener
+    Point3F listener_pos;
+    listener_pos = SFX->getListener().getTransform().getPosition();
+    listener_cons_id = constraint_mgr->setReferencePoint(LISTENER_CONS, listener_pos);
+  }
+
+  constraint_mgr->adjustProcessOrdering(this);
+
+  constraints_initialized = true;
+}
+
+void afxMagicSpell::init_scoping()
+{
+  if (scoping_initialized)
+  {
+    //Con::printf("SCOPING ALREADY INITIALIZED");
+    return;
+  }
+
+  if (isServerObject())
+  {
+    if (explicit_clients.size() > 0)
+    {
+      for (U32 i = 0; i < explicit_clients.size(); i++)
+        explicit_clients[i]->objectLocalScopeAlways(this);
+    }
+    else
+    {
+      mNetFlags.set(Ghostable);
+      setScopeAlways();
+    }
+    scoping_initialized = true;
+  }
+}
+
+void afxMagicSpell::setup_casting_fx()
+{
+  if (isServerObject())
+    phrases[CASTING_PHRASE] = new afxPhrase(isServerObject(), true);
+  else
+    phrases[CASTING_PHRASE] = new CastingPhrase_C(caster, notify_castbar);
+
+  if (phrases[CASTING_PHRASE])
+    phrases[CASTING_PHRASE]->init(datablock->casting_fx_list, datablock->casting_dur, this,
+                                  tfactors[CASTING_PHRASE], datablock->n_casting_loops, 0,
+                                  datablock->extra_casting_time);
+}
+
+void afxMagicSpell::setup_launch_fx()
+{
+  phrases[LAUNCH_PHRASE] = new afxPhrase(isServerObject(), false);
+  if (phrases[LAUNCH_PHRASE])
+    phrases[LAUNCH_PHRASE]->init(datablock->launch_fx_list, -1, this,
+                                 tfactors[LAUNCH_PHRASE], 1);
+}
+
+void afxMagicSpell::setup_delivery_fx()
+{
+  phrases[DELIVERY_PHRASE] = new afxPhrase(isServerObject(), true);
+  if (phrases[DELIVERY_PHRASE])
+  {
+    phrases[DELIVERY_PHRASE]->init(datablock->delivery_fx_list, datablock->delivery_dur, this,
+                                   tfactors[DELIVERY_PHRASE], datablock->n_delivery_loops, 0,
+                                   datablock->extra_delivery_time);
+  }
+}
+
+void afxMagicSpell::setup_impact_fx()
+{
+  phrases[IMPACT_PHRASE] = new afxPhrase(isServerObject(), false);
+  if (phrases[IMPACT_PHRASE])
+  {
+    phrases[IMPACT_PHRASE]->init(datablock->impact_fx_list, -1, this,
+                                 tfactors[IMPACT_PHRASE], 1);
+  }
+}
+
+void afxMagicSpell::setup_linger_fx()
+{
+  phrases[LINGER_PHRASE] = new afxPhrase(isServerObject(), true);
+  if (phrases[LINGER_PHRASE])
+    phrases[LINGER_PHRASE]->init(datablock->linger_fx_list, datablock->linger_dur, this,
+                                 tfactors[LINGER_PHRASE], datablock->n_linger_loops, 0,
+                                 datablock->extra_linger_time);
+}
+
+bool afxMagicSpell::cleanup_over()
+{
+  for (S32 i = 0; i < NUM_PHRASES; i++)
+    if (phrases[i] && !phrases[i]->isEmpty())
+      return false;
+
+  return true;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// private
+//
+// MISSILE STUFF
+//
+
+void afxMagicSpell::init_missile_s(afxMagicMissileData* mm_db)
+{
+  if (missile)
+    clearNotify(missile);
+
+  // create the missile
+  missile = new afxMagicMissile(true, false);
+  missile->setSubstitutionData(this, ranking);
+  missile->setDataBlock(mm_db);
+  missile->setChoreographer(this);
+  if (!missile->registerObject())
+  {
+    Con::errorf("afxMagicSpell: failed to register missile instance.");
+    delete missile;
+    missile = NULL;
+  }
+
+  if (missile)
+  {
+    deleteNotify(missile);
+    registerForCleanup(missile);
+  }
+}
+
+void afxMagicSpell::launch_missile_s()
+{
+  if (missile)
+  {
+    missile->launch();
+    constraint_mgr->setReferenceObject(MISSILE_CONS, missile);
+  }
+}
+
+void afxMagicSpell::init_missile_c(afxMagicMissileData* mm_db)
+{
+  if (missile)
+    clearNotify(missile);
+
+  // create the missile
+  missile = new afxMagicMissile(false, true);
+  missile->setSubstitutionData(this, ranking);
+  missile->setDataBlock(mm_db);
+  missile->setChoreographer(this);
+  if (!missile->registerObject())
+  {
+    Con::errorf("afxMagicSpell: failed to register missile instance.");
+    delete missile;
+    missile = NULL;
+  }
+
+  if (missile)
+  {
+    deleteNotify(missile);
+    registerForCleanup(missile);
+  }
+}
+
+void afxMagicSpell::launch_missile_c()
+{
+  if (missile)
+  {
+    missile->launch();
+    constraint_mgr->setReferenceObject(MISSILE_CONS, missile);
+  }
+}
+
+bool afxMagicSpell::is_impact_in_water(SceneObject* obj, const Point3F& p)
+{
+  // AFX_T3D_BROKEN -- water impact detection is disabled. Look at projectile.
+  return false;
+}
+
+void afxMagicSpell::impactNotify(const Point3F& p, const Point3F& n, SceneObject* obj)
+{
+  if (isClientObject())
+    return;
+
+  ///impact_time_ms = spell_elapsed_ms;
+  if (impacted_obj)
+      clearNotify(impacted_obj);
+  impacted_obj = obj;
+  impact_pos = p;
+  impact_norm = n;
+
+  if (impacted_obj != NULL)
+  {
+    deleteNotify(impacted_obj);
+    exec_conds_mask |= IMPACTED_SOMETHING;
+    if (impacted_obj == target)
+      exec_conds_mask |= IMPACTED_TARGET;
+    if (impacted_obj->getTypeMask() & datablock->primary_target_types)
+      exec_conds_mask |= IMPACTED_PRIMARY;
+  }
+
+  if (is_impact_in_water(obj, p))
+    exec_conds_mask |= IMPACT_IN_WATER;
+
+  postSpellEvent(IMPACT_EVENT);
+
+  if (missile)
+    clearNotify(missile);
+  missile = NULL;
+}
+
+void afxMagicSpell::executeScriptEvent(const char* method, afxConstraint* cons,
+                                        const MatrixF& xfm, const char* data)
+{
+  SceneObject* cons_obj = (cons) ? cons->getSceneObject() : NULL;
+
+  char *arg_buf = Con::getArgBuffer(256);
+  Point3F pos;
+  xfm.getColumn(3,&pos);
+  AngAxisF aa(xfm);
+  dSprintf(arg_buf,256,"%g %g %g %g %g %g %g",
+           pos.x, pos.y, pos.z,
+           aa.axis.x, aa.axis.y, aa.axis.z, aa.angle);
+
+  // CALL SCRIPT afxChoreographerData::method(%spell, %caster, %constraint, %transform, %data)
+  Con::executef(exeblock, method,
+                getIdString(),
+                (caster) ? caster->getIdString() : "",
+                (cons_obj) ? cons_obj->getIdString() : "",
+                arg_buf,
+                data);
+}
+
+void afxMagicSpell::inflictDamage(const char * label, const char* flavor, SimObjectId target_id,
+                                   F32 amount, U8 n, F32 ad_amount, F32 radius, Point3F pos, F32 impulse)
+{
+ // Con::printf("INFLICT-DAMAGE label=%s flav=%s id=%d amt=%g n=%d rad=%g pos=(%g %g %g) imp=%g",
+ //             label, flavor, target_id, amount, n, radius, pos.x, pos.y, pos.z, impulse);
+
+  // CALL SCRIPT afxMagicSpellData::onDamage()
+  //    onDamage(%spell, %label, %type, %damaged_obj, %amount, %count, %pos, %ad_amount,
+  //             %radius, %impulse)
+  datablock->onDamage_callback(this, label, flavor, target_id, amount, n, pos, ad_amount, radius, impulse);
+}
+
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// private
+
+void afxMagicSpell::process_server()
+{
+  if (spell_state != INACTIVE_STATE)
+    spell_elapsed += TickSec;
+
+  U8 pending_state = spell_state;
+
+  // check for state changes
+  switch (spell_state)
+  {
+
+  case INACTIVE_STATE:
+    if (marks_mask & MARK_ACTIVATE)
+      pending_state = CASTING_STATE;
+    break;
+
+  case CASTING_STATE:
+    if (datablock->casting_dur > 0.0f && datablock->do_move_interrupts && is_caster_moving())
+    {
+      displayScreenMessage(caster, "SPELL INTERRUPTED.");
+      postSpellEvent(INTERRUPT_SPELL_EVENT);
+    }
+    if (marks_mask & MARK_INTERRUPT_CASTING)
+      pending_state = CLEANUP_STATE;
+    else if (marks_mask & MARK_END_CASTING)
+      pending_state = DELIVERY_STATE;
+    else if (marks_mask & MARK_LAUNCH)
+      pending_state = DELIVERY_STATE;
+    else if (state_expired())
+      pending_state = DELIVERY_STATE;
+    break;
+
+  case DELIVERY_STATE:
+    if (marks_mask & MARK_INTERRUPT_DELIVERY)
+      pending_state = CLEANUP_STATE;
+    else if (marks_mask & MARK_END_DELIVERY)
+      pending_state = LINGER_STATE;
+    else if (marks_mask & MARK_IMPACT)
+      pending_state = LINGER_STATE;
+    else if (state_expired())
+      pending_state = LINGER_STATE;
+    break;
+
+  case LINGER_STATE:
+    if (marks_mask & MARK_INTERRUPT_LINGER)
+      pending_state = CLEANUP_STATE;
+    else if (marks_mask & MARK_END_LINGER)
+      pending_state = CLEANUP_STATE;
+    else if (marks_mask & MARK_SHUTDOWN)
+      pending_state = CLEANUP_STATE;
+    else if (state_expired())
+      pending_state = CLEANUP_STATE;
+    break;
+
+  case CLEANUP_STATE:
+    if ((marks_mask & MARK_INTERRUPT_CLEANUP) || cleanup_over())
+      pending_state = DONE_STATE;
+    break;
+  }
+
+  if (spell_state != pending_state)
+    change_state_s(pending_state);
+
+  if (spell_state == INACTIVE_STATE)
+    return;
+
+  //--------------------------//
+
+  // sample the constraints
+  constraint_mgr->sample(TickSec, Platform::getVirtualMilliseconds());
+
+  for (S32 i = 0; i < NUM_PHRASES; i++)
+    if (phrases[i])
+      phrases[i]->update(TickSec, spell_elapsed);
+
+  if (missile_is_armed)
+  {
+    launch_missile_s();
+    missile_is_armed = false;
+  }
+}
+
+void afxMagicSpell::change_state_s(U8 pending_state)
+{
+  if (spell_state == pending_state)
+    return;
+
+  // LEAVING THIS STATE
+  switch (spell_state)
+  {
+  case INACTIVE_STATE:
+    break;
+  case CASTING_STATE:
+    leave_casting_state_s();
+    break;
+  case DELIVERY_STATE:
+    leave_delivery_state_s();
+    break;
+  case LINGER_STATE:
+    leave_linger_state_s();
+    break;
+  case CLEANUP_STATE:
+    break;
+  case DONE_STATE:
+    break;
+  }
+
+  spell_state = pending_state;
+
+  // ENTERING THIS STATE
+  switch (pending_state)
+  {
+  case INACTIVE_STATE:
+    break;
+  case CASTING_STATE:
+    enter_casting_state_s();
+    break;
+  case DELIVERY_STATE:
+    enter_delivery_state_s();
+    break;
+  case LINGER_STATE:
+    enter_linger_state_s();
+    break;
+  case CLEANUP_STATE:
+    break;
+  case DONE_STATE:
+    enter_done_state_s();
+    break;
+  }
+}
+
+void afxMagicSpell::enter_done_state_s()
+{
+  postSpellEvent(DEACTIVATE_EVENT);
+
+  if (marks_mask & MARK_INTERRUPTS)
+  {
+    Sim::postEvent(this, new ObjectDeleteEvent, Sim::getCurrentTime() + 500);
+  }
+  else
+  {
+    F32 done_time = spell_elapsed;
+
+    for (S32 i = 0; i < NUM_PHRASES; i++)
+    {
+      if (phrases[i])
+      {
+        F32 phrase_done;
+        if (phrases[i]->willStop() && phrases[i]->isInfinite())
+          phrase_done = spell_elapsed + phrases[i]->calcAfterLife();
+        else
+          phrase_done = phrases[i]->calcDoneTime();
+        if (phrase_done > done_time)
+          done_time = phrase_done;
+      }
+    }
+
+    F32 time_left = done_time - spell_elapsed;
+    if (time_left < 0)
+      time_left = 0;
+
+    Sim::postEvent(this, new ObjectDeleteEvent, Sim::getCurrentTime() + time_left*1000 + 500);
+  }
+
+  // CALL SCRIPT afxMagicSpellData::onDeactivate(%spell)
+  datablock->onDeactivate_callback(this);
+}
+
+void afxMagicSpell::enter_casting_state_s()
+{
+  // note - onActivate() is called in cast_spell() instead of here to make sure any
+  // new time-factor settings resolve before they are sent off to the clients.
+
+  // stamp constraint-mgr starting time and reset spell timer
+  constraint_mgr->setStartTime(Platform::getVirtualMilliseconds());
+  spell_elapsed = 0;
+
+  setup_dynamic_constraints();
+
+  // start casting effects
+  setup_casting_fx();
+  if (phrases[CASTING_PHRASE])
+    phrases[CASTING_PHRASE]->start(spell_elapsed, spell_elapsed);
+
+  // initialize missile
+  if (missile_db)
+  {
+    missile_db = missile_db->cloneAndPerformSubstitutions(this, ranking);
+    init_missile_s(missile_db);
+  }
+}
+
+void afxMagicSpell::leave_casting_state_s()
+{
+  if (phrases[CASTING_PHRASE])
+  {
+    if (marks_mask & MARK_INTERRUPT_CASTING)
+    {
+      //Con::printf("INTERRUPT CASTING (S)");
+      phrases[CASTING_PHRASE]->interrupt(spell_elapsed);
+    }
+    else
+    {
+      //Con::printf("LEAVING CASTING (S)");
+      phrases[CASTING_PHRASE]->stop(spell_elapsed);
+    }
+  }
+
+  if (marks_mask & MARK_INTERRUPT_CASTING)
+  {
+    // CALL SCRIPT afxMagicSpellData::onInterrupt(%spell, %caster)
+    datablock->onInterrupt_callback(this, caster);
+  }
+}
+
+void afxMagicSpell::enter_delivery_state_s()
+{
+  // CALL SCRIPT afxMagicSpellData::onLaunch(%spell, %caster, %target, %missile)
+  datablock->onLaunch_callback(this, caster, target, missile);
+
+  if (datablock->launch_on_server_signal)
+    postSpellEvent(LAUNCH_EVENT);
+
+  missile_is_armed = true;
+
+  // start launch effects
+  setup_launch_fx();
+  if (phrases[LAUNCH_PHRASE])
+    phrases[LAUNCH_PHRASE]->start(spell_elapsed, spell_elapsed); //START
+
+  // start delivery effects
+  setup_delivery_fx();
+  if (phrases[DELIVERY_PHRASE])
+    phrases[DELIVERY_PHRASE]->start(spell_elapsed, spell_elapsed); //START
+}
+
+void afxMagicSpell::leave_delivery_state_s()
+{
+  if (phrases[DELIVERY_PHRASE])
+  {
+    if (marks_mask & MARK_INTERRUPT_DELIVERY)
+    {
+      //Con::printf("INTERRUPT DELIVERY (S)");
+      phrases[DELIVERY_PHRASE]->interrupt(spell_elapsed);
+    }
+    else
+    {
+      //Con::printf("LEAVING DELIVERY (S)");
+      phrases[DELIVERY_PHRASE]->stop(spell_elapsed);
+    }
+  }
+
+  if (!missile && !(marks_mask & MARK_IMPACT))
+  {
+    if (target)
+    {
+      Point3F p = afxMagicSpell::getShapeImpactPos(target);
+      Point3F n = Point3F(0,0,1);
+      impactNotify(p, n, target);
+    }
+    else
+    {
+      Point3F p = Point3F(0,0,0);
+      Point3F n = Point3F(0,0,1);
+      impactNotify(p, n, 0);
+    }
+  }
+}
+
+void afxMagicSpell::enter_linger_state_s()
+{
+  if (impacted_obj)
+  {
+    impacted_cons_id = constraint_mgr->setReferenceObject(IMPACTED_OBJECT_CONS, impacted_obj);
+#if defined(AFX_CAP_SCOPE_TRACKING)
+    if (impacted_obj->isScopeable())
+      constraint_mgr->addScopeableObject(impacted_obj);
+#endif
+  }
+  else
+    constraint_mgr->setReferencePoint(IMPACTED_OBJECT_CONS, impact_pos, impact_norm);
+  constraint_mgr->setReferencePoint(IMPACT_POINT_CONS, impact_pos, impact_norm);
+  constraint_mgr->setReferenceObject(MISSILE_CONS, 0);
+
+  // start impact effects
+  setup_impact_fx();
+  if (phrases[IMPACT_PHRASE])
+    phrases[IMPACT_PHRASE]->start(spell_elapsed, spell_elapsed); //START
+
+  // start linger effects
+  setup_linger_fx();
+  if (phrases[LINGER_PHRASE])
+    phrases[LINGER_PHRASE]->start(spell_elapsed, spell_elapsed); //START
+
+#if 0 // code temporarily replaced with old callback technique in order to avoid engine bug.
+  // CALL SCRIPT afxMagicSpellData::onImpact(%spell, %caster, %impactedObj, %impactedPos, %impactedNorm)
+  datablock->onImpact_callback(this, caster, impacted_obj, impact_pos, impact_norm);
+#else
+  char pos_buf[128];
+  dSprintf(pos_buf, sizeof(pos_buf), "%g %g %g", impact_pos.x, impact_pos.y, impact_pos.z);
+  char norm_buf[128];
+  dSprintf(norm_buf, sizeof(norm_buf), "%g %g %g", impact_norm.x, impact_norm.y, impact_norm.z);
+  Con::executef(exeblock, "onImpact", getIdString(),
+     (caster) ? caster->getIdString(): "",
+     (impacted_obj) ? impacted_obj->getIdString(): "",
+     pos_buf, norm_buf);
+#endif
+}
+
+void afxMagicSpell::leave_linger_state_s()
+{
+  if (phrases[LINGER_PHRASE])
+  {
+    if (marks_mask & MARK_INTERRUPT_LINGER)
+    {
+      //Con::printf("INTERRUPT LINGER (S)");
+      phrases[LINGER_PHRASE]->interrupt(spell_elapsed);
+    }
+    else
+    {
+      //Con::printf("LEAVING LINGER (S)");
+      phrases[LINGER_PHRASE]->stop(spell_elapsed);
+    }
+  }
+}
+
+
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// private
+
+void afxMagicSpell::process_client(F32 dt)
+{
+  spell_elapsed += dt; //SPELL_ELAPSED
+
+  U8 pending_state = spell_state;
+
+  // check for state changes
+  switch (spell_state)
+  {
+  case INACTIVE_STATE:
+    if (marks_mask & MARK_ACTIVATE)
+      pending_state = CASTING_STATE;
+    break;
+  case CASTING_STATE:
+    if (marks_mask & MARK_INTERRUPT_CASTING)
+      pending_state = CLEANUP_STATE;
+    else if (marks_mask & MARK_END_CASTING)
+      pending_state = DELIVERY_STATE;
+    else if (datablock->launch_on_server_signal)
+    {
+      if (marks_mask & MARK_LAUNCH)
+        pending_state = DELIVERY_STATE;
+    }
+    else
+    {
+      if (state_expired())
+        pending_state = DELIVERY_STATE;
+    }
+    break;
+  case DELIVERY_STATE:
+    if (marks_mask & MARK_INTERRUPT_DELIVERY)
+      pending_state = CLEANUP_STATE;
+    else if (marks_mask & MARK_END_DELIVERY)
+      pending_state = LINGER_STATE;
+    else if (marks_mask & MARK_IMPACT)
+      pending_state = LINGER_STATE;
+    else
+      state_expired();
+    break;
+  case LINGER_STATE:
+    if (marks_mask & MARK_INTERRUPT_LINGER)
+      pending_state = CLEANUP_STATE;
+    else if (marks_mask & MARK_END_LINGER)
+      pending_state = CLEANUP_STATE;
+    else if (marks_mask & MARK_SHUTDOWN)
+      pending_state = CLEANUP_STATE;
+    else if (state_expired())
+      pending_state = CLEANUP_STATE;
+    break;
+  case CLEANUP_STATE:
+    if ((marks_mask & MARK_INTERRUPT_CLEANUP) || cleanup_over())
+      pending_state = DONE_STATE;
+    break;
+  }
+
+  if (spell_state != pending_state)
+    change_state_c(pending_state);
+
+  if (spell_state == INACTIVE_STATE)
+    return;
+
+  //--------------------------//
+
+  // update the listener constraint position
+  if (!listener_cons_id.undefined())
+  {
+    Point3F listener_pos;
+    listener_pos = SFX->getListener().getTransform().getPosition();
+    constraint_mgr->setReferencePoint(listener_cons_id, listener_pos);
+  }
+
+  // find local camera position
+  Point3F cam_pos;
+  SceneObject* current_cam = get_camera(&cam_pos);
+
+  // detect camera changes
+  if (!camera_cons_id.undefined() && current_cam != camera_cons_obj)
+  {
+    constraint_mgr->setReferenceObject(camera_cons_id, current_cam);
+    camera_cons_obj = current_cam;
+  }
+
+  // sample the constraints
+  constraint_mgr->sample(dt, Platform::getVirtualMilliseconds(), (current_cam) ? &cam_pos : 0);
+
+  // update active effects lists
+  for (S32 i = 0; i < NUM_PHRASES; i++)
+    if (phrases[i])
+      phrases[i]->update(dt, spell_elapsed);
+
+  if (missile_is_armed)
+  {
+    launch_missile_c();
+    missile_is_armed = false;
+  }
+}
+
+void afxMagicSpell::change_state_c(U8 pending_state)
+{
+  if (spell_state == pending_state)
+    return;
+
+  // LEAVING THIS STATE
+  switch (spell_state)
+  {
+  case INACTIVE_STATE:
+    break;
+  case CASTING_STATE:
+    leave_casting_state_c();
+    break;
+  case DELIVERY_STATE:
+    leave_delivery_state_c();
+    break;
+  case LINGER_STATE:
+    leave_linger_state_c();
+    break;
+  case CLEANUP_STATE:
+    break;
+  case DONE_STATE:
+    break;
+  }
+
+  spell_state = pending_state;
+
+  // ENTERING THIS STATE
+  switch (pending_state)
+  {
+  case INACTIVE_STATE:
+    break;
+  case CASTING_STATE:
+    enter_casting_state_c(spell_elapsed);
+    break;
+  case DELIVERY_STATE:
+    enter_delivery_state_c(spell_elapsed);
+    break;
+  case LINGER_STATE:
+    enter_linger_state_c(spell_elapsed);
+    break;
+  case CLEANUP_STATE:
+    break;
+  case DONE_STATE:
+    break;
+  }
+}
+
+void afxMagicSpell::enter_casting_state_c(F32 starttime)
+{
+  // stamp constraint-mgr starting time
+  constraint_mgr->setStartTime(Platform::getVirtualMilliseconds() - (U32)(spell_elapsed*1000));
+  //spell_elapsed = 0; //SPELL_ELAPSED
+
+  setup_dynamic_constraints();
+
+  // start casting effects and castbar
+  setup_casting_fx();
+  if (phrases[CASTING_PHRASE])
+    phrases[CASTING_PHRASE]->start(starttime, spell_elapsed); //START
+
+  // initialize missile
+  if (missile_db)
+  {
+    missile_db = missile_db->cloneAndPerformSubstitutions(this, ranking);
+    init_missile_c(missile_db);
+  }
+}
+
+void afxMagicSpell::leave_casting_state_c()
+{
+  if (phrases[CASTING_PHRASE])
+  {
+    if (marks_mask & MARK_INTERRUPT_CASTING)
+    {
+      //Con::printf("INTERRUPT CASTING (C)");
+      phrases[CASTING_PHRASE]->interrupt(spell_elapsed);
+    }
+    else
+    {
+      //Con::printf("LEAVING CASTING (C)");
+      phrases[CASTING_PHRASE]->stop(spell_elapsed);
+    }
+  }
+}
+
+void afxMagicSpell::enter_delivery_state_c(F32 starttime)
+{
+  missile_is_armed = true;
+
+  setup_launch_fx();
+  if (phrases[LAUNCH_PHRASE])
+    phrases[LAUNCH_PHRASE]->start(starttime, spell_elapsed); //START
+
+  setup_delivery_fx();
+  if (phrases[DELIVERY_PHRASE])
+    phrases[DELIVERY_PHRASE]->start(starttime, spell_elapsed); //START
+}
+
+void afxMagicSpell::leave_delivery_state_c()
+{
+  if (missile)
+  {
+    clearNotify(missile);
+    missile->deleteObject();
+    missile = NULL;
+  }
+
+  if (phrases[DELIVERY_PHRASE])
+  {
+    if (marks_mask & MARK_INTERRUPT_DELIVERY)
+    {
+      //Con::printf("INTERRUPT DELIVERY (C)");
+      phrases[DELIVERY_PHRASE]->interrupt(spell_elapsed);
+    }
+    else
+    {
+      //Con::printf("LEAVING DELIVERY (C)");
+      phrases[DELIVERY_PHRASE]->stop(spell_elapsed);
+    }
+  }
+}
+
+void afxMagicSpell::enter_linger_state_c(F32 starttime)
+{
+  if (impacted_obj)
+    impacted_cons_id = constraint_mgr->setReferenceObject(IMPACTED_OBJECT_CONS, impacted_obj);
+  else if (impacted_scope_id > 0)
+    impacted_cons_id = constraint_mgr->setReferenceObjectByScopeId(IMPACTED_OBJECT_CONS, impacted_scope_id, impacted_is_shape);
+  else
+    constraint_mgr->setReferencePoint(IMPACTED_OBJECT_CONS, impact_pos, impact_norm);
+  constraint_mgr->setReferencePoint(IMPACT_POINT_CONS, impact_pos, impact_norm);
+  constraint_mgr->setReferenceObject(MISSILE_CONS, 0);
+
+  setup_impact_fx();
+  if (phrases[IMPACT_PHRASE])
+    phrases[IMPACT_PHRASE]->start(starttime, spell_elapsed); //START
+
+  setup_linger_fx();
+  if (phrases[LINGER_PHRASE])
+  {
+    phrases[LINGER_PHRASE]->start(starttime, spell_elapsed); //START
+  }
+}
+
+void afxMagicSpell::leave_linger_state_c()
+{
+  if (phrases[LINGER_PHRASE])
+  {
+    if (marks_mask & MARK_INTERRUPT_LINGER)
+    {
+      //Con::printf("INTERRUPT LINGER (C)");
+      phrases[LINGER_PHRASE]->interrupt(spell_elapsed);
+    }
+    else
+    {
+      //Con::printf("LEAVING LINGER (C)");
+      phrases[LINGER_PHRASE]->stop(spell_elapsed);
+    }
+  }
+}
+
+void afxMagicSpell::sync_client(U16 marks, U8 state, F32 elapsed, F32 spell_elapsed)
+{
+  //Con::printf("SYNC marks=%d old_state=%s state=%s elapsed=%g spell_elapsed=%g",
+  //            marks, name_from_state(spell_state), name_from_state(state), elapsed,
+  //            spell_elapsed);
+
+  if (spell_state != LATE_STATE)
+    return;
+
+  marks_mask = marks;
+
+  // don't want to be started on late zoning clients
+  if (!datablock->exec_on_new_clients)
+  {
+    spell_state = DONE_STATE;
+  }
+
+  // it looks like we're ghosting pretty late and
+  // should just return to the inactive state.
+  else if ((marks & (MARK_INTERRUPTS | MARK_DEACTIVATE | MARK_SHUTDOWN)) ||
+           (((marks & MARK_IMPACT) || (marks & MARK_END_DELIVERY)) && (marks & MARK_END_LINGER)))
+  {
+    spell_state = DONE_STATE;
+  }
+
+  // it looks like we should be in the linger state.
+  else if ((marks & MARK_IMPACT) ||
+           (((marks & MARK_LAUNCH) || (marks & MARK_END_CASTING)) && (marks & MARK_END_DELIVERY)))
+  {
+    spell_state = LINGER_STATE;
+    this->spell_elapsed = spell_elapsed;
+    enter_linger_state_c(spell_elapsed-elapsed);
+  }
+
+  // it looks like we should be in the delivery state.
+  else if ((marks & MARK_LAUNCH) || (marks & MARK_END_CASTING))
+  {
+    spell_state = DELIVERY_STATE;
+    this->spell_elapsed = spell_elapsed;
+    enter_delivery_state_c(spell_elapsed-elapsed);
+  }
+
+  // it looks like we should be in the casting state.
+  else if (marks & MARK_ACTIVATE)
+  {
+    spell_state = CASTING_STATE; //SPELL_STATE
+    this->spell_elapsed = spell_elapsed;
+    enter_casting_state_c(spell_elapsed-elapsed);
+  }
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// public:
+
+void afxMagicSpell::postSpellEvent(U8 event)
+{
+  setMaskBits(StateEventMask);
+
+  switch (event)
+  {
+  case ACTIVATE_EVENT:
+    marks_mask |= MARK_ACTIVATE;
+    break;
+  case LAUNCH_EVENT:
+    marks_mask |= MARK_LAUNCH;
+    setMaskBits(LaunchEventMask);
+    break;
+  case IMPACT_EVENT:
+    marks_mask |= MARK_IMPACT;
+    setMaskBits(ImpactEventMask);
+    break;
+  case SHUTDOWN_EVENT:
+    marks_mask |= MARK_SHUTDOWN;
+    break;
+  case DEACTIVATE_EVENT:
+    marks_mask |= MARK_DEACTIVATE;
+    break;
+  case INTERRUPT_PHASE_EVENT:
+    if (spell_state == CASTING_STATE)
+      marks_mask |= MARK_END_CASTING;
+    else if (spell_state == DELIVERY_STATE)
+      marks_mask |= MARK_END_DELIVERY;
+    else if (spell_state == LINGER_STATE)
+      marks_mask |= MARK_END_LINGER;
+    break;
+  case INTERRUPT_SPELL_EVENT:
+    if (spell_state == CASTING_STATE)
+      marks_mask |= MARK_INTERRUPT_CASTING;
+    else if (spell_state == DELIVERY_STATE)
+      marks_mask |= MARK_INTERRUPT_DELIVERY;
+    else if (spell_state == LINGER_STATE)
+      marks_mask |= MARK_INTERRUPT_LINGER;
+    else if (spell_state == CLEANUP_STATE)
+      marks_mask |= MARK_INTERRUPT_CLEANUP;
+    break;
+  }
+}
+
+void afxMagicSpell::resolveTimeFactors()
+{
+  for (S32 i = 0; i < NUM_PHRASES; i++)
+    tfactors[i] *= overall_time_factor;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+void afxMagicSpell::finish_startup()
+{
+#if !defined(BROKEN_POINT_IN_WATER)
+  // test if caster is in water
+  if (caster)
+  {
+    Point3F pos = caster->getPosition();
+    if (caster->pointInWater(pos))
+      exec_conds_mask |= CASTER_IN_WATER;
+  }
+#endif
+
+  resolveTimeFactors();
+
+  init_constraints();
+  init_scoping();
+
+  postSpellEvent(afxMagicSpell::ACTIVATE_EVENT);
+}
+
+// static
+afxMagicSpell*
+afxMagicSpell::cast_spell(afxMagicSpellData* datablock, ShapeBase* caster, SceneObject* target, SimObject* extra)
+{
+  AssertFatal(datablock != NULL, "Datablock is missing.");
+  AssertFatal(caster != NULL, "Caster is missing.");
+
+  afxMagicSpellData* exeblock = datablock;
+
+  SimObject* param_holder = new SimObject();
+  if (!param_holder->registerObject())
+  {
+    Con::errorf("afxMagicSpell: failed to register parameter object.");
+    delete param_holder;
+    return 0;
+  }
+
+  param_holder->assignDynamicFieldsFrom(datablock, arcaneFX::sParameterFieldPrefix);
+  if (extra)
+  {
+    // copy dynamic fields from the extra object to the param holder
+    param_holder->assignDynamicFieldsFrom(extra, arcaneFX::sParameterFieldPrefix);
+  }
+
+  if (datablock->isMethod("onPreactivate"))
+  {
+     // CALL SCRIPT afxMagicSpellData::onPreactivate(%params, %caster, %target, %extra)
+     bool result = datablock->onPreactivate_callback(param_holder, caster, target, extra);
+     if (!result)
+     {
+   #if defined(TORQUE_DEBUG)
+       Con::warnf("afxMagicSpell: onPreactivate() returned false, spell aborted.");
+   #endif
+       Sim::postEvent(param_holder, new ObjectDeleteEvent, Sim::getCurrentTime());
+       return 0;
+     }
+  }
+
+  // make a temp datablock clone if there are substitutions
+  if (datablock->getSubstitutionCount() > 0)
+  {
+    datablock = new afxMagicSpellData(*exeblock, true);
+    exeblock->performSubstitutions(datablock, param_holder);
+  }
+
+  // create a new spell instance
+  afxMagicSpell* spell = new afxMagicSpell(caster, target);
+  spell->setDataBlock(datablock);
+  spell->exeblock = exeblock;
+  spell->setExtra(extra);
+
+  // copy dynamic fields from the param holder to the spell
+  spell->assignDynamicFieldsFrom(param_holder, arcaneFX::sParameterFieldPrefix);
+  Sim::postEvent(param_holder, new ObjectDeleteEvent, Sim::getCurrentTime());
+
+  // register
+  if (!spell->registerObject())
+  {
+    Con::errorf("afxMagicSpell: failed to register spell instance.");
+    Sim::postEvent(spell, new ObjectDeleteEvent, Sim::getCurrentTime());
+    return 0;
+  }
+  registerForCleanup(spell);
+
+  spell->activate();
+
+  return spell;
+}
+
+IMPLEMENT_GLOBAL_CALLBACK( DisplayScreenMessage, void, (GameConnection* client, const char* message), (client, message),
+   "Called to display a screen message.\n"
+   "@ingroup AFX\n" );
+
+void afxMagicSpell::displayScreenMessage(ShapeBase* caster, const char* msg)
+{
+  if (!caster)
+    return;
+
+  GameConnection* client = caster->getControllingClient();
+  if (client)
+    DisplayScreenMessage_callback(client, msg);
+}
+
+Point3F afxMagicSpell::getShapeImpactPos(SceneObject* obj)
+{
+  Point3F pos = obj->getRenderPosition();
+  if (obj->getTypeMask() & CorpseObjectType)
+    pos.z += 0.5f;
+  else
+    pos.z += (obj->getObjBox().len_z()/2);
+  return pos;
+}
+
+void afxMagicSpell::restoreObject(SceneObject* obj)
+{
+  if (obj->getScopeId() == caster_scope_id && dynamic_cast<ShapeBase*>(obj) != NULL)
+  {
+    caster_scope_id = 0;
+    caster = (ShapeBase*)obj;
+    caster_field = caster;
+    deleteNotify(caster);
+    processAfter(caster);
+  }
+
+  if (obj->getScopeId() == target_scope_id)
+  {
+    target_scope_id = 0;
+    target = obj;
+    target_field = target;
+    deleteNotify(target);
+  }
+
+  if (obj->getScopeId() == impacted_scope_id)
+  {
+    impacted_scope_id = 0;
+    impacted_obj = obj;
+    deleteNotify(impacted_obj);
+  }
+}
+
+bool afxMagicSpell::activationCallInit(bool postponed)
+{
+  if (postponed && (!started_with_newop || !postpone_activation))
+  {
+    Con::errorf("afxMagicSpell::activate() -- activate() is only required when creating a spell with the \"new\" operator "
+                "and the postponeActivation field is set to \"true\".");
+    return false;
+  }
+
+  if (!caster_field)
+  {
+    Con::errorf("afxMagicSpell::activate() -- no spellcaster specified.");
+    return false;
+  }
+
+  caster = dynamic_cast<ShapeBase*>(caster_field);
+  if (!caster)
+  {
+    Con::errorf("afxMagicSpell::activate() -- spellcaster is not a ShapeBase derived object.");
+    return false;
+  }
+
+  if (target_field)
+  {
+    target = dynamic_cast<SceneObject*>(target_field);
+    if (!target)
+      Con::warnf("afxMagicSpell::activate() -- target is not a SceneObject derived object.");
+  }
+
+  return true;
+}
+
+void afxMagicSpell::activate()
+{
+  // separating the final part of startup allows the calling script
+  // to make certain types of calls on the returned spell that need
+  // to happen prior to object registration.
+  Sim::postEvent(this, new SpellFinishStartupEvent, Sim::getCurrentTime());
+
+  caster_field = caster;
+  target_field = target;
+
+  // CALL SCRIPT afxMagicSpellData::onActivate(%spell, %caster, %target)
+  datablock->onActivate_callback(this, caster, target);
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// console methods/functions
+
+DefineEngineMethod(afxMagicSpell, getCaster, S32, (),,
+                   "Returns ID of the spell's caster object.\n\n"
+                   "@ingroup AFX")
+{
+  ShapeBase* caster = object->getCaster();
+  return (caster) ? caster->getId() : -1;
+}
+
+DefineEngineMethod(afxMagicSpell, getTarget, S32, (),,
+                   "Returns ID of the spell's target object.\n\n"
+                   "@ingroup AFX")
+{
+  SceneObject* target = object->getTarget();
+  return (target) ? target->getId() : -1;
+}
+
+DefineEngineMethod(afxMagicSpell, getMissile, S32, (),,
+                   "Returns ID of the spell's magic-missile object.\n\n"
+                   "@ingroup AFX")
+{
+  afxMagicMissile* missile = object->getMissile();
+  return (missile) ? missile->getId() : -1;
+}
+
+DefineEngineMethod(afxMagicSpell, getImpactedObject, S32, (),,
+                   "Returns ID of impacted-object for the spell.\n\n"
+                   "@ingroup AFX")
+{
+  SceneObject* imp_obj = object->getImpactedObject();
+  return (imp_obj) ? imp_obj->getId() : -1;
+}
+
+ConsoleMethod(afxMagicSpell, setTimeFactor, void, 3, 4, "(F32 factor) or (string phase, F32 factor)"
+              "Sets the time-factor for the spell, either overall or for a specific phrase.\n\n"
+              "@ingroup AFX")
+{
+  if (argc == 3)
+    object->setTimeFactor(dAtof(argv[2]));
+  else
+  {
+    if (dStricmp(argv[2], "overall") == 0)
+      object->setTimeFactor(dAtof(argv[3]));
+    else if (dStricmp(argv[2], "casting") == 0)
+      object->setTimeFactor(afxMagicSpell::CASTING_PHRASE, dAtof(argv[3]));
+    else if (dStricmp(argv[2], "launch") == 0)
+      object->setTimeFactor(afxMagicSpell::LAUNCH_PHRASE, dAtof(argv[3]));
+    else if (dStricmp(argv[2], "delivery") == 0)
+      object->setTimeFactor(afxMagicSpell::DELIVERY_PHRASE, dAtof(argv[3]));
+    else if (dStricmp(argv[2], "impact") == 0)
+      object->setTimeFactor(afxMagicSpell::IMPACT_PHRASE, dAtof(argv[3]));
+    else if (dStricmp(argv[2], "linger") == 0)
+      object->setTimeFactor(afxMagicSpell::LINGER_PHRASE, dAtof(argv[3]));
+    else
+      Con::errorf("afxMagicSpell::setTimeFactor() -- unknown spell phrase [%s].", argv[2].getStringValue());
+  }
+}
+
+DefineEngineMethod(afxMagicSpell, interruptStage, void, (),,
+                   "Interrupts the current stage of a magic spell causing it to move onto the next one.\n\n"
+                   "@ingroup AFX")
+{
+  object->postSpellEvent(afxMagicSpell::INTERRUPT_PHASE_EVENT);
+}
+
+DefineEngineMethod(afxMagicSpell, interrupt, void, (),,
+                   "Interrupts and deletes a running magic spell.\n\n"
+                   "@ingroup AFX")
+{
+  object->postSpellEvent(afxMagicSpell::INTERRUPT_SPELL_EVENT);
+}
+
+DefineEngineMethod(afxMagicSpell, activate, void, (),,
+                   "Activates a magic spell that was started with postponeActivation=true.\n\n"
+                   "@ingroup AFX")
+{
+  if (object->activationCallInit(true))
+    object->activate();
+}
+
+DefineEngineFunction(castSpell, S32,	(afxMagicSpellData* datablock, ShapeBase* caster, SceneObject* target, SimObject* extra),
+										(nullAsType<afxMagicSpellData*>(), nullAsType<ShapeBase*>(), nullAsType<SceneObject*>(), nullAsType<SimObject*>()),		
+                     "Instantiates the magic spell defined by datablock and cast by caster.\n\n"
+                     "@ingroup AFX")
+{
+  if (!datablock)
+  {
+    Con::errorf("castSpell() -- missing valid spell datablock.");
+    return 0;
+  }
+
+  if (!caster)
+  {
+    Con::errorf("castSpell() -- missing valid spellcaster.");
+    return 0;
+  }
+
+  // target is optional (depends on spell)
+
+  // note -- we must examine all arguments prior to calling cast_spell because
+  // it calls Con::executef() which will overwrite the argument array.
+  afxMagicSpell* spell = afxMagicSpell::cast_spell(datablock, caster, target, extra);
+
+  return (spell) ? spell->getId() : 0;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//

+ 390 - 0
Engine/source/afx/afxMagicSpell.h

@@ -0,0 +1,390 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
+// Copyright (C) 2015 Faust Logic, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _AFX_MAGIC_SPELL_H_
+#define _AFX_MAGIC_SPELL_H_
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#include "core/util/tVector.h"
+#include "console/typeValidators.h"
+
+#include "afxChoreographer.h"
+#include "afxEffectDefs.h"
+#include "afxEffectWrapper.h"
+#include "afxMagicMissile.h"
+
+class afxChoreographerData;
+class afxMagicMissileData;
+class afxEffectWrapperData;
+class SceneObject;
+class afxMagicSpell;
+
+class afxMagicSpellDefs
+{
+public:
+  enum
+  {
+    CASTING_PHRASE,
+    LAUNCH_PHRASE,
+    DELIVERY_PHRASE,
+    IMPACT_PHRASE,
+    LINGER_PHRASE,
+    NUM_PHRASES
+  };
+};
+
+class afxMagicSpellData : public afxChoreographerData, public afxMagicSpellDefs
+{
+  typedef afxChoreographerData Parent;
+
+  class ewValidator : public TypeValidator
+  {
+    U32 id;
+  public:
+    ewValidator(U32 id) { this->id = id; }
+    void validateType(SimObject *object, void *typePtr);
+  };
+
+  bool                  do_id_convert;
+
+public:
+  F32                   casting_dur;
+  F32                   delivery_dur;
+  F32                   linger_dur;
+  //
+  S32                   n_casting_loops;
+  S32                   n_delivery_loops;
+  S32                   n_linger_loops;
+  //
+  F32                   extra_casting_time;
+  F32                   extra_delivery_time;
+  F32                   extra_linger_time;
+  //
+  bool                  do_move_interrupts;
+  F32                   move_interrupt_speed;
+  //
+  afxMagicMissileData*  missile_db;
+  bool                  launch_on_server_signal;
+  U32                   primary_target_types;
+  //
+  afxEffectWrapperData* dummy_fx_entry;
+
+                        // various effects lists
+  afxEffectList         casting_fx_list;
+  afxEffectList         launch_fx_list;
+  afxEffectList         delivery_fx_list;
+  afxEffectList         impact_fx_list;
+  afxEffectList         linger_fx_list;
+
+  void                  pack_fx(BitStream* stream, const afxEffectList& fx, bool packed);
+  void                  unpack_fx(BitStream* stream, afxEffectList& fx);
+
+public:
+  /*C*/                 afxMagicSpellData();
+  /*C*/                 afxMagicSpellData(const afxMagicSpellData&, bool = false);
+
+  virtual void          reloadReset();
+
+  virtual bool          onAdd();
+  virtual void          packData(BitStream*);
+  virtual void          unpackData(BitStream*);
+  virtual bool          writeField(StringTableEntry fieldname, const char* value);
+
+  bool                  preload(bool server, String &errorStr);
+
+  void                  gatherConstraintDefs(Vector<afxConstraintDef>&); 
+
+  virtual bool          allowSubstitutions() const { return true; }
+
+  static void           initPersistFields();
+
+  DECLARE_CONOBJECT(afxMagicSpellData);
+  DECLARE_CATEGORY("AFX");
+
+  /// @name Callbacks
+  /// @{
+  DECLARE_CALLBACK( void, onDamage, (afxMagicSpell* spell, const char* label, const char* flaver, U32 target_id, F32 amount, U8 n, Point3F pos, F32 ad_amount, F32 radius, F32 impulse) );
+  DECLARE_CALLBACK( void, onDeactivate, (afxMagicSpell* spell) );
+  DECLARE_CALLBACK( void, onInterrupt, (afxMagicSpell* spell, ShapeBase* caster) );
+  DECLARE_CALLBACK( void, onLaunch, (afxMagicSpell* spell, ShapeBase* caster, SceneObject* target, afxMagicMissile* missile) );
+  DECLARE_CALLBACK( void, onImpact, (afxMagicSpell* spell, ShapeBase* caster, SceneObject* impacted, Point3F pos, Point3F normal) );
+  DECLARE_CALLBACK( bool, onPreactivate, (SimObject* param_holder, ShapeBase* caster, SceneObject* target, SimObject* extra) );
+  DECLARE_CALLBACK( void, onActivate, (afxMagicSpell* spell, ShapeBase* caster, SceneObject* target) );
+  /// @}
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxMagicSpell
+
+class ShapeBase;
+class GameConnection;
+class afxEffectVector;
+class afxConstraint;
+class afxConstraintMgr;
+class afxMagicMissile;
+class afxChoreographer;
+class afxPhrase;
+
+class afxMagicSpell : public afxChoreographer, public afxMagicSpellDefs
+{
+  typedef afxChoreographer Parent;
+  friend class afxMagicMissile;
+
+  enum MaskBits 
+  {
+    MagicMissileMask      = Parent::NextFreeMask << 0,
+    StateEventMask        = Parent::NextFreeMask << 1,
+    LaunchEventMask       = Parent::NextFreeMask << 2,
+    ImpactEventMask       = Parent::NextFreeMask << 3,
+    SyncEventMask         = Parent::NextFreeMask << 4,
+    RemapConstraintMask   = Parent::NextFreeMask << 5, // CONSTRAINT REMAPPING
+    NextFreeMask          = Parent::NextFreeMask << 6
+  };
+
+public:
+  enum
+  {
+    NULL_EVENT,
+    ACTIVATE_EVENT,
+    LAUNCH_EVENT,
+    IMPACT_EVENT,
+    SHUTDOWN_EVENT,
+    DEACTIVATE_EVENT,
+    INTERRUPT_PHASE_EVENT,
+    INTERRUPT_SPELL_EVENT
+  };
+
+  enum
+  {
+    INACTIVE_STATE,
+    CASTING_STATE,
+    DELIVERY_STATE,
+    LINGER_STATE,
+    CLEANUP_STATE,
+    DONE_STATE,
+    LATE_STATE
+  };
+
+  enum {
+    MARK_ACTIVATE           = BIT(0),
+    MARK_LAUNCH             = BIT(1),
+    MARK_IMPACT             = BIT(2),
+    MARK_SHUTDOWN           = BIT(3),
+    MARK_DEACTIVATE         = BIT(4),
+    MARK_END_CASTING        = BIT(5),
+    MARK_END_DELIVERY       = BIT(6),
+    MARK_END_LINGER         = BIT(7),
+    MARK_INTERRUPT_CASTING  = BIT(8),
+    MARK_INTERRUPT_DELIVERY = BIT(9),
+    MARK_INTERRUPT_LINGER   = BIT(10),
+    MARK_INTERRUPT_CLEANUP  = BIT(11),
+    //
+    MARK_ENDINGS = MARK_END_CASTING | MARK_END_DELIVERY | MARK_END_LINGER,
+    MARK_INTERRUPTS = MARK_INTERRUPT_CASTING | MARK_INTERRUPT_DELIVERY | MARK_INTERRUPT_LINGER | MARK_INTERRUPT_CLEANUP
+  };
+
+  class ObjectDeleteEvent : public SimEvent
+  {
+  public:
+    void process(SimObject *obj) { if (obj) obj->deleteObject(); }
+  };
+
+private:
+  static StringTableEntry  CASTER_CONS;
+  static StringTableEntry  TARGET_CONS;
+  static StringTableEntry  MISSILE_CONS;
+  static StringTableEntry  CAMERA_CONS;
+  static StringTableEntry  LISTENER_CONS;
+  static StringTableEntry  IMPACT_POINT_CONS;
+  static StringTableEntry  IMPACTED_OBJECT_CONS;
+
+private:
+  afxMagicSpellData*   datablock;
+  SimObject*           exeblock;
+  afxMagicMissileData* missile_db;
+
+  ShapeBase*    caster;
+  SceneObject*  target;
+  SimObject*    caster_field;
+  SimObject*    target_field;
+
+  U16           caster_scope_id;
+  U16           target_scope_id;
+  bool          target_is_shape;
+
+  bool          constraints_initialized;
+  bool          scoping_initialized;
+
+  U8            spell_state;
+  F32           spell_elapsed;
+
+  afxConstraintID listener_cons_id;
+  afxConstraintID caster_cons_id;
+  afxConstraintID target_cons_id;
+  afxConstraintID impacted_cons_id;
+  afxConstraintID camera_cons_id;
+  SceneObject*  camera_cons_obj;
+
+  afxPhrase*    phrases[NUM_PHRASES];
+  F32           tfactors[NUM_PHRASES];
+
+  bool          notify_castbar;
+  F32           overall_time_factor;
+
+  U16           marks_mask;
+
+private:
+  void          init();
+  bool          state_expired();
+  F32           state_elapsed();
+  void          init_constraints();
+  void          init_scoping();
+  void          setup_casting_fx();
+  void          setup_launch_fx();
+  void          setup_delivery_fx();
+  void          setup_impact_fx();
+  void          setup_linger_fx();
+  bool          cleanup_over();
+  bool          is_caster_moving();
+  bool          is_caster_client(ShapeBase* caster, GameConnection* conn);
+  bool          is_impact_in_water(SceneObject* obj, const Point3F& p);
+
+protected:
+  virtual bool  remap_builtin_constraint(SceneObject*, const char* cons_name); // CONSTRAINT REMAPPING
+  virtual void  pack_constraint_info(NetConnection* conn, BitStream* stream);
+  virtual void  unpack_constraint_info(NetConnection* conn, BitStream* stream);
+
+private:
+  afxMagicMissile*  missile;
+  bool              missile_is_armed;
+  SceneObject*      impacted_obj;
+  Point3F           impact_pos;
+  Point3F           impact_norm;
+  U16               impacted_scope_id;
+  bool              impacted_is_shape;
+
+  void          init_missile_s(afxMagicMissileData* mm);
+  void          launch_missile_s();
+
+  void          init_missile_c(afxMagicMissileData* mm);
+  void          launch_missile_c();
+
+public:
+  virtual void  impactNotify(const Point3F& p, const Point3F& n, SceneObject*);
+  virtual void  executeScriptEvent(const char* method, afxConstraint*, 
+                                   const MatrixF& pos, const char* data);
+  virtual void  inflictDamage(const char * label, const char* flavor, SimObjectId target,
+                              F32 amt, U8 count, F32 ad_amt, F32 rad, Point3F pos, F32 imp);
+
+public:
+  /*C*/         afxMagicSpell();
+  /*C*/         afxMagicSpell(ShapeBase* caster, SceneObject* target);
+  /*D*/         ~afxMagicSpell();
+
+    // STANDARD OVERLOADED METHODS //
+  virtual bool  onNewDataBlock(GameBaseData* dptr, bool reload);
+  virtual void  processTick(const Move*);
+  virtual void  advanceTime(F32 dt);
+  virtual bool  onAdd();
+  virtual void  onRemove();
+  virtual void  onDeleteNotify(SimObject*);
+  virtual U32   packUpdate(NetConnection*, U32, BitStream*);
+  virtual void  unpackUpdate(NetConnection*, BitStream*);
+
+  virtual void  sync_with_clients();
+  void          finish_startup();
+
+  static void   initPersistFields();
+
+  DECLARE_CONOBJECT(afxMagicSpell);
+  DECLARE_CATEGORY("AFX");
+
+private:
+  void          process_server();
+  //
+  void          change_state_s(U8 pending_state);
+  //
+  void          enter_casting_state_s();
+  void          leave_casting_state_s();
+  void          enter_delivery_state_s();
+  void          leave_delivery_state_s();
+  void          enter_linger_state_s();
+  void          leave_linger_state_s();
+  void          enter_done_state_s();
+
+private:
+  void          process_client(F32 dt);
+  //
+  void          change_state_c(U8 pending_state);
+  //
+  void          enter_casting_state_c(F32 starttime);
+  void          leave_casting_state_c();
+  void          enter_delivery_state_c(F32 starttime);
+  void          leave_delivery_state_c();
+  void          enter_linger_state_c(F32 starttime);
+  void          leave_linger_state_c();
+  //
+  void          sync_client(U16 marks, U8 state, F32 state_elapsed, F32 spell_elapsed);
+
+public:
+  void          postSpellEvent(U8 event);
+  void          resolveTimeFactors();
+
+  void          setTimeFactor(F32 f) { overall_time_factor = (f > 0) ? f : 1.0f; }
+  F32           getTimeFactor() { return overall_time_factor; }
+  void          setTimeFactor(U8 phase, F32 f) { tfactors[phase] = (f > 0) ? f : 1.0f; }
+  F32           getTimeFactor(U8 phase) { return tfactors[phase]; }
+
+  ShapeBase*        getCaster() const { return caster; }
+  SceneObject*      getTarget() const { return target; }
+  afxMagicMissile*  getMissile() const { return missile; }
+  SceneObject*      getImpactedObject() const { return impacted_obj; }
+
+  virtual void      restoreObject(SceneObject*);
+
+  bool              activationCallInit(bool postponed=false);
+  void              activate();
+
+public:
+  static afxMagicSpell* cast_spell(afxMagicSpellData*, ShapeBase* caster, SceneObject* target, SimObject* extra);
+  
+  static void     displayScreenMessage(ShapeBase* caster, const char* msg);
+  static Point3F  getShapeImpactPos(SceneObject*);
+};
+
+inline bool afxMagicSpell::is_caster_moving()
+{
+  return (caster) ? (caster->getVelocity().len() > datablock->move_interrupt_speed) : false;
+}
+
+inline bool afxMagicSpell::is_caster_client(ShapeBase* caster, GameConnection* conn)
+{
+  return (caster) ? (caster->getControllingClient() == conn) : false;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#endif // _AFX_MAGIC_SPELL_H_

+ 196 - 0
Engine/source/afx/afxPhrase.cpp

@@ -0,0 +1,196 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
+// Copyright (C) 2015 Faust Logic, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#include "afx/arcaneFX.h"
+
+#include "afx/afxEffectVector.h"
+#include "afx/afxPhrase.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxPhrase
+
+void
+afxPhrase::init_fx(S32 group_index)
+{
+  fx->ev_init(init_chor, *init_fx_list, on_server, will_stop, init_time_factor, init_dur, group_index); 
+}
+
+//~~~~~~~~~~~~~~~~~~~~//
+
+afxPhrase::afxPhrase(bool on_server, bool will_stop)
+{
+  this->on_server = on_server;
+  this->will_stop = will_stop;
+
+  init_fx_list = NULL;
+  init_dur = 0.0f;
+  init_chor = NULL;
+  init_time_factor = 1.0f;
+
+  fx = new afxEffectVector;
+  fx2 = NULL;
+  starttime = 0;
+  dur = 0;
+
+  n_loops = 1;
+  loop_cnt = 1;
+
+  extra_time = 0.0f;
+  extra_stoptime = 0.0f;
+}
+
+afxPhrase::~afxPhrase()
+{
+  delete fx;
+  delete fx2;
+};
+
+void 
+afxPhrase::init(afxEffectList& fx_list, F32 dur, afxChoreographer* chor, F32 time_factor, 
+                S32 n_loops, S32 group_index, F32 extra_time)
+{
+  init_fx_list = &fx_list;
+  init_dur = dur;
+  init_chor = chor;
+  init_time_factor = time_factor;
+
+  this->n_loops = n_loops;
+  this->extra_time = extra_time;
+  this->dur = (init_dur < 0) ? init_dur : init_dur*init_time_factor;
+
+  init_fx(group_index);
+}
+
+void
+afxPhrase::start(F32 startstamp, F32 timestamp)
+{
+  starttime = startstamp;
+
+  F32 loopstart = timestamp - startstamp;
+
+  if (dur > 0 && loopstart > dur)
+  {
+    loop_cnt += (S32) (loopstart/dur);
+    loopstart = mFmod(loopstart, dur);
+  }
+
+  if (!fx->empty())
+    fx->start(loopstart);
+}
+
+void
+afxPhrase::update(F32 dt, F32 timestamp)
+{
+  if (fx->isActive())
+    fx->update(dt);
+
+  if (fx2 && fx2->isActive())
+    fx2->update(dt);
+
+  if (extra_stoptime > 0 && timestamp > extra_stoptime)
+  {
+    stop(timestamp);
+  }
+}
+
+void
+afxPhrase::stop(F32 timestamp)
+{
+  if (extra_time > 0 && !(extra_stoptime > 0))
+  {
+    extra_stoptime = timestamp + extra_time;
+    return;
+  }
+
+  if (fx->isActive())
+    fx->stop();
+
+  if (fx2 && fx2->isActive())
+    fx2->stop();
+}
+
+bool
+afxPhrase::expired(F32 timestamp)
+{
+  if (dur < 0)
+    return false;
+
+  return ((timestamp - starttime) > loop_cnt*dur);
+}
+
+F32
+afxPhrase::elapsed(F32 timestamp)
+{
+  return (timestamp - starttime);
+}
+
+bool
+afxPhrase::recycle(F32 timestamp)
+{
+  if (n_loops < 0 || loop_cnt < n_loops)
+  {
+    if (fx2)
+      delete fx2;
+
+    fx2 = fx;
+
+    fx = new afxEffectVector;
+    init_fx();
+
+    if (fx2 && !fx2->empty())
+      fx2->stop();
+
+    if (!fx->empty())
+      fx->start(0.0F);
+
+    loop_cnt++;
+    return true;
+  }
+
+  return false;
+}
+
+void
+afxPhrase::interrupt(F32 timestamp)
+{
+  if (fx->isActive())
+    fx->interrupt();
+
+  if (fx2 && fx2->isActive())
+    fx2->interrupt();
+}
+
+F32 afxPhrase::calcDoneTime()
+{
+  return starttime + fx->getTotalDur();
+}
+
+F32 afxPhrase::calcAfterLife()
+{
+  return fx->getAfterLife();
+}
+
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//

+ 87 - 0
Engine/source/afx/afxPhrase.h

@@ -0,0 +1,87 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
+// Copyright (C) 2015 Faust Logic, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _AFX_PHRASE_H_
+#define _AFX_PHRASE_H_
+
+#include "afxEffectVector.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxPhrase
+
+class afxChoreographer;
+class afxConstraintMgr;
+class afxEffectVector;
+
+class afxPhrase
+{
+protected:
+  afxEffectList*    init_fx_list;
+  F32               init_dur;
+  afxChoreographer* init_chor;
+  F32               init_time_factor;
+  F32               extra_time;
+
+  afxEffectVector*  fx;
+  afxEffectVector*  fx2;
+
+  bool              on_server;
+  bool              will_stop;
+
+  F32               starttime;
+  F32               dur;
+  S32               n_loops;
+  S32               loop_cnt;
+  F32               extra_stoptime;
+
+  void              init_fx(S32 group_index=0);
+
+public:
+  /*C*/             afxPhrase(bool on_server, bool will_stop);
+  virtual           ~afxPhrase();
+
+  virtual void      init(afxEffectList&, F32 dur, afxChoreographer*, F32 time_factor, 
+                         S32 n_loops, S32 group_index=0, F32 extra_time=0.0f);
+
+  virtual void      start(F32 startstamp, F32 timestamp);
+  virtual void      update(F32 dt, F32 timestamp);
+  virtual void      stop(F32 timestamp);
+  virtual void      interrupt(F32 timestamp);
+  virtual bool      expired(F32 timestamp);
+  virtual bool      recycle(F32 timestamp);
+  virtual F32       elapsed(F32 timestamp);
+
+  bool              isEmpty() { return fx->empty(); }
+  bool              isInfinite() { return (init_dur < 0); }
+  F32               calcDoneTime();
+  F32               calcAfterLife();
+  bool              willStop() { return will_stop; }
+  bool              onServer() { return on_server; }
+  S32               count() { return fx->count(); }
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#endif // _AFX_PHRASE_H_

+ 176 - 0
Engine/source/afx/afxRenderHighlightMgr.cpp

@@ -0,0 +1,176 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
+// Copyright (C) 2015 Faust Logic, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// The afxRenderHighlightMgr class is adapted from the resource,
+// "Silhoute selection via postFX for Torque3D" posted by Konrad Kiss.
+// http://www.garagegames.com/community/resources/view/17821
+// Supporting code mods in other areas of the engine are marked as
+// "(selection-highlight)".
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#include "platform/platform.h"
+#include "afxRenderHighlightMgr.h"
+
+#include "scene/sceneManager.h"
+#include "scene/sceneRenderState.h"
+#include "materials/sceneData.h"
+#include "materials/matInstance.h"
+//#include "materials/materialFeatureTypes.h"
+#include "materials/processedMaterial.h"
+#include "postFx/postEffect.h"
+#include "gfx/gfxTransformSaver.h"
+#include "gfx/gfxDebugEvent.h"
+#include "math/util/matrixSet.h"
+
+IMPLEMENT_CONOBJECT( afxRenderHighlightMgr );
+
+afxRenderHighlightMgr::afxRenderHighlightMgr()
+   : RenderTexTargetBinManager(  RenderPassManager::RIT_Mesh, 
+                                 1.0f, 
+                                 1.0f,
+                                 GFXFormatR8G8B8A8,
+                                 Point2I( 512, 512 ) )
+{
+   mNamedTarget.registerWithName( "highlight" );
+   mTargetSizeType = WindowSize;
+}
+
+afxRenderHighlightMgr::~afxRenderHighlightMgr()
+{
+}
+
+PostEffect* afxRenderHighlightMgr::getSelectionEffect()
+{
+   if ( !mSelectionEffect )
+      mSelectionEffect = dynamic_cast<PostEffect*>( Sim::findObject( "afxHighlightPostFX" ) );
+   
+   return mSelectionEffect;
+}
+
+bool afxRenderHighlightMgr::isSelectionEnabled()
+{
+   return getSelectionEffect() && getSelectionEffect()->isEnabled();
+}
+
+void afxRenderHighlightMgr::addElement( RenderInst *inst )
+{
+   // Skip out if we don't have the selection post 
+   // effect enabled at this time.
+   if ( !isSelectionEnabled() )  
+      return;
+
+   // Skip it if we don't have a selection material.
+   BaseMatInstance *matInst = getMaterial( inst );
+   if ( !matInst || !matInst->needsSelectionHighlighting() )   
+      return;
+
+   internalAddElement(inst);
+}
+
+void afxRenderHighlightMgr::render( SceneRenderState *state )  
+{
+   PROFILE_SCOPE( RenderSelectionMgr_Render );
+   
+   if ( !isSelectionEnabled() )
+      return;
+
+   const U32 binSize = mElementList.size();
+
+   // If this is a non-diffuse pass or we have no objects to
+   // render then tell the effect to skip rendering.
+   if ( !state->isDiffusePass() || binSize == 0 )
+   {
+      getSelectionEffect()->setSkip( true );
+      return;
+   }
+
+   GFXDEBUGEVENT_SCOPE( RenderSelectionMgr_Render, ColorI::GREEN );
+
+   GFXTransformSaver saver;
+
+   // Tell the superclass we're about to render, preserve contents
+   const bool isRenderingToTarget = _onPreRender( state, true );
+
+   // Clear all the buffers to black.
+   //GFX->clear( GFXClearTarget, ColorI::BLACK, 1.0f, 0);
+   GFX->clear( GFXClearTarget, ColorI::ZERO, 1.0f, 0);
+
+   // Restore transforms
+   MatrixSet &matrixSet = getRenderPass()->getMatrixSet();
+   matrixSet.restoreSceneViewProjection();
+
+   // init loop data
+   SceneData sgData;
+   sgData.init( state, SceneData::HighlightBin );
+
+   for( U32 j=0; j<binSize; )
+   {
+      MeshRenderInst *ri = static_cast<MeshRenderInst*>(mElementList[j].inst);
+
+      setupSGData( ri, sgData );
+
+      BaseMatInstance *mat = ri->matInst;
+
+      U32 matListEnd = j;
+
+      while( mat && mat->setupPass( state, sgData ) )
+      {
+         U32 a;
+         for( a=j; a<binSize; a++ )
+         {
+            MeshRenderInst *passRI = static_cast<MeshRenderInst*>(mElementList[a].inst);
+
+            if ( newPassNeeded( ri, passRI ) )
+               break;
+
+            matrixSet.setWorld(*passRI->objectToWorld);
+            matrixSet.setView(*passRI->worldToCamera);
+            matrixSet.setProjection(*passRI->projection);
+            mat->setTransforms(matrixSet, state);
+
+            mat->setSceneInfo(state, sgData);
+            mat->setBuffers(passRI->vertBuff, passRI->primBuff);
+
+            if ( passRI->prim )
+               GFX->drawPrimitive( *passRI->prim );
+            else
+               GFX->drawPrimitive( passRI->primBuffIndex );
+         }
+         matListEnd = a;
+         setupSGData( ri, sgData );
+      }
+
+      // force increment if none happened, otherwise go to end of batch
+      j = ( j == matListEnd ) ? j+1 : matListEnd;
+   }
+
+   // Finish up.
+   if ( isRenderingToTarget )
+      _onPostRender();
+
+   // Make sure the effect is gonna render.
+   getSelectionEffect()->setSkip( false );
+}

+ 76 - 0
Engine/source/afx/afxRenderHighlightMgr.h

@@ -0,0 +1,76 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
+// Copyright (C) 2015 Faust Logic, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// The afxRenderHighlightMgr class is adapted from the resource,
+// "Silhoute selection via postFX for Torque3D" posted by Konrad Kiss.
+// http://www.garagegames.com/community/resources/view/17821
+// Supporting code mods in other areas of the engine are marked as
+// "(selection-highlight)".
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _afxRENDERHIGHLIGHTMGR_H_
+#define _afxRENDERHIGHLIGHTMGR_H_
+
+#ifndef _TEXTARGETBIN_MGR_H_
+#include "renderInstance/renderTexTargetBinManager.h"
+#endif
+
+
+class PostEffect;
+
+
+///
+class afxRenderHighlightMgr : public RenderTexTargetBinManager
+{  
+   typedef RenderTexTargetBinManager Parent;
+
+public:
+
+   afxRenderHighlightMgr();
+   virtual ~afxRenderHighlightMgr();
+
+   /// Returns the selection post effect.
+   PostEffect* getSelectionEffect();
+
+   /// Returns true if the highlight post effect is
+   /// enabled and the selection buffer should be updated.
+   bool isSelectionEnabled();
+
+   // RenderBinManager
+   virtual void addElement( RenderInst *inst );
+   virtual void render( SceneRenderState *state );
+
+   // ConsoleObject
+   DECLARE_CONOBJECT( afxRenderHighlightMgr );
+
+protected:
+
+   SimObjectPtr<PostEffect> mSelectionEffect;  
+
+};
+
+
+#endif // _afxRENDERHIGHLIGHTMGR_H_

+ 469 - 0
Engine/source/afx/afxResidueMgr.cpp

@@ -0,0 +1,469 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
+// Copyright (C) 2015 Faust Logic, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#include "afx/arcaneFX.h"
+
+#include "ts/tsShapeInstance.h"
+
+#include "afx/ce/afxZodiacMgr.h"
+#include "afx/ce/afxModel.h"
+#include "afx/afxResidueMgr.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+int QSORT_CALLBACK afxResidueMgr::ResidueList::compare_residue(const void* p1, const void* p2)
+{
+  const afxResidueMgr::Residue** pd1 = (const afxResidueMgr::Residue**)p1;
+  const afxResidueMgr::Residue** pd2 = (const afxResidueMgr::Residue**)p2;
+  
+  return int(((char*)(*pd1)->data.simobject) - ((char*)(*pd2)->data.simobject));
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+inline void afxResidueMgr::ResidueList::swap_array_ptrs()
+{
+  Vector<Residue*>* tmp = m_array;
+  m_array = m_scratch_array;
+  m_scratch_array = tmp;
+}
+
+void afxResidueMgr::ResidueList::free_residue(Residue* residue)
+{
+  if (the_mgr == NULL)
+    return;
+
+  if (the_mgr->requires_delete_tracking(residue))
+    the_mgr->disable_delete_tracking(residue);
+  the_mgr->free_residue(residue);
+}
+
+afxResidueMgr::ResidueList::ResidueList()
+{
+  VECTOR_SET_ASSOCIATION(m_array_a);
+  VECTOR_SET_ASSOCIATION(m_array_b);
+
+  m_array = &m_array_a;
+  m_scratch_array = &m_array_b ;
+
+  m_dirty = false;
+  m_pending = -1;
+}
+
+afxResidueMgr::ResidueList::~ResidueList()
+{
+  clear();
+}
+
+void afxResidueMgr::ResidueList::clear()
+{
+  if (the_mgr)
+  {
+    for (S32 i = 0; i < m_array->size(); i++)
+    {
+      Residue* r = (*m_array)[i];
+      the_mgr->free_residue(r);
+    }
+  }
+
+  m_array_a.clear();
+  m_array_b.clear();
+}
+
+void afxResidueMgr::ResidueList::sort()
+{
+  dQsort(m_array->address(), m_array->size(), sizeof(Residue*), compare_residue);
+  m_dirty = false;
+}
+
+void afxResidueMgr::ResidueList::fadeAndCull(U32 now)
+{
+  for (S32 i = 0; i < m_array->size(); i++)
+  {
+    Residue* r = (*m_array)[i];
+
+    // done
+    if (now >= r->stop_time)
+    {
+      free_residue(r);
+    }
+    // fading
+    else if (now >= r->fade_time)
+    {
+      r->fade = 1.0f - ((F32)(now - r->fade_time))/((F32)(r->stop_time - r->fade_time));
+      m_scratch_array->push_back(r);
+    }
+    // opaque
+    else
+    {
+      r->fade = 1.0f;
+      m_scratch_array->push_back(r);
+    }
+  }
+
+  m_array->clear();
+  swap_array_ptrs();
+}
+
+// removes all residue with datablock matching obj
+void afxResidueMgr::ResidueList::stripMatchingObjects(SimObject* db, bool del_notify)
+{
+  if (del_notify)
+  {
+    for (S32 i = 0; i < m_array->size(); i++)
+    {
+      Residue* r = (*m_array)[i];
+      if (db == r->data.simobject && the_mgr != NULL)
+        the_mgr->free_residue(r);
+      else
+        m_scratch_array->push_back(r);
+    }
+  }
+  else
+  {
+    for (S32 i = 0; i < m_array->size(); i++)
+    {
+      Residue* r = (*m_array)[i];
+      if (db == r->data.simobject)
+        free_residue(r);
+      else
+        m_scratch_array->push_back(r);
+    }
+  }
+
+  m_array->clear();
+  swap_array_ptrs();
+}
+
+void afxResidueMgr::ResidueList::add(Residue* residue)
+{
+  m_array->push_back(residue);
+  m_dirty = true;
+}
+
+void afxResidueMgr::manage_residue(const Residue* r)
+{
+  if (r == NULL || r->fade < 0.01f)
+    return;
+
+  if (r->type == ZODIAC)
+  {
+    LinearColorF zode_color = ColorI(r->params.zodiac.r, r->params.zodiac.g, r->params.zodiac.b, r->params.zodiac.a);
+
+    afxZodiacData* zd = (afxZodiacData*) r->data.zodiac;
+    if (zd->blend_flags == afxZodiacDefs::BLEND_SUBTRACTIVE)
+       zode_color *= r->fade;
+    else
+       zode_color.alpha *= r->fade;
+
+    Point3F zode_pos(r->params.zodiac.pos_x, r->params.zodiac.pos_y, r->params.zodiac.pos_z);
+    Point2F zode_vrange(r->params.zodiac.vrange_dn, r->params.zodiac.vrange_dn);
+    if (r->params.zodiac.on_terrain)
+    {
+      afxZodiacMgr::addTerrainZodiac(zode_pos, r->params.zodiac.rad, zode_color, r->params.zodiac.ang, zd);
+    }
+    else
+    {
+      afxZodiacMgr::addInteriorZodiac(zode_pos, r->params.zodiac.rad, zode_vrange, zode_color, r->params.zodiac.ang, zd);
+    }
+  }
+  else if (r->type == MODEL)
+  {
+    r->data.model->setFadeAmount(r->fade);
+  }
+}
+
+void afxResidueMgr::ResidueList::manage()
+{
+  if (the_mgr == NULL)
+    return;
+
+  S32 n_residue = m_array->size(); 
+
+  for (S32 x = 0; x < n_residue; x++)
+    the_mgr->manage_residue((*m_array)[x]);
+}
+
+U32 afxResidueMgr::ResidueList::findPendingBestBump(U32 look_max)
+{
+  U32 soonest = 1000*60*60*24;
+  m_pending = -1;
+
+  U32 n = m_array->size();
+  for (U32 i = 0; i < n && i < look_max; i++)
+  {
+    Residue* r = (*m_array)[i];
+    if (r->stop_time < soonest)
+    {
+      soonest = r->stop_time;
+      m_pending = i;
+    }
+  }
+
+  return soonest;
+}
+
+void afxResidueMgr::ResidueList::bumpPending()
+{
+  if (m_pending >= 0 && m_pending < m_array->size())
+  {
+    Residue* r = (*m_array)[m_pending];
+    m_array->erase(m_pending);
+    free_residue(r);
+  }
+
+  m_pending = -1;
+}
+
+bool afxResidueMgr::requires_delete_tracking(Residue* r)
+{
+  return (r->type == MODEL);
+}
+
+void afxResidueMgr::enable_delete_tracking(Residue* r)
+{
+  deleteNotify(r->data.simobject);
+}
+
+void afxResidueMgr::disable_delete_tracking(Residue* r)
+{
+  clearNotify(r->data.simobject);
+  r->data.simobject->deleteObject();
+  r->data.simobject = 0;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+afxResidueMgr*  afxResidueMgr::the_mgr = NULL;
+U32             afxResidueMgr::m_max_residue_objs = 256;
+bool            afxResidueMgr::enabled = true;
+
+IMPLEMENT_CONOBJECT(afxResidueMgr);
+
+ConsoleDocClass( afxResidueMgr,
+   "@brief A class that manages certain AFX effects that can persist for long durations.\n\n"
+
+   "A class that manages certain AFX effects that can persist much longer than the duration of choreographers.\n"
+
+   "@ingroup AFX\n"
+);
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// free-list management
+
+afxResidueMgr::Residue* afxResidueMgr::alloc_free_pool_block()
+{
+  // allocate new block for the free-list
+  m_free_pool_blocks.push_back(new Residue[FREE_POOL_BLOCK_SIZE]);
+  
+  // link them onto the free-list
+  Residue* new_block = m_free_pool_blocks.last();
+  for (U32 i = 0; i < FREE_POOL_BLOCK_SIZE - 1; i++)
+    new_block[i].next = &new_block[i + 1];
+  
+  // tail of free-list points to NULL
+  new_block[FREE_POOL_BLOCK_SIZE - 1].next = NULL;
+
+  return new_block;
+}
+
+afxResidueMgr::Residue* afxResidueMgr::alloc_residue()
+{
+  // need new free-list-block if m_next_free is null
+  if (!m_next_free)
+    m_next_free = alloc_free_pool_block();
+  
+  // pop new residue from head of free-list
+  Residue* residue = m_next_free;
+  m_next_free = residue->next;
+  residue->next = NULL;
+
+  return residue;
+}
+
+void afxResidueMgr::free_residue(Residue* residue)
+{
+  if (residue && residue->type == ZODIAC)
+  {
+    if (residue->data.zodiac && residue->data.zodiac->isTempClone())
+    {
+      delete residue->data.zodiac;
+      residue->data.zodiac = 0;
+    }
+  }
+
+  // push residue onto head of free-list
+  residue->next = m_next_free;
+  m_next_free = residue;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+void afxResidueMgr::deleteResidueObject(SimObject* obj, bool del_notify)
+{
+  m_managed.stripMatchingObjects(obj, del_notify);
+}
+
+void afxResidueMgr::bump_residue()
+{
+  if (m_managed.findPendingBestBump())
+    m_managed.bumpPending();
+}
+
+void afxResidueMgr::add_residue(Residue* residue)
+{
+  AssertFatal(residue != NULL, "residue pointer is NULL.");
+
+  if (m_managed.size() >= m_max_residue_objs)
+    bump_residue();
+
+  m_managed.add(residue);
+  manage_residue(residue);
+
+  if (requires_delete_tracking(residue))
+    enable_delete_tracking(residue);
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+afxResidueMgr::afxResidueMgr()
+{ 
+  mObjBox.minExtents.set(-1e7, -1e7, -1e7);
+  mObjBox.maxExtents.set( 1e7,  1e7,  1e7);
+  mWorldBox.minExtents.set(-1e7, -1e7, -1e7);
+  mWorldBox.maxExtents.set( 1e7,  1e7,  1e7);
+  
+  m_next_free = NULL;
+
+  VECTOR_SET_ASSOCIATION(m_free_pool_blocks);
+}
+
+afxResidueMgr::~afxResidueMgr()
+{
+  cleanup();
+}
+
+void afxResidueMgr::cleanup()
+{
+  m_managed.clear();
+
+  m_next_free = NULL;
+
+  for (S32 i = 0; i < m_free_pool_blocks.size(); i++)
+    delete [] m_free_pool_blocks[i];
+
+  m_free_pool_blocks.clear();
+}
+
+void afxResidueMgr::onDeleteNotify(SimObject* obj)
+{
+  deleteResidueObject(obj, true);
+  Parent::onDeleteNotify(obj);
+}
+
+void afxResidueMgr::residueAdvanceTime()
+{
+  U32 now = Platform::getVirtualMilliseconds();
+  m_managed.fadeAndCull(now);
+  m_managed.sortIfDirty();
+  m_managed.manage();
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//
+// add ZODIAC residue
+void afxResidueMgr::add_interior_zodiac(F32 dur, F32 fade_dur, afxZodiacData* zode, const Point3F& pos, 
+                                       F32 rad, const Point2F& vrange, const LinearColorF& col, F32 ang)
+{
+  add_zodiac(dur, fade_dur, zode, pos, rad, vrange, col, ang, false);
+}
+
+void afxResidueMgr::add_terrain_zodiac(F32 dur, F32 fade_dur, afxZodiacData* zode, const Point3F& pos, 
+                                       F32 rad, const LinearColorF& col, F32 ang)
+{
+  static Point2F vrange(0.0, 0.0);
+  add_zodiac(dur, fade_dur, zode, pos, rad, vrange, col, ang, true);
+}
+
+void afxResidueMgr::add_zodiac(F32 dur, F32 fade_dur, afxZodiacData* zode, const Point3F& pos, 
+                               F32 rad, const Point2F& vrange, const LinearColorF& col, F32 ang, bool on_terrain)
+{
+  if (m_max_residue_objs == 0 || dur <= 0 || the_mgr == NULL)
+    return;
+
+  ColorI col_i = LinearColorF(col).toColorI();
+  U32 now = Platform::getVirtualMilliseconds();
+
+  Residue* residue = the_mgr->alloc_residue();
+  //
+  residue->type = ZODIAC;
+  residue->data.zodiac = zode;
+  residue->fade_time = now + (U32)(dur*1000);
+  residue->stop_time = residue->fade_time + (U32)(fade_dur*1000);
+  residue->fade = 1.0f;
+  //
+  residue->params.zodiac.pos_x = pos.x;
+  residue->params.zodiac.pos_y = pos.y;
+  residue->params.zodiac.pos_z = pos.z;
+  residue->params.zodiac.rad = rad;
+  residue->params.zodiac.vrange_dn = vrange.x;
+  residue->params.zodiac.vrange_up = vrange.y;
+  residue->params.zodiac.r = col_i.red;
+  residue->params.zodiac.g = col_i.green;
+  residue->params.zodiac.b = col_i.blue;
+  residue->params.zodiac.a = col_i.alpha;
+  residue->params.zodiac.ang = ang;
+  residue->params.zodiac.on_terrain = on_terrain;
+  //
+  residue->next = 0;
+  
+  the_mgr->add_residue(residue);
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//
+// add MODEL residue
+
+void afxResidueMgr::add(F32 dur, F32 fade_dur, afxModel* model)
+{
+  if (m_max_residue_objs == 0 || dur <= 0 || the_mgr == NULL)
+    return;
+
+  U32 now = Platform::getVirtualMilliseconds();
+  
+  Residue* residue = the_mgr->alloc_residue();
+  //
+  residue->type = MODEL;
+  residue->data.model = model;
+  residue->fade_time = now + (U32)(dur*1000);
+  residue->stop_time = residue->fade_time + (U32)(fade_dur*1000);
+  residue->fade = 1.0f;
+  //
+  residue->next = 0;
+
+  the_mgr->add_residue(residue);
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//

+ 179 - 0
Engine/source/afx/afxResidueMgr.h

@@ -0,0 +1,179 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
+// Copyright (C) 2015 Faust Logic, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _AFX_RESIDUE_MGR_H_
+#define _AFX_RESIDUE_MGR_H_
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+class afxZodiacData;
+class afxModel;
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxResidueMgr
+//
+//    Manage transient objects in the world.
+
+class afxResidueMgr : public GameBase
+{
+
+  typedef GameBase Parent;
+ 
+  enum { 
+    ZODIAC,
+    MODEL
+  };
+
+  struct Residue
+  {
+    struct ZodiacParams
+    {
+      F32   pos_x, pos_y, pos_z;
+      F32   rad, vrange_dn, vrange_up;
+      U8    r,g,b,a;
+      F32   ang;
+      bool  on_terrain;
+    };  
+    
+    union ResidueParams
+    {
+      ZodiacParams  zodiac;
+    };
+    
+    union ResidueData
+    {
+      afxZodiacData*  zodiac;
+      afxModel*       model;
+      SimObject*      simobject;
+    };
+
+    
+    U32           type;
+    ResidueData   data;
+    ResidueParams params;
+    U32           fade_time;
+    U32           stop_time;
+    F32           fade;
+        
+    Residue*      next;
+  };
+
+  class ResidueList
+  {
+    Vector<Residue*>  m_array_a;
+    Vector<Residue*>  m_array_b;
+
+    Vector<Residue*>* m_array;
+    Vector<Residue*>* m_scratch_array;
+    bool              m_dirty;
+    S32               m_pending;
+
+    void              swap_array_ptrs();
+    void              free_residue(Residue*);
+
+  public:
+    /*C*/             ResidueList();
+    /*D*/             ~ResidueList();
+
+    void              clear();
+    S32               size() { return m_array->size(); }
+    bool              empty() { return m_array->empty(); }
+    void              sortIfDirty() { if (m_dirty) sort(); }
+
+    void              sort();
+    void              fadeAndCull(U32 now);
+    void              stripMatchingObjects(SimObject* db, bool del_notify=false);
+    void              add(Residue*);
+
+    void              manage();
+
+    U32               findPendingBestBump(U32 look_max=256);
+    void              bumpPending();
+
+    static int QSORT_CALLBACK compare_residue(const void* p1, const void* p2);
+  };
+
+  friend class ResidueList;
+
+private:
+  enum { FREE_POOL_BLOCK_SIZE = 256 };
+
+  static afxResidueMgr* the_mgr;
+
+  static U32        m_max_residue_objs;
+  static bool       enabled;
+
+  ResidueList       m_managed;
+  
+  Vector<Residue*>  m_free_pool_blocks;
+  Residue*          m_next_free;
+
+  Residue*          alloc_free_pool_block();
+  Residue*          alloc_residue();
+  void              free_residue(Residue*);
+
+  void              bump_residue();
+  void              add_residue(Residue*);
+  static void       add_zodiac(F32 dur, F32 fade_dur, afxZodiacData*, const Point3F& pos, F32 rad, 
+                               const Point2F& vrange, const LinearColorF& col, F32 ang, bool on_terrain);
+ 
+protected:
+  void              deleteResidueObject(SimObject* obj, bool del_notify=false); 
+
+  void              manage_residue(const Residue* r);
+
+  bool              requires_delete_tracking(Residue*);
+  void              enable_delete_tracking(Residue*);
+  void              disable_delete_tracking(Residue*);
+   
+public:                     
+  /*C*/             afxResidueMgr();
+  /*D*/             ~afxResidueMgr();
+
+  void              cleanup();
+  virtual void      onDeleteNotify(SimObject *obj);
+    
+public:
+  void              residueAdvanceTime();
+
+                    // ZODIAC
+  static void       add_terrain_zodiac(F32 dur, F32 fade_dur, afxZodiacData*, const Point3F& pos, F32 rad,
+                                       const LinearColorF& col, F32 ang);
+  static void       add_interior_zodiac(F32 dur, F32 fade_dur, afxZodiacData*, const Point3F& pos, F32 rad,
+                                        const Point2F& vrange, const LinearColorF& col, F32 ang);
+
+                    // MODEL
+  static void       add(F32 dur, F32 fade_dur, afxModel*);
+
+  static afxResidueMgr* getMaster() { return the_mgr; }
+  static void           setMaster(afxResidueMgr* m) { the_mgr = m; }
+
+  DECLARE_CONOBJECT(afxResidueMgr);
+  DECLARE_CATEGORY("AFX");
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#endif // _AFX_RESIDUE_MGR_H_

+ 1173 - 0
Engine/source/afx/afxSelectron.cpp

@@ -0,0 +1,1173 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
+// Copyright (C) 2015 Faust Logic, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#include "afx/arcaneFX.h"
+
+#include "console/engineAPI.h"
+#include "T3D/gameBase/gameConnection.h"
+#include "sfx/sfxSystem.h"
+
+#include "afx/afxChoreographer.h"
+#include "afx/afxSelectron.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxSelectronData::ewValidator
+//
+// When an effect is added using "addEffect", this validator intercepts the value
+// and adds it to the dynamic effects list.
+//
+void afxSelectronData::ewValidator::validateType(SimObject* object, void* typePtr)
+{
+  afxSelectronData* sele_data = dynamic_cast<afxSelectronData*>(object);
+  afxEffectBaseData** ew = (afxEffectBaseData**)(typePtr);
+
+  if (sele_data && ew)
+  {
+    switch (id)
+    {
+    case MAIN_PHRASE:
+      sele_data->main_fx_list.push_back(*ew);
+      break;
+    case SELECT_PHRASE:
+      sele_data->select_fx_list.push_back(*ew);
+      break;
+    case DESELECT_PHRASE:
+      sele_data->deselect_fx_list.push_back(*ew);
+      break;
+    }
+    *ew = 0;
+  }
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+class SelectronFinishStartupEvent : public SimEvent
+{
+public:
+  void process(SimObject* obj)
+  {
+     afxSelectron* selectron = dynamic_cast<afxSelectron*>(obj);
+     if (selectron)
+       selectron->finish_startup();
+  }
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxSelectronData
+
+IMPLEMENT_CO_DATABLOCK_V1(afxSelectronData);
+
+ConsoleDocClass( afxSelectronData,
+   "@brief Defines the properties of an afxSelectronData.\n\n"
+
+   "@ingroup afxChoreographers\n"
+   "@ingroup AFX\n"
+   "@ingroup Datablocks\n"
+);
+
+afxSelectronData::afxSelectronData()
+{
+  main_dur = 0.0f;
+  select_dur = 0.0f;
+  deselect_dur = 0.0f;
+
+  n_main_loops = 1;
+  n_select_loops = 1;
+  n_deselect_loops = 1;
+
+  registered = false;
+
+  obj_type_style = 0;
+  obj_type_mask = 0;
+
+  // dummy entry holds effect-wrapper pointer while a special validator
+  // grabs it and adds it to an appropriate effects list
+  dummy_fx_entry = NULL;
+
+  // marked true if datablock ids need to
+  // be converted into pointers
+  do_id_convert = false;
+}
+
+afxSelectronData::afxSelectronData(const afxSelectronData& other, bool temp_clone) : afxChoreographerData(other, temp_clone)
+{
+  main_dur = other.main_dur;
+  select_dur = other.select_dur;
+  deselect_dur = other.deselect_dur;
+  n_main_loops = other.n_main_loops;
+  n_select_loops = other.n_select_loops;
+  n_deselect_loops = other.n_deselect_loops;
+  registered = false;
+  obj_type_style = other.obj_type_style;
+  obj_type_mask = other.obj_type_mask;
+  dummy_fx_entry = other.dummy_fx_entry;
+  do_id_convert = other.do_id_convert;
+
+  main_fx_list = other.main_fx_list;
+  select_fx_list = other.select_fx_list;
+  deselect_fx_list = other.deselect_fx_list;
+}
+
+afxSelectronData::~afxSelectronData()
+{
+  if (registered && !isTempClone())
+    arcaneFX::unregisterSelectronData(this);
+}
+
+void afxSelectronData::reloadReset()
+{
+  main_fx_list.clear();
+  select_fx_list.clear();
+  deselect_fx_list.clear();
+}
+
+#define myOffset(field) Offset(field, afxSelectronData)
+
+void afxSelectronData::initPersistFields()
+{
+   static ewValidator _mainPhrase(MAIN_PHRASE);
+   static ewValidator _selectPhrase(SELECT_PHRASE);
+   static ewValidator _deselectPhrase(DESELECT_PHRASE);
+
+  addField("mainDur",                   TypeF32,    myOffset(main_dur),
+    "...");
+  addField("selectDur",                 TypeF32,    myOffset(select_dur),
+    "...");
+  addField("deselectDur",               TypeF32,    myOffset(deselect_dur),
+    "...");
+  addField("mainRepeats",               TypeS32,    myOffset(n_main_loops),
+    "...");
+  addField("selectRepeats",             TypeS32,    myOffset(n_select_loops),
+    "...");
+  addField("deselectRepeats",           TypeS32,    myOffset(n_deselect_loops),
+    "...");
+  addField("selectionTypeMask",         TypeS32,    myOffset(obj_type_mask),
+    "...");
+  addField("selectionTypeStyle",        TypeS8,     myOffset(obj_type_style),
+    "...");
+
+  // effect lists
+  // for each of these, dummy_fx_entry is set and then a validator adds it to the appropriate effects list
+  addFieldV("addMainEffect",      TYPEID<afxEffectBaseData>(),  myOffset(dummy_fx_entry),  &_mainPhrase,
+    "...");
+  addFieldV("addSelectEffect",    TYPEID<afxEffectBaseData>(),  myOffset(dummy_fx_entry),  &_selectPhrase,
+    "...");
+  addFieldV("addDeselectEffect",  TYPEID<afxEffectBaseData>(),  myOffset(dummy_fx_entry),  &_deselectPhrase,
+    "...");
+
+  // deprecated
+  addField("numMainLoops",      TypeS32,      myOffset(n_main_loops),
+    "...");
+  addField("numSelectLoops",    TypeS32,      myOffset(n_select_loops),
+    "...");
+  addField("numDeselectLoops",  TypeS32,      myOffset(n_deselect_loops),
+    "...");
+
+  Parent::initPersistFields();
+
+  // disallow some field substitutions
+  disableFieldSubstitutions("addMainEffect");
+  disableFieldSubstitutions("addSelectEffect");
+  disableFieldSubstitutions("addDeselectEffect");
+}
+
+bool afxSelectronData::onAdd()
+{
+  if (Parent::onAdd() == false)
+    return false;
+
+  return true;
+}
+
+void afxSelectronData::pack_fx(BitStream* stream, const afxEffectList& fx, bool packed)
+{
+  stream->writeInt(fx.size(), EFFECTS_PER_PHRASE_BITS);
+  for (int i = 0; i < fx.size(); i++)
+    writeDatablockID(stream, fx[i], packed);
+}
+
+void afxSelectronData::unpack_fx(BitStream* stream, afxEffectList& fx)
+{
+  fx.clear();
+  S32 n_fx = stream->readInt(EFFECTS_PER_PHRASE_BITS);
+  for (int i = 0; i < n_fx; i++)
+    fx.push_back((afxEffectWrapperData*)readDatablockID(stream));
+}
+
+void afxSelectronData::packData(BitStream* stream)
+{
+  Parent::packData(stream);
+
+  stream->write(main_dur);
+  stream->write(select_dur);
+  stream->write(deselect_dur);
+  stream->write(n_main_loops);
+  stream->write(n_select_loops);
+  stream->write(n_deselect_loops);
+  stream->write(obj_type_style);
+  stream->write(obj_type_mask);
+
+  pack_fx(stream, main_fx_list, packed);
+  pack_fx(stream, select_fx_list, packed);
+  pack_fx(stream, deselect_fx_list, packed);
+}
+
+void afxSelectronData::unpackData(BitStream* stream)
+{
+  Parent::unpackData(stream);
+
+  stream->read(&main_dur);
+  stream->read(&select_dur);
+  stream->read(&deselect_dur);
+  stream->read(&n_main_loops);
+  stream->read(&n_select_loops);
+  stream->read(&n_deselect_loops);
+  stream->read(&obj_type_style);
+  stream->read(&obj_type_mask);
+
+  do_id_convert = true;
+  unpack_fx(stream, main_fx_list);
+  unpack_fx(stream, select_fx_list);
+  unpack_fx(stream, deselect_fx_list);
+}
+
+inline void expand_fx_list(afxEffectList& fx_list, const char* tag)
+{
+  for (S32 i = 0; i < fx_list.size(); i++)
+  {
+    SimObjectId db_id = SimObjectId((uintptr_t)fx_list[i]);
+    if (db_id != 0)
+    {
+      // try to convert id to pointer
+      if (!Sim::findObject(db_id, fx_list[i]))
+      {
+        Con::errorf(ConsoleLogEntry::General,
+          "afxSelectronData::preload() -- bad datablockId: 0x%x (%s)",
+          db_id, tag);
+      }
+    }
+  }
+}
+
+bool afxSelectronData::preload(bool server, String &errorStr)
+{
+  if (!Parent::preload(server, errorStr))
+    return false;
+
+  // Resolve objects transmitted from server
+  if (!server)
+  {
+    if (do_id_convert)
+    {
+      expand_fx_list(main_fx_list, "main");
+      expand_fx_list(select_fx_list, "select");
+      expand_fx_list(deselect_fx_list, "deselect");
+      do_id_convert = false;
+    }
+
+    // this is where a selectron registers itself with the rest of AFX
+    if (!registered)
+    {
+      arcaneFX::registerSelectronData(this);
+      registered = true;
+    }
+  }
+
+  return true;
+}
+
+void afxSelectronData::gatherConstraintDefs(Vector<afxConstraintDef>& defs)
+{
+  afxConstraintDef::gather_cons_defs(defs, main_fx_list);
+  afxConstraintDef::gather_cons_defs(defs, select_fx_list);
+  afxConstraintDef::gather_cons_defs(defs, deselect_fx_list);
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//
+
+DefineEngineMethod(afxSelectronData, reset, void, (),,
+                   "Resets a selectron datablock during reload.\n\n"
+                   "@ingroup AFX")
+{
+  object->reloadReset();
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxSelectron
+
+IMPLEMENT_CO_NETOBJECT_V1(afxSelectron);
+
+ConsoleDocClass( afxSelectron,
+   "@brief A choreographer for selection effects.\n\n"
+
+   "@ingroup afxChoreographers\n"
+   "@ingroup AFX\n"
+);
+
+StringTableEntry  afxSelectron::CAMERA_CONS;
+StringTableEntry  afxSelectron::LISTENER_CONS;
+StringTableEntry  afxSelectron::FREE_TARGET_CONS;
+
+void afxSelectron::init()
+{
+  client_only = true;
+  mNetFlags.clear(Ghostable | ScopeAlways);
+  mNetFlags.set(IsGhost);
+
+  // setup static predefined constraint names
+  if (CAMERA_CONS == 0)
+  {
+    CAMERA_CONS = StringTable->insert("camera");
+    LISTENER_CONS = StringTable->insert("listener");
+    FREE_TARGET_CONS = StringTable->insert("freeTarget");
+  }
+
+  datablock = NULL;
+  exeblock = NULL;
+
+  constraints_initialized = false;
+
+  effect_state = (U8) INACTIVE_STATE;
+  effect_elapsed = 0;
+
+  // define named constraints
+  constraint_mgr->defineConstraint(CAMERA_CONSTRAINT, CAMERA_CONS);
+  constraint_mgr->defineConstraint(POINT_CONSTRAINT,  LISTENER_CONS);
+  constraint_mgr->defineConstraint(POINT_CONSTRAINT,  FREE_TARGET_CONS);
+
+  for (S32 i = 0; i < NUM_PHRASES; i++)
+    phrases[i] = NULL;
+
+  time_factor = 1.0f;
+  camera_cons_obj = 0;
+
+  marks_mask = 0;
+}
+
+afxSelectron::afxSelectron()
+{
+  started_with_newop = true;
+  init();
+}
+
+afxSelectron::afxSelectron(bool not_default)
+{
+  started_with_newop = false;
+  init();
+}
+
+afxSelectron::~afxSelectron()
+{
+  for (S32 i = 0; i < NUM_PHRASES; i++)
+  {
+    if (phrases[i])
+    {
+      phrases[i]->interrupt(effect_elapsed);
+      delete phrases[i];
+    }
+  }
+
+  if (datablock && datablock->isTempClone())
+  {
+    delete datablock;
+    datablock = 0;
+  }
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+// STANDARD OVERLOADED METHODS //
+
+bool afxSelectron::onNewDataBlock(GameBaseData* dptr, bool reload)
+{
+  datablock = dynamic_cast<afxSelectronData*>(dptr);
+  if (!datablock || !Parent::onNewDataBlock(dptr, reload))
+    return false;
+
+  exeblock = datablock;
+
+  return true;
+}
+
+void afxSelectron::processTick(const Move* m)
+{
+	Parent::processTick(m);
+
+  // don't process moves or client ticks
+  if (m != 0 || isClientObject())
+    return;
+
+  process_server();
+}
+
+void afxSelectron::advanceTime(F32 dt)
+{
+  Parent::advanceTime(dt);
+
+  process_client(dt);
+}
+
+bool afxSelectron::onAdd()
+{
+  if (started_with_newop)
+  {
+    Con::errorf("afxSelectron::onAdd() -- selectrons cannot be created with the \"new\" operator. Use startSelectron() instead.");
+    return false;
+  }
+
+  NetConnection* conn = NetConnection::getConnectionToServer();
+  if (!conn || !Parent::onAdd())
+    return false;
+
+  conn->addObject(this);
+
+  return true;
+}
+
+void afxSelectron::onRemove()
+{
+  getContainer()->removeObject(this);
+  Parent::onRemove();
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+U32 afxSelectron::packUpdate(NetConnection* conn, U32 mask, BitStream* stream)
+{
+  U32 retMask = Parent::packUpdate(conn, mask, stream);
+
+  // InitialUpdate
+  if (stream->writeFlag(mask & InitialUpdateMask))
+  {
+    stream->write(time_factor);
+
+    GameConnection* gconn = dynamic_cast<GameConnection*>(conn);
+    bool zoned_in = (gconn) ? gconn->isZonedIn() : false;
+    if (stream->writeFlag(zoned_in))
+      pack_constraint_info(conn, stream);
+  }
+
+  // StateEvent or SyncEvent
+  if (stream->writeFlag((mask & StateEventMask) || (mask & SyncEventMask)))
+  {
+    stream->write(marks_mask);
+    stream->write(effect_state);
+    stream->write(effect_elapsed);
+  }
+
+  // SyncEvent
+  bool do_sync_event = ((mask & SyncEventMask) && !(mask & InitialUpdateMask));
+  if (stream->writeFlag(do_sync_event))
+  {
+    pack_constraint_info(conn, stream);
+  }
+
+  return retMask;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//
+
+void afxSelectron::unpackUpdate(NetConnection * conn, BitStream * stream)
+{
+  Parent::unpackUpdate(conn, stream);
+
+  bool initial_update = false;
+  bool zoned_in = true;
+  bool do_sync_event = false;
+  U8 new_marks_mask = 0;
+  U8 new_state = INACTIVE_STATE;
+  F32 new_elapsed = 0;
+
+  // InitialUpdate Only
+  if (stream->readFlag())
+  {
+    initial_update = true;
+
+    stream->read(&time_factor);
+
+    // if client is marked as fully zoned in
+    if ((zoned_in = stream->readFlag()) == true)
+    {
+      unpack_constraint_info(conn, stream);
+      init_constraints();
+    }
+  }
+
+  // StateEvent or SyncEvent
+  // this state data is sent for both state-events and
+  // sync-events
+  if (stream->readFlag())
+  {
+    stream->read(&new_marks_mask);
+    stream->read(&new_state);
+    stream->read(&new_elapsed);
+
+    marks_mask = new_marks_mask;
+  }
+
+  // SyncEvent
+  do_sync_event = stream->readFlag();
+  if (do_sync_event)
+  {
+    unpack_constraint_info(conn, stream);
+    init_constraints();
+  }
+
+  //~~~~~~~~~~~~~~~~~~~~//
+
+  if (!zoned_in)
+    effect_state = LATE_STATE;
+
+  // need to adjust state info to get all synced up with spell on server
+  if (do_sync_event && !initial_update)
+    sync_client(new_marks_mask, new_state, new_elapsed);
+}
+
+void afxSelectron::sync_with_clients()
+{
+  setMaskBits(SyncEventMask);
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// private
+
+bool afxSelectron::state_expired()
+{
+  afxPhrase* phrase = (effect_state == ACTIVE_STATE) ? phrases[MAIN_PHRASE] : NULL;
+
+  if (phrase)
+  {
+    if (phrase->expired(effect_elapsed))
+      return (!phrase->recycle(effect_elapsed));
+    return false;
+  }
+
+  return true;
+}
+
+void afxSelectron::init_constraints()
+{
+  if (constraints_initialized)
+  {
+    //Con::printf("CONSTRAINTS ALREADY INITIALIZED");
+    return;
+  }
+
+  Vector<afxConstraintDef> defs;
+  datablock->gatherConstraintDefs(defs);
+
+  constraint_mgr->initConstraintDefs(defs, isServerObject());
+
+  if (isClientObject())
+  {
+    // find local camera
+    camera_cons_obj = get_camera();
+    if (camera_cons_obj)
+      camera_cons_id = constraint_mgr->setReferenceObject(CAMERA_CONS, camera_cons_obj);
+
+    // find local listener
+    Point3F listener_pos;
+    listener_pos = SFX->getListener().getTransform().getPosition();
+    listener_cons_id = constraint_mgr->setReferencePoint(LISTENER_CONS, listener_pos);
+
+    // find free target
+    free_target_cons_id = constraint_mgr->setReferencePoint(FREE_TARGET_CONS, arcaneFX::sFreeTargetPos);
+  }
+
+  constraint_mgr->adjustProcessOrdering(this);
+
+  constraints_initialized = true;
+}
+
+void afxSelectron::setup_main_fx()
+{
+  phrases[MAIN_PHRASE] = new afxPhrase(isServerObject(), true);
+
+  if (phrases[MAIN_PHRASE])
+    phrases[MAIN_PHRASE]->init(datablock->main_fx_list, datablock->main_dur, this, time_factor,
+                               datablock->n_main_loops);
+}
+
+void afxSelectron::setup_select_fx()
+{
+  phrases[SELECT_PHRASE] = new afxPhrase(isServerObject(), true);
+
+  if (phrases[SELECT_PHRASE])
+    phrases[SELECT_PHRASE]->init(datablock->select_fx_list, -1, this, time_factor, 1);
+}
+
+void afxSelectron::setup_deselect_fx()
+{
+  phrases[DESELECT_PHRASE] = new afxPhrase(isServerObject(), true);
+
+  if (phrases[DESELECT_PHRASE])
+    phrases[DESELECT_PHRASE]->init(datablock->deselect_fx_list, -1, this, time_factor, 1);
+}
+
+bool afxSelectron::cleanup_over()
+{
+  for (S32 i = 0; i < NUM_PHRASES; i++)
+    if (phrases[i] && !phrases[i]->isEmpty())
+      return false;
+
+  return true;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// private
+
+void afxSelectron::process_server()
+{
+  if (effect_state != INACTIVE_STATE)
+    effect_elapsed += TickSec;
+
+  U8 pending_state = effect_state;
+
+  // check for state changes
+  switch (effect_state)
+  {
+  case INACTIVE_STATE:
+    if (marks_mask & MARK_ACTIVATE)
+      pending_state = ACTIVE_STATE;
+    break;
+  case ACTIVE_STATE:
+    if (marks_mask & MARK_INTERRUPT)
+      pending_state = CLEANUP_STATE;
+    else if (marks_mask & MARK_SHUTDOWN)
+      pending_state = CLEANUP_STATE;
+    else if (state_expired())
+      pending_state = CLEANUP_STATE;
+    break;
+  case CLEANUP_STATE:
+    if (cleanup_over())
+      pending_state = DONE_STATE;
+    break;
+  }
+
+  if (effect_state != pending_state)
+    change_state_s(pending_state);
+
+  if (effect_state == INACTIVE_STATE)
+    return;
+
+  //--------------------------//
+
+  // sample the constraints
+  constraint_mgr->sample(TickSec, Platform::getVirtualMilliseconds());
+
+  for (S32 i = 0; i < NUM_PHRASES; i++)
+    if (phrases[i])
+      phrases[i]->update(TickSec, effect_elapsed);
+}
+
+void afxSelectron::change_state_s(U8 pending_state)
+{
+  if (effect_state == pending_state)
+    return;
+
+  switch (effect_state)
+  {
+  case INACTIVE_STATE:
+    break;
+  case ACTIVE_STATE:
+    leave_active_state_s();
+    break;
+  case CLEANUP_STATE:
+    break;
+  case DONE_STATE:
+    break;
+  }
+
+  effect_state = pending_state;
+
+  switch (pending_state)
+  {
+  case INACTIVE_STATE:
+    break;
+  case ACTIVE_STATE:
+    enter_active_state_s();
+    break;
+  case CLEANUP_STATE:
+    enter_cleanup_state_s();
+    break;
+  case DONE_STATE:
+    enter_done_state_s();
+    break;
+  }
+}
+
+void afxSelectron::enter_done_state_s()
+{
+  postEvent(DEACTIVATE_EVENT);
+
+  F32 done_time = effect_elapsed;
+
+  for (S32 i = 0; i < NUM_PHRASES; i++)
+  {
+    if (phrases[i])
+    {
+      F32 phrase_done;
+      if (phrases[i]->willStop() && phrases[i]->isInfinite())
+        phrase_done = effect_elapsed + phrases[i]->calcAfterLife();
+      else
+        phrase_done = phrases[i]->calcDoneTime();
+      if (phrase_done > done_time)
+        done_time = phrase_done;
+    }
+  }
+
+  F32 time_left = done_time - effect_elapsed;
+  if (time_left < 0)
+    time_left = 0;
+
+  Sim::postEvent(this, new ObjectDeleteEvent, Sim::getCurrentTime() + time_left*1000 + 500);
+
+  // CALL SCRIPT afxSelectronData::onDeactivate(%sele)
+  Con::executef(datablock, "onDeactivate", getIdString());
+}
+
+void afxSelectron::enter_active_state_s()
+{
+  // stamp constraint-mgr starting time
+  constraint_mgr->setStartTime(Platform::getVirtualMilliseconds());
+  effect_elapsed = 0;
+
+  setup_dynamic_constraints();
+
+  // start casting effects
+  setup_main_fx();
+  if (phrases[MAIN_PHRASE])
+    phrases[MAIN_PHRASE]->start(effect_elapsed, effect_elapsed);
+
+  setup_select_fx();
+  if (phrases[SELECT_PHRASE])
+    phrases[SELECT_PHRASE]->start(effect_elapsed, effect_elapsed);
+}
+
+void afxSelectron::leave_active_state_s()
+{
+  if (phrases[MAIN_PHRASE])
+    phrases[MAIN_PHRASE]->stop(effect_elapsed);
+}
+
+void afxSelectron::enter_cleanup_state_s()
+{
+  // start deselect effects
+  setup_deselect_fx();
+  if (phrases[SELECT_PHRASE])
+    phrases[SELECT_PHRASE]->interrupt(effect_elapsed);
+  if (phrases[DESELECT_PHRASE])
+    phrases[DESELECT_PHRASE]->start(effect_elapsed, effect_elapsed);
+
+  postEvent(SHUTDOWN_EVENT);
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// private
+
+void afxSelectron::process_client(F32 dt)
+{
+  effect_elapsed += dt;
+
+  U8 pending_state = effect_state;
+
+  // check for state changes
+  switch (effect_state)
+  {
+  case INACTIVE_STATE:
+    if (marks_mask & MARK_ACTIVATE)
+      pending_state = ACTIVE_STATE;
+    break;
+  case ACTIVE_STATE:
+    if (marks_mask & MARK_INTERRUPT)
+      pending_state = CLEANUP_STATE;
+    else if (marks_mask & MARK_SHUTDOWN)
+      pending_state = CLEANUP_STATE;
+    else if (state_expired())
+      pending_state = CLEANUP_STATE;
+    break;
+  case CLEANUP_STATE:
+    if (cleanup_over())
+      pending_state = DONE_STATE;
+    break;
+  }
+
+  if (effect_state != pending_state)
+    change_state_c(pending_state);
+
+  if (effect_state == INACTIVE_STATE)
+    return;
+
+  //--------------------------//
+
+  // update the listener constraint position
+  if (!listener_cons_id.undefined())
+  {
+    Point3F listener_pos;
+    listener_pos = SFX->getListener().getTransform().getPosition();
+    constraint_mgr->setReferencePoint(listener_cons_id, listener_pos);
+  }
+
+  // update the free target constraint position
+  if (!free_target_cons_id.undefined())
+  {
+    if (!arcaneFX::sFreeTargetPosValid)
+      constraint_mgr->invalidateReference(free_target_cons_id);
+    else
+      constraint_mgr->setReferencePoint(free_target_cons_id, arcaneFX::sFreeTargetPos);
+  }
+
+  // find local camera position
+  Point3F cam_pos;
+  SceneObject* current_cam = get_camera(&cam_pos);
+
+  // detect camera changes
+  if (!camera_cons_id.undefined() && current_cam != camera_cons_obj)
+  {
+    constraint_mgr->setReferenceObject(camera_cons_id, current_cam);
+    camera_cons_obj = current_cam;
+  }
+
+  // sample the constraints
+  constraint_mgr->sample(dt, Platform::getVirtualMilliseconds(), (current_cam) ? &cam_pos : 0);
+
+  // update active effects lists
+  for (S32 i = 0; i < NUM_PHRASES; i++)
+    if (phrases[i])
+      phrases[i]->update(dt, effect_elapsed);
+
+}
+
+void afxSelectron::change_state_c(U8 pending_state)
+{
+  if (effect_state == pending_state)
+    return;
+
+  switch (effect_state)
+  {
+  case INACTIVE_STATE:
+    break;
+  case ACTIVE_STATE:
+    leave_active_state_c();
+    break;
+  case CLEANUP_STATE:
+    break;
+  case DONE_STATE:
+    break;
+  }
+
+  effect_state = pending_state;
+
+  switch (pending_state)
+  {
+  case INACTIVE_STATE:
+    break;
+  case ACTIVE_STATE:
+    enter_active_state_c(effect_elapsed);
+    break;
+  case CLEANUP_STATE:
+    enter_cleanup_state_c();
+    break;
+  case DONE_STATE:
+    enter_done_state_c();
+    break;
+  }
+}
+
+void afxSelectron::enter_active_state_c(F32 starttime)
+{
+  // stamp constraint-mgr starting time
+  constraint_mgr->setStartTime(Platform::getVirtualMilliseconds() - (U32)(effect_elapsed*1000));
+  ///effect_elapsed = 0;
+
+  setup_dynamic_constraints();
+
+  setup_main_fx();
+  if (phrases[MAIN_PHRASE])
+    phrases[MAIN_PHRASE]->start(starttime, effect_elapsed);
+
+  setup_select_fx();
+  if (phrases[SELECT_PHRASE])
+    phrases[SELECT_PHRASE]->start(starttime, effect_elapsed);
+}
+
+void afxSelectron::leave_active_state_c()
+{
+  if (phrases[MAIN_PHRASE])
+  {
+    //if (marks_mask & MARK_INTERRUPT)
+    //  active_phrase->interrupt(effect_elapsed);
+    //else
+      phrases[MAIN_PHRASE]->stop(effect_elapsed);
+  }
+}
+
+void afxSelectron::enter_cleanup_state_c()
+{
+  if (!client_only)
+    return;
+
+  // start deselect effects
+  setup_deselect_fx();
+  if (phrases[DESELECT_PHRASE])
+    phrases[DESELECT_PHRASE]->start(effect_elapsed, effect_elapsed);
+
+  postEvent(SHUTDOWN_EVENT);
+}
+
+void afxSelectron::enter_done_state_c()
+{
+  if (!client_only)
+    return;
+
+  postEvent(DEACTIVATE_EVENT);
+
+  F32 done_time = effect_elapsed;
+
+  for (S32 i = 0; i < NUM_PHRASES; i++)
+  {
+    if (phrases[i])
+    {
+      F32 phrase_done;
+      if (phrases[i]->willStop() && phrases[i]->isInfinite())
+        phrase_done = effect_elapsed + phrases[i]->calcAfterLife();
+      else
+        phrase_done = phrases[i]->calcDoneTime();
+      if (phrase_done > done_time)
+        done_time = phrase_done;
+    }
+  }
+
+  F32 time_left = done_time - effect_elapsed;
+  if (time_left < 0)
+    time_left = 0;
+
+  Sim::postEvent(this, new ObjectDeleteEvent, Sim::getCurrentTime() + time_left*1000 + 500);
+
+  // CALL SCRIPT afxSelectronData::onDeactivate(%selectron)
+  Con::executef(datablock, "onDeactivate", getIdString());
+}
+
+void afxSelectron::sync_client(U16 marks, U8 state, F32 elapsed)
+{
+  //Con::printf("SYNC marks=%d old_state=%d state=%d elapsed=%g",
+  //            marks, effect_state, state, elapsed);
+
+  if (effect_state != LATE_STATE)
+    return;
+
+  marks_mask = marks;
+
+  // don't want to be started on late zoning clients
+  if (!datablock->exec_on_new_clients)
+  {
+    effect_state = DONE_STATE;
+  }
+
+  // it looks like we're ghosting pretty late and
+  // should just return to the inactive state.
+  else if (marks & (MARK_INTERRUPT | MARK_DEACTIVATE | MARK_SHUTDOWN))
+  {
+    effect_state = DONE_STATE;
+  }
+
+  // it looks like we should be in the active state.
+  else if (marks & MARK_ACTIVATE)
+  {
+    effect_state = ACTIVE_STATE;
+    effect_elapsed = elapsed;
+    enter_active_state_c(0.0);
+  }
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// public:
+
+void afxSelectron::postEvent(U8 event)
+{
+  setMaskBits(StateEventMask);
+
+  switch (event)
+  {
+  case ACTIVATE_EVENT:
+    marks_mask |= MARK_ACTIVATE;
+    break;
+  case SHUTDOWN_EVENT:
+    marks_mask |= MARK_SHUTDOWN;
+    break;
+  case DEACTIVATE_EVENT:
+    marks_mask |= MARK_DEACTIVATE;
+    break;
+  case INTERRUPT_EVENT:
+    marks_mask |= MARK_INTERRUPT;
+    break;
+  }
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+void afxSelectron::finish_startup()
+{
+  init_constraints();
+  postEvent(afxSelectron::ACTIVATE_EVENT);
+}
+
+// static
+afxSelectron*
+afxSelectron::start_selectron(SceneObject* picked, U8 subcode, SimObject* extra)
+{
+  U32 picked_type = (picked) ? picked->getTypeMask() : 0;
+
+  afxSelectronData* datablock = arcaneFX::findSelectronData(picked_type, subcode);
+  if (!datablock)
+  {
+    Con::errorf("startSelectron() -- failed to match object-type (%x/%d) to a selection-effect.",
+                picked_type, subcode);
+    return 0;
+  }
+
+  afxSelectronData* exeblock = datablock;
+
+  SimObject* param_holder = new SimObject();
+  if (!param_holder->registerObject())
+  {
+    Con::errorf("afxSelectron: failed to register parameter object.");
+    delete param_holder;
+    return 0;
+  }
+
+  param_holder->assignDynamicFieldsFrom(datablock, arcaneFX::sParameterFieldPrefix);
+  if (extra)
+  {
+    // copy dynamic fields from the extra object to the param holder
+    param_holder->assignDynamicFieldsFrom(extra, arcaneFX::sParameterFieldPrefix);
+  }
+
+  // CALL SCRIPT afxSelectronData::onPreactivate(%params, %extra)
+  const char* result = Con::executef(datablock, "onPreactivate",
+                                     Con::getIntArg(param_holder->getId()),
+                                     (extra) ? Con::getIntArg(extra->getId()) : "");
+  if (result && result[0] != '\0' && !dAtob(result))
+  {
+#if defined(TORQUE_DEBUG)
+    Con::warnf("afxSelectron: onPreactivate() returned false, effect aborted.");
+#endif
+    Sim::postEvent(param_holder, new ObjectDeleteEvent, Sim::getCurrentTime());
+    return 0;
+  }
+
+  // make a temp datablock clone if there are substitutions
+  if (datablock->getSubstitutionCount() > 0)
+  {
+    datablock = new afxSelectronData(*exeblock, true);
+    exeblock->performSubstitutions(datablock, param_holder);
+  }
+
+  // create a new selectron instance
+  afxSelectron* selectron = new afxSelectron(true);
+  selectron->setDataBlock(datablock);
+  selectron->exeblock = exeblock;
+  selectron->setExtra(extra);
+
+  // copy dynamic fields from the param holder to the selectron
+  selectron->assignDynamicFieldsFrom(param_holder, arcaneFX::sParameterFieldPrefix);
+  Sim::postEvent(param_holder, new ObjectDeleteEvent, Sim::getCurrentTime());
+
+  // register
+  if (!selectron->registerObject())
+  {
+    Con::errorf("afxSelectron: failed to register selectron instance.");
+    Sim::postEvent(selectron, new ObjectDeleteEvent, Sim::getCurrentTime());
+    return 0;
+  }
+
+  selectron->activate();
+
+  return selectron;
+}
+
+void afxSelectron::activate()
+{
+  // separating the final part of startup allows the calling script
+  // to make certain types of calls on the returned effectron that
+  // need to happen prior to constraint initialization.
+  Sim::postEvent(this, new SelectronFinishStartupEvent, Sim::getCurrentTime());
+
+  // CALL SCRIPT afxEffectronData::onActivate(%eff)
+  Con::executef(exeblock, "onActivate", getIdString());
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// console functions
+
+DefineEngineMethod(afxSelectron, setTimeFactor, void, (float factor), (1.0f),
+                   "Sets the time factor of the selectron.\n\n"
+                   "@ingroup AFX")
+{
+  object->setTimeFactor(factor);
+}
+
+DefineEngineMethod(afxSelectron, interrupt, void, (),,
+                   "Interrupts and deletes a running selectron.\n\n"
+                   "@ingroup AFX")
+{
+  object->postEvent(afxSelectron::INTERRUPT_EVENT);
+}
+
+DefineEngineMethod(afxSelectron, stopSelectron, void, (),,
+                   "Stops and deletes a running selectron.\n\n"
+                   "@ingroup AFX")
+{
+  object->postEvent(afxSelectron::INTERRUPT_EVENT);
+}
+
+DefineEngineFunction(startSelectron, S32, (SceneObject* selectedObj, unsigned int subcode, SimObject* extra),
+                     (nullAsType<SceneObject*>(), 0, nullAsType<SimObject*>()),
+                     "Instantiates a selectron.\n\n"
+                     "@ingroup AFX")
+{
+  //
+  // Start the Selectron
+  //
+  afxSelectron* selectron = afxSelectron::start_selectron(selectedObj, (U8)subcode, extra);
+
+  //
+  // Return the ID (or 0 if start failed).
+  //
+  return (selectron) ? selectron->getId() : 0;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+
+
+
+
+
+
+
+

+ 258 - 0
Engine/source/afx/afxSelectron.h

@@ -0,0 +1,258 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
+// Copyright (C) 2015 Faust Logic, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _AFX_SELECTION_EFFECT_H_
+#define _AFX_SELECTION_EFFECT_H_
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#include "console/typeValidators.h"
+
+#include "afxChoreographer.h"
+#include "afxEffectWrapper.h"
+#include "afxPhrase.h"
+
+class afxChoreographerData;
+class afxEffectBaseData;
+
+class  afxSelectronDefs
+{
+public:
+  enum {
+    MAIN_PHRASE,
+    SELECT_PHRASE,
+    DESELECT_PHRASE,
+    NUM_PHRASES
+  };
+};
+
+class afxSelectronData : public afxChoreographerData, public afxSelectronDefs
+{
+  typedef afxChoreographerData Parent;
+
+  class ewValidator : public TypeValidator
+  {
+    U32 id;
+  public:
+    ewValidator(U32 id) { this->id = id; }
+    void validateType(SimObject *object, void *typePtr);
+  };
+
+  bool          do_id_convert;
+
+public:
+  F32           main_dur;
+  F32           select_dur;
+  F32           deselect_dur;
+
+  S32           n_main_loops;
+  S32           n_select_loops;
+  S32           n_deselect_loops;
+
+  bool          registered;
+  U8            obj_type_style;
+  U32           obj_type_mask;
+
+  afxEffectBaseData* dummy_fx_entry;
+
+  afxEffectList main_fx_list;
+  afxEffectList select_fx_list;
+  afxEffectList deselect_fx_list;
+  
+private:
+  void          pack_fx(BitStream* stream, const afxEffectList& fx, bool packed);
+  void          unpack_fx(BitStream* stream, afxEffectList& fx);
+
+public:
+  /*C*/         afxSelectronData();
+  /*C*/         afxSelectronData(const afxSelectronData&, bool = false);
+  /*D*/         ~afxSelectronData();
+
+  virtual void  reloadReset();
+
+  virtual bool  onAdd();
+  virtual void  packData(BitStream*);
+  virtual void  unpackData(BitStream*);
+
+  bool          preload(bool server, String &errorStr);
+
+  bool          matches(U32 mask, U8 style);
+  void          gatherConstraintDefs(Vector<afxConstraintDef>&); 
+
+  virtual bool  allowSubstitutions() const { return true; }
+
+  static void   initPersistFields();
+
+  DECLARE_CONOBJECT(afxSelectronData);
+  DECLARE_CATEGORY("AFX");
+};
+
+inline bool afxSelectronData::matches(U32 mask, U8 style)
+{
+  if (obj_type_style != style)
+    return false;
+
+  if (obj_type_mask == 0 && mask == 0)
+    return true;
+
+  return ((obj_type_mask & mask) != 0);
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxSelectron
+
+class afxSelectron : public afxChoreographer, public afxSelectronDefs
+{
+  typedef afxChoreographer Parent;
+  friend class arcaneFX;
+
+public:
+  enum MaskBits 
+  {
+    StateEventMask    = Parent::NextFreeMask << 0,
+    SyncEventMask     = Parent::NextFreeMask << 1,
+    NextFreeMask      = Parent::NextFreeMask << 2
+  };
+
+  enum
+  {
+    NULL_EVENT,
+    ACTIVATE_EVENT,
+    SHUTDOWN_EVENT,
+    DEACTIVATE_EVENT,
+    INTERRUPT_EVENT
+  };
+
+  enum
+  {
+    INACTIVE_STATE,
+    ACTIVE_STATE,
+    CLEANUP_STATE,
+    DONE_STATE,
+    LATE_STATE
+  };
+
+  enum {
+    MARK_ACTIVATE   = BIT(0),
+    MARK_SHUTDOWN   = BIT(1),
+    MARK_DEACTIVATE = BIT(2),
+    MARK_INTERRUPT  = BIT(3),
+  };
+
+  class ObjectDeleteEvent : public SimEvent
+  {
+  public:
+    void process(SimObject *obj) { if (obj) obj->deleteObject(); }
+  };
+
+private:
+  static StringTableEntry  CAMERA_CONS;
+  static StringTableEntry  LISTENER_CONS;
+  static StringTableEntry  FREE_TARGET_CONS;
+
+private:
+  afxSelectronData*  datablock;
+  SimObject*         exeblock;
+
+  bool          constraints_initialized;
+  bool          client_only;
+
+  U8            effect_state;
+  F32           effect_elapsed;
+
+  afxConstraintID listener_cons_id;
+  afxConstraintID free_target_cons_id;
+  afxConstraintID camera_cons_id;
+  SceneObject*  camera_cons_obj;
+
+  afxPhrase*    phrases[NUM_PHRASES];
+
+  F32           time_factor;
+  U8            marks_mask;
+
+private:
+  void          init();
+  bool          state_expired();
+  void          init_constraints();
+  void          setup_main_fx();
+  void          setup_select_fx();
+  void          setup_deselect_fx();
+  bool          cleanup_over();
+
+public:
+  /*C*/         afxSelectron();
+  /*C*/         afxSelectron(bool not_default);
+  /*D*/         ~afxSelectron();
+
+    // STANDARD OVERLOADED METHODS //
+  virtual bool  onNewDataBlock(GameBaseData* dptr, bool reload);
+  virtual void  processTick(const Move*);
+  virtual void  advanceTime(F32 dt);
+  virtual bool  onAdd();
+  virtual void  onRemove();
+  virtual U32   packUpdate(NetConnection*, U32, BitStream*);
+  virtual void  unpackUpdate(NetConnection*, BitStream*);
+
+  virtual void  sync_with_clients();
+  void          finish_startup();
+
+  DECLARE_CONOBJECT(afxSelectron);
+  DECLARE_CATEGORY("AFX");
+
+private:
+  void          process_server();
+  //
+  void          change_state_s(U8 pending_state);
+  //
+  void          enter_active_state_s();
+  void          leave_active_state_s();
+  void          enter_cleanup_state_s();
+  void          enter_done_state_s();
+
+private:
+  void          process_client(F32 dt);
+  //
+  void          change_state_c(U8 pending_state);
+  //
+  void          enter_active_state_c(F32 starttime);
+  void          enter_cleanup_state_c();
+  void          enter_done_state_c();
+  void          leave_active_state_c();
+
+  void          sync_client(U16 marks, U8 state, F32 elapsed);
+
+public:
+  void          postEvent(U8 event);
+  void          setTimeFactor(F32 f) { time_factor = (f > 0) ? f : 1.0f; }
+  F32           getTimeFactor() { return time_factor; }
+
+  void          activate();
+
+public:
+  static afxSelectron*  start_selectron(SceneObject* picked, U8 subcode, SimObject* extra);
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+#endif // _AFX_SELECTION_EFFECT_H_

+ 357 - 0
Engine/source/afx/afxSpellBook.cpp

@@ -0,0 +1,357 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
+// Copyright (C) 2015 Faust Logic, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#include "afx/arcaneFX.h"
+
+#include "console/engineAPI.h"
+#include "console/consoleTypes.h"
+#include "core/stream/bitStream.h"
+#include "T3D/gameBase/gameBase.h"
+
+#include "afx/afxSpellBook.h"
+#include "afx/afxMagicSpell.h"
+#include "afx/rpg/afxRPGMagicSpell.h"
+#include "afx/ui/afxSpellButton.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxSpellBookData
+
+IMPLEMENT_CO_DATABLOCK_V1(afxSpellBookData);
+
+ConsoleDocClass( afxSpellBookData,
+   "@brief A spellbook datablock.\n\n"
+
+   "@ingroup afxMisc\n"
+   "@ingroup AFX\n"
+   "@ingroup Datablocks\n"
+);
+
+afxSpellBookData::afxSpellBookData()
+{
+  spells_per_page = 12;
+  pages_per_book = 12;
+  dMemset(spells, 0, sizeof(spells));
+  dMemset(rpg_spells, 0, sizeof(rpg_spells));
+
+  // marked true if datablock ids need to
+  // be converted into pointers
+  do_id_convert = false;
+}
+
+#define myOffset(field) Offset(field, afxSpellBookData)
+
+void afxSpellBookData::initPersistFields()
+{
+  addField("spellsPerPage",  TypeS8,                myOffset(spells_per_page),
+    "...");
+  addField("pagesPerBook",   TypeS8,                myOffset(pages_per_book),
+    "...");
+
+  addField("spells",       TYPEID<GameBaseData>(),   myOffset(spells),     MAX_PAGES_PER_BOOK*MAX_SPELLS_PER_PAGE,
+    "...");
+  addField("rpgSpells",    TYPEID<GameBaseData>(),   myOffset(rpg_spells), MAX_PAGES_PER_BOOK*MAX_SPELLS_PER_PAGE,
+    "...");
+
+  Parent::initPersistFields();
+}
+
+bool afxSpellBookData::preload(bool server, String &errorStr)
+{
+  if (!Parent::preload(server, errorStr))
+    return false;
+
+  // Resolve objects transmitted from server
+  if (!server)
+  {
+    if (do_id_convert)
+    {
+      for (S32 i = 0; i < pages_per_book*spells_per_page; i++)
+      {
+        SimObjectId db_id = SimObjectId((uintptr_t)rpg_spells[i]);
+        if (db_id != 0)
+        {
+          // try to convert id to pointer
+          if (!Sim::findObject(db_id, rpg_spells[i]))
+          {
+            Con::errorf(ConsoleLogEntry::General,
+              "afxSpellBookData::preload() -- bad datablockId: 0x%x (afxRPGMagicSpellData)",
+              db_id);
+          }
+        }
+      }
+      do_id_convert = false;
+    }
+  }
+
+  return true;
+}
+
+void afxSpellBookData::packData(BitStream* stream)
+{
+	Parent::packData(stream);
+
+  stream->write(spells_per_page);
+  stream->write(pages_per_book);
+
+  for (S32 i = 0; i < pages_per_book*spells_per_page; i++)
+    writeDatablockID(stream, rpg_spells[i], packed);
+}
+
+void afxSpellBookData::unpackData(BitStream* stream)
+{
+  Parent::unpackData(stream);
+
+  stream->read(&spells_per_page);
+  stream->read(&pages_per_book);
+
+  do_id_convert = true;
+  for (S32 i = 0; i < pages_per_book*spells_per_page; i++)
+    rpg_spells[i] = (afxRPGMagicSpellData*) readDatablockID(stream);
+}
+
+DefineEngineMethod(afxSpellBookData, getPageSlotIndex, S32, (Point2I bookSlot),,
+                   "...\n\n"
+                   "@ingroup AFX")
+{
+  return object->getPageSlotIndex(bookSlot.x, bookSlot.y);
+}
+
+DefineEngineMethod(afxSpellBookData, getCapacity, S32, (),,
+                   "Get the capacity (total number of spell slots) in a spellbook.\n\n"
+                   "@ingroup AFX")
+{
+  return object->spells_per_page*object->pages_per_book;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxSpellBook
+
+IMPLEMENT_CO_NETOBJECT_V1(afxSpellBook);
+
+ConsoleDocClass( afxSpellBook,
+   "@brief A spellbook object.\n\n"
+
+   "@ingroup afxMisc\n"
+   "@ingroup AFX\n"
+);
+
+afxSpellBook::afxSpellBook()
+{
+	mNetFlags.set(Ghostable | ScopeAlways);
+	mDataBlock = NULL;
+  all_spell_cooldown = 1.0f;
+}
+
+afxSpellBook::~afxSpellBook()
+{
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//
+
+void afxSpellBook::initPersistFields()
+{
+	Parent::initPersistFields();
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//
+
+void afxSpellBook::processTick(const Move* m)
+{
+	Parent::processTick(m);
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//
+
+void afxSpellBook::advanceTime(F32 dt)
+{
+  Parent::advanceTime(dt);
+
+  if (all_spell_cooldown < 1.0f)
+  {
+    all_spell_cooldown += dt/2.0f;
+    if (all_spell_cooldown > 1.0f)
+      all_spell_cooldown = 1.0f;
+  }
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//
+
+bool afxSpellBook::onNewDataBlock(GameBaseData* dptr, bool reload)
+{
+  mDataBlock = dynamic_cast<afxSpellBookData*>(dptr);
+  if (!mDataBlock || !Parent::onNewDataBlock(dptr, reload))
+    return false;
+
+  scriptOnNewDataBlock();
+
+  return true;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//
+
+bool afxSpellBook::onAdd()
+{
+	if (!Parent::onAdd())
+    return(false);
+
+	return(true);
+}
+
+void afxSpellBook::onRemove()
+{
+	Parent::onRemove();
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//
+
+U32 afxSpellBook::packUpdate(NetConnection * con, U32 mask, BitStream * stream)
+{
+  U32 retMask = Parent::packUpdate(con, mask, stream);
+
+  if (stream->writeFlag(mask & InitialUpdateMask))
+  {
+  }
+
+  // AllSpellCooldown
+  if (stream->writeFlag(mask & AllSpellCooldownMask))
+  {
+  }
+
+	return(retMask);
+}
+
+void afxSpellBook::unpackUpdate(NetConnection * con, BitStream * stream)
+{
+	Parent::unpackUpdate(con, stream);
+
+  // InitialUpdate
+  if (stream->readFlag())
+  {
+  }
+
+  // AllSpellCooldown
+  if (stream->readFlag())
+  {
+    all_spell_cooldown = 0.0f;
+  }
+}
+
+#define SPELL_DATA_NOT_FOUND "\n<just:center><font:Arial:20><color:FF0000>** Spell data not found **\n\n\n\n"
+
+char* afxSpellBook::formatDesc(char* buffer, int len, S32 page, S32 slot) const
+{
+  S32 idx = mDataBlock->getPageSlotIndex(page, slot);
+  if (idx < 0 || !mDataBlock->rpg_spells[idx])
+    return SPELL_DATA_NOT_FOUND;
+
+  return mDataBlock->rpg_spells[idx]->formatDesc(buffer, len);
+}
+
+const char* afxSpellBook::getSpellIcon(S32 page, S32 slot) const
+{
+  S32 idx = mDataBlock->getPageSlotIndex(page, slot);
+  if (idx < 0 || !mDataBlock->rpg_spells[idx])
+    return 0;
+
+  return mDataBlock->rpg_spells[idx]->icon_name;
+}
+
+bool afxSpellBook::isPlaceholder(S32 page, S32 slot) const
+{
+  S32 idx = mDataBlock->getPageSlotIndex(page, slot);
+  if (idx < 0 || !mDataBlock->rpg_spells[idx])
+    return false;
+
+  return mDataBlock->rpg_spells[idx]->is_placeholder;
+}
+
+
+afxMagicSpellData* afxSpellBook::getSpellData(S32 page, S32 slot)
+{
+  S32 idx = mDataBlock->getPageSlotIndex(page, slot);
+  if (idx < 0 || !mDataBlock->spells[idx])
+    return 0;
+
+  return mDataBlock->spells[idx];
+}
+
+afxRPGMagicSpellData* afxSpellBook::getSpellRPGData(S32 page, S32 slot)
+{
+  S32 idx = mDataBlock->getPageSlotIndex(page, slot);
+  if (idx < 0 || !mDataBlock->rpg_spells[idx])
+    return 0;
+
+  return mDataBlock->rpg_spells[idx];
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+void afxSpellBook::startAllSpellCooldown()
+{
+  //all_spell_cooldown = 0.0f;
+  setMaskBits(AllSpellCooldownMask);
+}
+
+F32 afxSpellBook::getCooldownFactor(S32 page, S32 slot)
+{
+  return all_spell_cooldown;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+DefineEngineMethod(afxSpellBook, getPageSlotIndex, S32, (Point2I bookSlot),,
+                   "...\n\n"
+                   "@ingroup AFX")
+{
+  return object->getPageSlotIndex(bookSlot.x, bookSlot.y);
+}
+
+DefineEngineMethod(afxSpellBook, getSpellData, S32, (Point2I bookSlot),,
+                   "Get spell datablock for spell stored at spellbook index, (page, slot).\n\n"
+                   "@ingroup AFX")
+{
+  afxMagicSpellData* spell_data = object->getSpellData(bookSlot.x, bookSlot.y);
+  return (spell_data) ? spell_data->getId() : 0;
+}
+
+DefineEngineMethod(afxSpellBook, getSpellRPGData, S32, (Point2I bookSlot),,
+                   "Get spell RPG datablock for spell stored at spellbook index, (page, slot).\n\n"
+                   "@ingroup AFX")
+{
+  afxRPGMagicSpellData* spell_data = object->getSpellRPGData(bookSlot.x, bookSlot.y);
+  return (spell_data) ? spell_data->getId() : 0;
+}
+
+DefineEngineMethod(afxSpellBook, startAllSpellCooldown, void, (),,
+                   "...\n\n"
+                   "@ingroup AFX")
+{
+  object->startAllSpellCooldown();
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+
+

+ 142 - 0
Engine/source/afx/afxSpellBook.h

@@ -0,0 +1,142 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
+// Copyright (C) 2015 Faust Logic, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _AFX_SPELL_BOOK_H_
+#define _AFX_SPELL_BOOK_H_
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#include "T3D/gameBase/gameBase.h"
+
+class afxSpellBookDefs
+{
+public:
+  enum {
+    MAX_SPELLS_PER_PAGE = 12,
+    MAX_PAGES_PER_BOOK = 12
+  };
+};
+
+class afxMagicSpellData;
+class afxRPGMagicSpellData;
+
+class afxSpellBookData : public GameBaseData, public afxSpellBookDefs
+{
+  typedef GameBaseData  Parent;
+
+  bool                  do_id_convert;
+
+public:
+  U8                    spells_per_page;
+  U8                    pages_per_book;
+  afxMagicSpellData*    spells[MAX_PAGES_PER_BOOK*MAX_SPELLS_PER_PAGE];
+  afxRPGMagicSpellData* rpg_spells[MAX_PAGES_PER_BOOK*MAX_SPELLS_PER_PAGE];
+
+public:
+  /*C*/                 afxSpellBookData();
+
+  virtual void          packData(BitStream*);
+  virtual void          unpackData(BitStream*);
+
+  bool                  preload(bool server, String &errorStr);
+
+  bool                  verifyPageSlot(S32 page, S32 slot);
+  S32                   getPageSlotIndex(S32 page, S32 slot);
+
+  static void           initPersistFields();
+
+  DECLARE_CONOBJECT(afxSpellBookData);
+  DECLARE_CATEGORY("AFX");
+};
+
+inline bool afxSpellBookData::verifyPageSlot(S32 page, S32 slot)
+{
+  return (page >= 0 && page < pages_per_book && slot >= 0 && slot < spells_per_page);
+}
+
+inline S32 afxSpellBookData::getPageSlotIndex(S32 page, S32 slot)
+{
+  return (verifyPageSlot(page, slot)) ? page*spells_per_page + slot : -1;
+}
+
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+class afxMagicSpellData;
+class afxSpellButton;
+
+class afxSpellBook : public GameBase, public afxSpellBookDefs
+{
+  typedef GameBase        Parent;
+
+  enum MaskBits 
+  {
+    AllSpellCooldownMask  = Parent::NextFreeMask << 0,
+    NextFreeMask          = Parent::NextFreeMask << 1
+  };
+
+private:
+  afxSpellBookData*       mDataBlock;
+  F32                     all_spell_cooldown;
+
+public:
+  /*C*/                   afxSpellBook();
+  /*D*/                   ~afxSpellBook();
+
+  virtual bool            onNewDataBlock(GameBaseData* dptr, bool reload);
+  virtual void            processTick(const Move*);
+  virtual void            advanceTime(F32 dt);
+
+  virtual bool            onAdd();
+  virtual void            onRemove();
+
+  virtual U32             packUpdate(NetConnection*, U32, BitStream*);
+  virtual void            unpackUpdate(NetConnection*, BitStream*);
+
+  static void             initPersistFields();
+
+  S32                     getPageSlotIndex(S32 page, S32 slot);
+  char*                   formatDesc(char* buffer, int len, S32 page, S32 slot) const;
+  const char*             getSpellIcon(S32 page, S32 slot) const;
+  bool                    isPlaceholder(S32 page, S32 slot) const;
+  afxMagicSpellData*      getSpellData(S32 page, S32 slot);
+  afxRPGMagicSpellData*   getSpellRPGData(S32 page, S32 slot);
+
+  void                    startAllSpellCooldown();
+  F32                     getCooldownFactor(S32 page, S32 slot);
+
+  DECLARE_CONOBJECT(afxSpellBook);
+  DECLARE_CATEGORY("AFX");
+};
+
+inline S32 afxSpellBook::getPageSlotIndex(S32 page, S32 slot)
+{
+  return (mDataBlock) ? mDataBlock->getPageSlotIndex(page, slot) : -1;
+}
+
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#endif // _AFX_SPELL_BOOK_H_

+ 311 - 0
Engine/source/afx/afxZodiacGroundPlaneRenderer_T3D.cpp

@@ -0,0 +1,311 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
+// Copyright (C) 2015 Faust Logic, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#include "afx/arcaneFX.h"
+
+#include "materials/shaderData.h"
+#include "gfx/gfxTransformSaver.h"
+#include "scene/sceneRenderState.h"
+#include "collision/concretePolyList.h"
+#include "T3D/tsStatic.h"
+#include "gfx/primBuilder.h"
+
+#include "afx/ce/afxZodiacMgr.h"
+#include "afx/afxZodiacGroundPlaneRenderer_T3D.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+const RenderInstType afxZodiacGroundPlaneRenderer::RIT_GroundPlaneZodiac("GroundPlaneZodiac");
+
+afxZodiacGroundPlaneRenderer* afxZodiacGroundPlaneRenderer::master = 0;
+
+IMPLEMENT_CONOBJECT(afxZodiacGroundPlaneRenderer);
+
+ConsoleDocClass( afxZodiacGroundPlaneRenderer, 
+   "@brief A render bin for zodiac rendering on GroundPlane objects.\n\n"
+
+   "This bin renders instances of AFX zodiac effects onto GroundPlane surfaces.\n\n"
+
+   "@ingroup RenderBin\n"
+   "@ingroup AFX\n"
+);
+
+afxZodiacGroundPlaneRenderer::afxZodiacGroundPlaneRenderer()
+: RenderBinManager(RIT_GroundPlaneZodiac, 1.0f, 1.0f)
+{
+   if (!master)
+     master = this;
+   shader_initialized = false;
+}
+
+afxZodiacGroundPlaneRenderer::afxZodiacGroundPlaneRenderer(F32 renderOrder, F32 processAddOrder)
+: RenderBinManager(RIT_GroundPlaneZodiac, renderOrder, processAddOrder)
+{
+   if (!master)
+     master = this;
+   shader_initialized = false;
+}
+
+afxZodiacGroundPlaneRenderer::~afxZodiacGroundPlaneRenderer()
+{
+  if (this == master)
+    master = 0;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+void afxZodiacGroundPlaneRenderer::initShader()
+{
+   if (shader_initialized)
+     return;
+
+   shader_initialized = true;
+
+   shader_consts = 0;
+   norm_norefl_zb_SB = norm_refl_zb_SB;
+   add_norefl_zb_SB = add_refl_zb_SB;
+   sub_norefl_zb_SB = sub_refl_zb_SB;
+
+   zodiac_shader = afxZodiacMgr::getGroundPlaneZodiacShader();
+   if (!zodiac_shader)
+     return;
+
+   GFXStateBlockDesc d;
+
+   d.cullDefined = true;
+   d.ffLighting = false;
+   d.blendDefined = true;
+   d.blendEnable = true;
+   d.zDefined = false;
+   d.zEnable = true;
+   d.zWriteEnable = false;
+   d.zFunc = GFXCmpLessEqual;
+   d.zSlopeBias = 0;
+   d.alphaDefined = true;
+   d.alphaTestEnable = true; 
+   d.alphaTestRef = 0;
+   d.alphaTestFunc = GFXCmpGreater;
+   d.samplersDefined = true;
+   d.samplers[0] = GFXSamplerStateDesc::getClampLinear();
+
+   // normal
+   d.blendSrc = GFXBlendSrcAlpha;
+   d.blendDest = GFXBlendInvSrcAlpha;
+   //
+   d.cullMode = GFXCullCCW;
+   d.zBias = arcaneFX::sPolysoupZodiacZBias;
+   norm_norefl_zb_SB = GFX->createStateBlock(d);
+   //
+   d.cullMode = GFXCullCW;
+   d.zBias = arcaneFX::sPolysoupZodiacZBias;
+   norm_refl_zb_SB = GFX->createStateBlock(d);
+
+   // additive
+   d.blendSrc = GFXBlendSrcAlpha;
+   d.blendDest = GFXBlendOne;
+   //
+   d.cullMode = GFXCullCCW;
+   d.zBias = arcaneFX::sPolysoupZodiacZBias;
+   add_norefl_zb_SB = GFX->createStateBlock(d);
+   //
+   d.cullMode = GFXCullCW;
+   d.zBias = arcaneFX::sPolysoupZodiacZBias;
+   add_refl_zb_SB = GFX->createStateBlock(d);
+
+   // subtractive
+   d.blendSrc = GFXBlendZero;
+   d.blendDest = GFXBlendInvSrcColor;
+   //
+   d.cullMode = GFXCullCCW;
+   d.zBias = arcaneFX::sPolysoupZodiacZBias;
+   sub_norefl_zb_SB = GFX->createStateBlock(d);
+   //
+   d.cullMode = GFXCullCW;
+   d.zBias = arcaneFX::sPolysoupZodiacZBias;
+   sub_refl_zb_SB = GFX->createStateBlock(d);
+
+   shader_consts = zodiac_shader->getShader()->allocConstBuffer();
+   projection_sc = zodiac_shader->getShader()->getShaderConstHandle("$modelView");
+   color_sc = zodiac_shader->getShader()->getShaderConstHandle("$zodiacColor");
+}
+
+void afxZodiacGroundPlaneRenderer::clear()
+{
+  Parent::clear();
+  groundPlane_zodiacs.clear();
+}
+
+void afxZodiacGroundPlaneRenderer::addZodiac(U32 zode_idx, const Point3F& pos, F32 ang, const GroundPlane* gp, F32 camDist)
+{
+  groundPlane_zodiacs.increment();
+  GroundPlaneZodiacElem& elem = groundPlane_zodiacs.last();
+
+  elem.gp = gp;
+  elem.zode_idx = zode_idx;
+  elem.ang = ang;
+  elem.camDist = camDist;
+}
+
+afxZodiacGroundPlaneRenderer* afxZodiacGroundPlaneRenderer::getMaster()
+{
+  if (!master)
+    master = new afxZodiacGroundPlaneRenderer;
+  return master;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+GFXStateBlock* afxZodiacGroundPlaneRenderer::chooseStateBlock(U32 blend, bool isReflectPass)
+{
+  GFXStateBlock* sb = 0;
+  
+  switch (blend)
+  {
+  case afxZodiacData::BLEND_ADDITIVE:
+    sb = (isReflectPass) ? add_refl_zb_SB : add_norefl_zb_SB;
+    break;
+  case afxZodiacData::BLEND_SUBTRACTIVE:
+    sb = (isReflectPass) ? sub_refl_zb_SB : sub_norefl_zb_SB;
+    break;
+  default: // afxZodiacData::BLEND_NORMAL:
+    sb = (isReflectPass) ? norm_refl_zb_SB : norm_norefl_zb_SB;
+    break;
+  }
+
+  return sb;
+}
+
+void afxZodiacGroundPlaneRenderer::render(SceneRenderState* state)
+{
+   PROFILE_SCOPE(afxRenderZodiacGroundPlaneMgr_render);
+
+   // Early out if no ground-plane zodiacs to draw.
+   if (groundPlane_zodiacs.size() == 0)
+     return;
+
+   initShader();
+   if (!zodiac_shader)
+     return;
+
+   bool is_reflect_pass = state->isReflectPass();
+
+   // Automagically save & restore our viewport and transforms.
+   GFXTransformSaver saver;
+
+   MatrixF proj = GFX->getProjectionMatrix();
+
+   // Set up world transform
+   MatrixF world = GFX->getWorldMatrix();
+   proj.mul(world);
+   shader_consts->set(projection_sc, proj);
+
+   //~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//
+   // RENDER EACH ZODIAC
+   //
+   for (S32 zz = 0; zz < groundPlane_zodiacs.size(); zz++)
+   {
+      GroundPlaneZodiacElem& elem = groundPlane_zodiacs[zz];
+
+      afxZodiacMgr::ZodiacSpec* zode = &afxZodiacMgr::terr_zodes[elem.zode_idx];
+      if (!zode)
+         continue;
+
+      if (is_reflect_pass)
+      {
+         //if ((zode->zflags & afxZodiacData::SHOW_IN_REFLECTIONS) == 0)
+            continue;
+      }
+      else
+      {
+         if ((zode->zflags & afxZodiacData::SHOW_IN_NON_REFLECTIONS) == 0)
+            continue;
+      }
+
+      F32 fadebias = zode->calcDistanceFadeBias(elem.camDist);
+      if (fadebias < 0.01f)
+        continue;
+
+      F32 cos_ang = mCos(elem.ang);
+      F32 sin_ang = mSin(elem.ang);
+
+      GFXStateBlock* sb = chooseStateBlock(zode->zflags & afxZodiacData::BLEND_MASK, is_reflect_pass);
+
+      GFX->setShader(zodiac_shader->getShader());
+      GFX->setStateBlock(sb);
+      GFX->setShaderConstBuffer(shader_consts);
+
+      // set the texture
+      GFX->setTexture(0, *zode->txr);
+      LinearColorF zode_color = (LinearColorF)zode->color;
+      zode_color.alpha *= fadebias;
+      shader_consts->set(color_sc, zode_color);
+
+      F32 rad_xy = zode->radius_xy;
+      F32 inv_radius = 1.0f/rad_xy;
+      F32 offset_xy = mSqrt(2*rad_xy*rad_xy);
+
+      F32 zx = zode->pos.x;
+      F32 zy = zode->pos.y;
+      F32 z = 0.00001f;
+
+      Point3F verts[4];
+      verts[0].set(zx+offset_xy, zy+offset_xy, z);
+      verts[1].set(zx-offset_xy, zy+offset_xy, z);
+      verts[2].set(zx-offset_xy, zy-offset_xy, z);
+      verts[3].set(zx+offset_xy, zy-offset_xy, z);
+
+      S32 vertind[6];
+      vertind[0] = 2;
+      vertind[1] = 1;
+      vertind[2] = 0;
+      vertind[3] = 3;
+      vertind[4] = 2;
+      vertind[5] = 0;
+
+      PrimBuild::begin(GFXTriangleList, 6);
+      for (U32 i = 0; i < 2; i++) 
+      {
+        for (U32 j = 0; j < 3; j++) 
+        {
+          const Point3F& vtx = verts[vertind[i*3+j]];
+
+          // compute UV
+          F32 u1 = (vtx.x - zode->pos.x)*inv_radius;
+          F32 v1 = (vtx.y - zode->pos.y)*inv_radius;
+          F32 ru1 = u1*cos_ang - v1*sin_ang;
+          F32 rv1 = u1*sin_ang + v1*cos_ang;
+
+          F32 uu = (ru1 + 1.0f)/2.0f;
+          F32 vv = 1.0f - (rv1 + 1.0f)/2.0f;
+
+          PrimBuild::texCoord2f(uu, vv);
+          PrimBuild::vertex3fv(vtx);
+        }
+      }
+      PrimBuild::end(false);
+   }
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//

+ 91 - 0
Engine/source/afx/afxZodiacGroundPlaneRenderer_T3D.h

@@ -0,0 +1,91 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
+// Copyright (C) 2015 Faust Logic, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _AFX_ZODIAC_GROUNDPLANE_RENDERER_H_
+#define _AFX_ZODIAC_GROUNDPLANE_RENDERER_H_
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#include "renderInstance/renderBinManager.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+class ConcretePolyList;
+class GroundPlane;
+
+class afxZodiacGroundPlaneRenderer : public RenderBinManager
+{
+   typedef RenderBinManager Parent;
+
+   struct GroundPlaneZodiacElem
+   {
+     const GroundPlane* gp;
+     U32                zode_idx;
+     F32                ang;
+     F32                camDist;
+   };
+
+   Vector<GroundPlaneZodiacElem> groundPlane_zodiacs;
+   static afxZodiacGroundPlaneRenderer* master;
+
+   GFXStateBlockRef norm_norefl_zb_SB, norm_refl_zb_SB;
+   GFXStateBlockRef add_norefl_zb_SB, add_refl_zb_SB;
+   GFXStateBlockRef sub_norefl_zb_SB, sub_refl_zb_SB;
+
+   ShaderData*    zodiac_shader;
+   GFXShaderConstBufferRef shader_consts;
+   GFXShaderConstHandle* projection_sc;
+   GFXShaderConstHandle* color_sc;
+
+   bool           shader_initialized;
+
+   GFXStateBlock* chooseStateBlock(U32 blend, bool isReflectPass);
+
+public:
+   static const RenderInstType RIT_GroundPlaneZodiac;
+
+   /*C*/          afxZodiacGroundPlaneRenderer();
+   /*C*/          afxZodiacGroundPlaneRenderer(F32 renderOrder, F32 processAddOrder);
+   /*D*/          ~afxZodiacGroundPlaneRenderer();
+
+   // RenderBinManager
+   virtual void   sort(){}  // don't sort them
+   virtual void   clear();
+
+   void           initShader();
+   void           addZodiac(U32 zode_idx, const Point3F& pos, F32 ang, const GroundPlane*, F32 camDist);
+
+   virtual void   render(SceneRenderState* state);
+
+   static afxZodiacGroundPlaneRenderer* getMaster();
+
+   // ConsoleObject
+   DECLARE_CONOBJECT(afxZodiacGroundPlaneRenderer);
+   DECLARE_CATEGORY("AFX");
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#endif // _AFX_ZODIAC_GROUNDPLANE_RENDERER_H_

+ 302 - 0
Engine/source/afx/afxZodiacMeshRoadRenderer_T3D.cpp

@@ -0,0 +1,302 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
+// Copyright (C) 2015 Faust Logic, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#include "afx/arcaneFX.h"
+
+#include "materials/shaderData.h"
+#include "gfx/gfxTransformSaver.h"
+#include "scene/sceneRenderState.h"
+#include "collision/concretePolyList.h"
+#include "T3D/tsStatic.h"
+#include "gfx/primBuilder.h"
+
+#include "afx/ce/afxZodiacMgr.h"
+#include "afx/afxZodiacMeshRoadRenderer_T3D.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+const RenderInstType afxZodiacMeshRoadRenderer::RIT_MeshRoadZodiac("MeshRoadZodiac");
+
+afxZodiacMeshRoadRenderer* afxZodiacMeshRoadRenderer::master = 0;
+
+IMPLEMENT_CONOBJECT(afxZodiacMeshRoadRenderer);
+
+ConsoleDocClass( afxZodiacMeshRoadRenderer, 
+   "@brief A render bin for zodiac rendering on MeshRoad objects.\n\n"
+
+   "This bin renders instances of AFX zodiac effects onto MeshRoad surfaces.\n\n"
+
+   "@ingroup RenderBin\n"
+   "@ingroup AFX\n"
+);
+
+afxZodiacMeshRoadRenderer::afxZodiacMeshRoadRenderer()
+: RenderBinManager(RIT_MeshRoadZodiac, 1.0f, 1.0f)
+{
+   if (!master)
+     master = this;
+   shader_initialized = false;
+}
+
+afxZodiacMeshRoadRenderer::afxZodiacMeshRoadRenderer(F32 renderOrder, F32 processAddOrder)
+: RenderBinManager(RIT_MeshRoadZodiac, renderOrder, processAddOrder)
+{
+   if (!master)
+     master = this;
+   shader_initialized = false;
+}
+
+afxZodiacMeshRoadRenderer::~afxZodiacMeshRoadRenderer()
+{
+  if (this == master)
+    master = 0;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+void afxZodiacMeshRoadRenderer::initShader()
+{
+   if (shader_initialized)
+     return;
+
+   shader_initialized = true;
+
+   shader_consts = 0;
+   norm_norefl_zb_SB = norm_refl_zb_SB;
+   add_norefl_zb_SB = add_refl_zb_SB;
+   sub_norefl_zb_SB = sub_refl_zb_SB;
+
+   zodiac_shader = afxZodiacMgr::getMeshRoadZodiacShader();
+   if (!zodiac_shader)
+     return;
+
+   GFXStateBlockDesc d;
+
+   d.cullDefined = true;
+   d.ffLighting = false;
+   d.blendDefined = true;
+   d.blendEnable = true;
+   d.zDefined = false;
+   d.zEnable = true;
+   d.zWriteEnable = false;
+   d.zFunc = GFXCmpLessEqual;
+   d.zSlopeBias = 0;
+   d.alphaDefined = true;
+   d.alphaTestEnable = true; 
+   d.alphaTestRef = 0;
+   d.alphaTestFunc = GFXCmpGreater;
+   d.samplersDefined = true;
+   d.samplers[0] = GFXSamplerStateDesc::getClampLinear();
+
+   // normal
+   d.blendSrc = GFXBlendSrcAlpha;
+   d.blendDest = GFXBlendInvSrcAlpha;
+   //
+   d.cullMode = GFXCullCCW;
+   d.zBias = arcaneFX::sPolysoupZodiacZBias;
+   norm_norefl_zb_SB = GFX->createStateBlock(d);
+   //
+   d.cullMode = GFXCullCW;
+   d.zBias = arcaneFX::sPolysoupZodiacZBias;
+   norm_refl_zb_SB = GFX->createStateBlock(d);
+
+   // additive
+   d.blendSrc = GFXBlendSrcAlpha;
+   d.blendDest = GFXBlendOne;
+   //
+   d.cullMode = GFXCullCCW;
+   d.zBias = arcaneFX::sPolysoupZodiacZBias;
+   add_norefl_zb_SB = GFX->createStateBlock(d);
+   //
+   d.cullMode = GFXCullCW;
+   d.zBias = arcaneFX::sPolysoupZodiacZBias;
+   add_refl_zb_SB = GFX->createStateBlock(d);
+
+   // subtractive
+   d.blendSrc = GFXBlendZero;
+   d.blendDest = GFXBlendInvSrcColor;
+   //
+   d.cullMode = GFXCullCCW;
+   d.zBias = arcaneFX::sPolysoupZodiacZBias;
+   sub_norefl_zb_SB = GFX->createStateBlock(d);
+   //
+   d.cullMode = GFXCullCW;
+   d.zBias = arcaneFX::sPolysoupZodiacZBias;
+   sub_refl_zb_SB = GFX->createStateBlock(d);
+
+   shader_consts = zodiac_shader->getShader()->allocConstBuffer();
+   projection_sc = zodiac_shader->getShader()->getShaderConstHandle("$modelView");
+   color_sc = zodiac_shader->getShader()->getShaderConstHandle("$zodiacColor");
+}
+
+void afxZodiacMeshRoadRenderer::clear()
+{
+  Parent::clear();
+  for (S32 i = 0; i < meshRoad_zodiacs.size(); i++)
+    if (meshRoad_zodiacs[i].polys)
+      delete meshRoad_zodiacs[i].polys;
+  meshRoad_zodiacs.clear();
+}
+
+void afxZodiacMeshRoadRenderer::addZodiac(U32 zode_idx, ConcretePolyList* polys, const Point3F& pos, F32 ang, const MeshRoad* road, F32 camDist)
+{
+  meshRoad_zodiacs.increment();
+  MeshRoadZodiacElem& elem = meshRoad_zodiacs.last();
+
+  elem.road = road;
+  elem.polys = polys;
+  elem.zode_idx = zode_idx;
+  elem.ang = ang;
+  elem.camDist = camDist;
+}
+
+afxZodiacMeshRoadRenderer* afxZodiacMeshRoadRenderer::getMaster()
+{
+  if (!master)
+    master = new afxZodiacMeshRoadRenderer;
+  return master;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+GFXStateBlock* afxZodiacMeshRoadRenderer::chooseStateBlock(U32 blend, bool isReflectPass)
+{
+  GFXStateBlock* sb = 0;
+  
+  switch (blend)
+  {
+  case afxZodiacData::BLEND_ADDITIVE:
+    sb = (isReflectPass) ? add_refl_zb_SB : add_norefl_zb_SB;
+    break;
+  case afxZodiacData::BLEND_SUBTRACTIVE:
+    sb = (isReflectPass) ? sub_refl_zb_SB : sub_norefl_zb_SB;
+    break;
+  default: // afxZodiacData::BLEND_NORMAL:
+    sb = (isReflectPass) ? norm_refl_zb_SB : norm_norefl_zb_SB;
+    break;
+  }
+
+  return sb;
+}
+
+void afxZodiacMeshRoadRenderer::render(SceneRenderState* state)
+{
+   PROFILE_SCOPE(afxRenderZodiacMeshRoadMgr_render);
+
+   // Early out if no ground-plane zodiacs to draw.
+   if (meshRoad_zodiacs.size() == 0)
+     return;
+
+   initShader();
+   if (!zodiac_shader)
+     return;
+
+   bool is_reflect_pass = state->isReflectPass();
+
+   // Automagically save & restore our viewport and transforms.
+   GFXTransformSaver saver;
+
+   MatrixF proj = GFX->getProjectionMatrix();
+
+   // Set up world transform
+   MatrixF world = GFX->getWorldMatrix();
+   proj.mul(world);
+   shader_consts->set(projection_sc, proj);
+
+   //~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//
+   // RENDER EACH ZODIAC
+   //
+   for (S32 zz = 0; zz < meshRoad_zodiacs.size(); zz++)
+   {
+      MeshRoadZodiacElem& elem = meshRoad_zodiacs[zz];
+
+      afxZodiacMgr::ZodiacSpec* zode = &afxZodiacMgr::terr_zodes[elem.zode_idx];
+      if (!zode)
+         continue;
+
+      if (is_reflect_pass)
+      {
+         if ((zode->zflags & afxZodiacData::SHOW_IN_REFLECTIONS) == 0)
+            continue;
+      }
+      else
+      {
+         if ((zode->zflags & afxZodiacData::SHOW_IN_NON_REFLECTIONS) == 0)
+            continue;
+      }
+
+      F32 fadebias = zode->calcDistanceFadeBias(elem.camDist);
+      if (fadebias < 0.01f)
+        continue;
+
+      F32 cos_ang = mCos(elem.ang);
+      F32 sin_ang = mSin(elem.ang);
+
+      GFXStateBlock* sb = chooseStateBlock(zode->zflags & afxZodiacData::BLEND_MASK, is_reflect_pass);
+
+      GFX->setShader(zodiac_shader->getShader());
+      GFX->setStateBlock(sb);
+      GFX->setShaderConstBuffer(shader_consts);
+
+      // set the texture
+      GFX->setTexture(0, *zode->txr);
+      LinearColorF zode_color = (LinearColorF)zode->color;
+      zode_color.alpha *= fadebias;
+      shader_consts->set(color_sc, zode_color);
+
+      F32 inv_radius = 1.0f/zode->radius_xy;
+
+      PrimBuild::begin(GFXTriangleList, 3*elem.polys->mPolyList.size());
+      for (U32 i = 0; i < elem.polys->mPolyList.size(); i++) 
+      {
+        ConcretePolyList::Poly* poly = &elem.polys->mPolyList[i];
+
+        S32 vertind[3];
+        vertind[0] = elem.polys->mIndexList[poly->vertexStart];
+        vertind[1] = elem.polys->mIndexList[poly->vertexStart + 1];
+        vertind[2] = elem.polys->mIndexList[poly->vertexStart + 2];
+
+        for (U32 j = 0; j < 3; j++) 
+        {
+          Point3F vtx = elem.polys->mVertexList[vertind[j]];
+
+          // compute UV
+          F32 u1 = (vtx.x - zode->pos.x)*inv_radius;
+          F32 v1 = (vtx.y - zode->pos.y)*inv_radius;
+          F32 ru1 = u1*cos_ang - v1*sin_ang;
+          F32 rv1 = u1*sin_ang + v1*cos_ang;
+
+          F32 uu = (ru1 + 1.0f)/2.0f;
+          F32 vv = 1.0f - (rv1 + 1.0f)/2.0f;
+
+          PrimBuild::texCoord2f(uu, vv);
+          PrimBuild::vertex3fv(vtx);
+        }
+      }
+      PrimBuild::end(false);
+   }
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//

+ 92 - 0
Engine/source/afx/afxZodiacMeshRoadRenderer_T3D.h

@@ -0,0 +1,92 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
+// Copyright (C) 2015 Faust Logic, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _AFX_ZODIAC_MESHROAD_RENDERER_H_
+#define _AFX_ZODIAC_MESHROAD_RENDERER_H_
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#include "renderInstance/renderBinManager.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+class ConcretePolyList;
+class MeshRoad;
+
+class afxZodiacMeshRoadRenderer : public RenderBinManager
+{
+   typedef RenderBinManager Parent;
+
+   struct MeshRoadZodiacElem
+   {
+     const MeshRoad*    road;
+     U32                zode_idx;
+     ConcretePolyList*  polys;
+     F32                ang;
+     F32                camDist;
+   };
+
+   Vector<MeshRoadZodiacElem> meshRoad_zodiacs;
+   static afxZodiacMeshRoadRenderer* master;
+
+   GFXStateBlockRef norm_norefl_zb_SB, norm_refl_zb_SB;
+   GFXStateBlockRef add_norefl_zb_SB, add_refl_zb_SB;
+   GFXStateBlockRef sub_norefl_zb_SB, sub_refl_zb_SB;
+
+   ShaderData*    zodiac_shader;
+   GFXShaderConstBufferRef shader_consts;
+   GFXShaderConstHandle* projection_sc;
+   GFXShaderConstHandle* color_sc;
+
+   bool           shader_initialized;
+
+   GFXStateBlock* chooseStateBlock(U32 blend, bool isReflectPass);
+
+public:
+   static const RenderInstType RIT_MeshRoadZodiac;
+
+   /*C*/          afxZodiacMeshRoadRenderer();
+   /*C*/          afxZodiacMeshRoadRenderer(F32 renderOrder, F32 processAddOrder);
+   /*D*/          ~afxZodiacMeshRoadRenderer();
+
+   // RenderBinManager
+   virtual void   sort(){}  // don't sort them
+   virtual void   clear();
+
+   void           initShader();
+   void           addZodiac(U32 zode_idx, ConcretePolyList*, const Point3F& pos, F32 ang, const MeshRoad*, F32 camDist);
+
+   virtual void   render(SceneRenderState*);
+
+   static afxZodiacMeshRoadRenderer* getMaster();
+
+   // ConsoleObject
+   DECLARE_CONOBJECT(afxZodiacMeshRoadRenderer);
+   DECLARE_CATEGORY("AFX");
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#endif // _AFX_ZODIAC_MESHROAD_RENDERER_H_

+ 420 - 0
Engine/source/afx/afxZodiacPolysoupRenderer_T3D.cpp

@@ -0,0 +1,420 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
+// Copyright (C) 2015 Faust Logic, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#include "afx/arcaneFX.h"
+
+#include "materials/shaderData.h"
+#include "gfx/gfxTransformSaver.h"
+#include "scene/sceneRenderState.h"
+#include "collision/concretePolyList.h"
+#include "T3D/tsStatic.h"
+#include "gfx/primBuilder.h"
+
+#include "afx/ce/afxZodiacMgr.h"
+#include "afx/afxZodiacPolysoupRenderer_T3D.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+const RenderInstType afxZodiacPolysoupRenderer::RIT_PolysoupZodiac("PolysoupZodiac");
+
+afxZodiacPolysoupRenderer* afxZodiacPolysoupRenderer::master = 0;
+
+IMPLEMENT_CONOBJECT(afxZodiacPolysoupRenderer);
+
+ConsoleDocClass( afxZodiacPolysoupRenderer, 
+   "@brief A render bin for zodiac rendering on polysoup TSStatic objects.\n\n"
+
+   "This bin renders instances of AFX zodiac effects onto polysoup TSStatic surfaces.\n\n"
+
+   "@ingroup RenderBin\n"
+   "@ingroup AFX\n"
+);
+
+afxZodiacPolysoupRenderer::afxZodiacPolysoupRenderer()
+: RenderBinManager(RIT_PolysoupZodiac, 1.0f, 1.0f)
+{
+   if (!master)
+     master = this;
+   shader_initialized = false;
+}
+
+afxZodiacPolysoupRenderer::afxZodiacPolysoupRenderer(F32 renderOrder, F32 processAddOrder)
+: RenderBinManager(RIT_PolysoupZodiac, renderOrder, processAddOrder)
+{
+   if (!master)
+     master = this;
+   shader_initialized = false;
+}
+
+afxZodiacPolysoupRenderer::~afxZodiacPolysoupRenderer()
+{
+  if (this == master)
+    master = 0;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+void afxZodiacPolysoupRenderer::initShader()
+{
+   if (shader_initialized)
+     return;
+
+   shader_initialized = true;
+
+   shader_consts = 0;
+   norm_norefl_zb_SB = norm_refl_zb_SB;
+   add_norefl_zb_SB = add_refl_zb_SB;
+   sub_norefl_zb_SB = sub_refl_zb_SB;
+
+   zodiac_shader = afxZodiacMgr::getPolysoupZodiacShader();
+   if (!zodiac_shader)
+     return;
+
+   GFXStateBlockDesc d;
+
+   d.cullDefined = true;
+   d.ffLighting = false;
+   d.blendDefined = true;
+   d.blendEnable = true;
+   d.zDefined = false;
+   d.zEnable = true;
+   d.zWriteEnable = false;
+   d.zFunc = GFXCmpLessEqual;
+   d.zSlopeBias = 0;
+   d.alphaDefined = true;
+   d.alphaTestEnable = true; 
+   d.alphaTestRef = 0;
+   d.alphaTestFunc = GFXCmpGreater;
+   d.samplersDefined = true;
+   d.samplers[0] = GFXSamplerStateDesc::getClampLinear();
+
+   // normal
+   d.blendSrc = GFXBlendSrcAlpha;
+   d.blendDest = GFXBlendInvSrcAlpha;
+   //
+   d.cullMode = GFXCullCCW;
+   d.zBias = arcaneFX::sPolysoupZodiacZBias;
+   norm_norefl_zb_SB = GFX->createStateBlock(d);
+   //
+   d.cullMode = GFXCullCW;
+   d.zBias = arcaneFX::sPolysoupZodiacZBias;
+   norm_refl_zb_SB = GFX->createStateBlock(d);
+
+   // additive
+   d.blendSrc = GFXBlendSrcAlpha;
+   d.blendDest = GFXBlendOne;
+   //
+   d.cullMode = GFXCullCCW;
+   d.zBias = arcaneFX::sPolysoupZodiacZBias;
+   add_norefl_zb_SB = GFX->createStateBlock(d);
+   //
+   d.cullMode = GFXCullCW;
+   d.zBias = arcaneFX::sPolysoupZodiacZBias;
+   add_refl_zb_SB = GFX->createStateBlock(d);
+
+   // subtractive
+   d.blendSrc = GFXBlendZero;
+   d.blendDest = GFXBlendInvSrcColor;
+   //
+   d.cullMode = GFXCullCCW;
+   d.zBias = arcaneFX::sPolysoupZodiacZBias;
+   sub_norefl_zb_SB = GFX->createStateBlock(d);
+   //
+   d.cullMode = GFXCullCW;
+   d.zBias = arcaneFX::sPolysoupZodiacZBias;
+   sub_refl_zb_SB = GFX->createStateBlock(d);
+
+   shader_consts = zodiac_shader->getShader()->allocConstBuffer();
+   projection_sc = zodiac_shader->getShader()->getShaderConstHandle("$modelView");
+   color_sc = zodiac_shader->getShader()->getShaderConstHandle("$zodiacColor");
+}
+
+void afxZodiacPolysoupRenderer::clear()
+{
+  Parent::clear();
+  for (S32 i = 0; i < polysoup_zodes.size(); i++)
+    if (polysoup_zodes[i].polys)
+      delete polysoup_zodes[i].polys;
+  polysoup_zodes.clear();
+}
+
+void afxZodiacPolysoupRenderer::addZodiac(U32 zode_idx, ConcretePolyList* polys, const Point3F& pos, F32 ang, const TSStatic* tss, F32 camDist)
+{
+  polysoup_zodes.increment();
+  PolysoupZodiacElem& elem = polysoup_zodes.last();
+
+  elem.tss = tss;
+  elem.polys = polys;
+  elem.zode_idx = zode_idx;
+  elem.ang = ang;
+  elem.camDist = camDist;
+}
+
+afxZodiacPolysoupRenderer* afxZodiacPolysoupRenderer::getMaster()
+{
+  if (!master)
+    master = new afxZodiacPolysoupRenderer;
+  return master;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+GFXStateBlock* afxZodiacPolysoupRenderer::chooseStateBlock(U32 blend, bool isReflectPass)
+{
+  GFXStateBlock* sb = 0;
+  
+  switch (blend)
+  {
+  case afxZodiacData::BLEND_ADDITIVE:
+    sb = (isReflectPass) ? add_refl_zb_SB : add_norefl_zb_SB;
+    break;
+  case afxZodiacData::BLEND_SUBTRACTIVE:
+    sb = (isReflectPass) ? sub_refl_zb_SB : sub_norefl_zb_SB;
+    break;
+  default: // afxZodiacData::BLEND_NORMAL:
+    sb = (isReflectPass) ? norm_refl_zb_SB : norm_norefl_zb_SB;
+    break;
+  }
+
+  return sb;
+}
+
+void afxZodiacPolysoupRenderer::render(SceneRenderState* state)
+{
+   PROFILE_SCOPE(afxRenderZodiacPolysoupMgr_render);
+
+   // Early out if no polysoup zodiacs to draw.
+   if (polysoup_zodes.size() == 0)
+     return;
+
+   initShader();
+   if (!zodiac_shader)
+     return;
+
+   bool is_reflect_pass = state->isReflectPass();
+
+   // Automagically save & restore our viewport and transforms.
+   GFXTransformSaver saver;
+
+   MatrixF proj = GFX->getProjectionMatrix();
+
+   // Set up world transform
+   MatrixF world = GFX->getWorldMatrix();
+   proj.mul(world);
+   shader_consts->set(projection_sc, proj);
+
+   //~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//
+   // RENDER EACH ZODIAC
+   //
+   for (S32 zz = 0; zz < polysoup_zodes.size(); zz++)
+   {
+      PolysoupZodiacElem& elem = polysoup_zodes[zz];
+
+      afxZodiacMgr::ZodiacSpec* zode = &afxZodiacMgr::inter_zodes[elem.zode_idx];
+      if (!zode)
+         continue;
+
+      if (is_reflect_pass)
+      {
+         if ((zode->zflags & afxZodiacData::SHOW_IN_REFLECTIONS) == 0)
+            continue;
+      }
+      else
+      {
+         if ((zode->zflags & afxZodiacData::SHOW_IN_NON_REFLECTIONS) == 0)
+            continue;
+      }
+
+      F32 fadebias = zode->calcDistanceFadeBias(elem.camDist);
+      if (fadebias < 0.01f)
+        continue;
+
+      F32 cos_ang = mCos(elem.ang);
+      F32 sin_ang = mSin(elem.ang);
+
+      GFXStateBlock* sb = chooseStateBlock(zode->zflags & afxZodiacData::BLEND_MASK, is_reflect_pass);
+
+      GFX->setShader(zodiac_shader->getShader());
+      GFX->setStateBlock(sb);
+      GFX->setShaderConstBuffer(shader_consts);
+
+      // set the texture
+      GFX->setTexture(0, *zode->txr);
+      LinearColorF zode_color = (LinearColorF)zode->color;
+      zode_color.alpha *= fadebias;
+      shader_consts->set(color_sc, zode_color);
+
+      F32 inv_radius = 1.0f/zode->radius_xy;
+
+      // FILTER USING GRADIENT RANGE
+      if ((zode->zflags & afxZodiacData::USE_GRADE_RANGE) != 0)
+      {
+        bool skip_oob;
+        F32 grade_min, grade_max;
+        if (elem.tss->mHasGradients && ((zode->zflags & afxZodiacData::PREFER_DEST_GRADE) != 0))
+        {
+          skip_oob = (elem.tss->mInvertGradientRange == false);
+          grade_min = elem.tss->mGradientRange.x;
+          grade_max = elem.tss->mGradientRange.y;
+        }
+        else
+        {
+          skip_oob = ((zode->zflags & afxZodiacData::INVERT_GRADE_RANGE) == 0);
+          grade_min = zode->grade_range.x;
+          grade_max = zode->grade_range.y;
+        }
+
+        PrimBuild::begin(GFXTriangleList, 3*elem.polys->mPolyList.size());
+        for (U32 i = 0; i < elem.polys->mPolyList.size(); i++)
+        {
+          ConcretePolyList::Poly* poly = &elem.polys->mPolyList[i];
+
+          const PlaneF& plane = poly->plane;
+
+          bool oob = (plane.z > grade_max || plane.z < grade_min);          
+          if (oob == skip_oob)
+            continue;
+
+          S32 vertind[3];
+          vertind[0] = elem.polys->mIndexList[poly->vertexStart];
+          vertind[1] = elem.polys->mIndexList[poly->vertexStart + 1];
+          vertind[2] = elem.polys->mIndexList[poly->vertexStart + 2];
+
+          for (U32 j = 0; j < 3; j++) 
+          {
+            Point3F vtx = elem.polys->mVertexList[vertind[j]];
+
+            // compute UV
+            F32 u1 = (vtx.x - zode->pos.x)*inv_radius;
+            F32 v1 = (vtx.y - zode->pos.y)*inv_radius;
+            F32 ru1 = u1*cos_ang - v1*sin_ang;
+            F32 rv1 = u1*sin_ang + v1*cos_ang;
+
+            F32 uu = (ru1 + 1.0f)/2.0f;
+            F32 vv = 1.0f - (rv1 + 1.0f)/2.0f;
+
+            PrimBuild::texCoord2f(uu, vv);
+            PrimBuild::vertex3fv(vtx);
+          }
+        }
+        PrimBuild::end(false);
+      }
+
+      // FILTER USING OTHER FILTERS
+      else if (zode->zflags & afxZodiacData::INTERIOR_FILTERS)
+      {
+        PrimBuild::begin(GFXTriangleList, 3*elem.polys->mPolyList.size());
+        for (U32 i = 0; i < elem.polys->mPolyList.size(); i++)
+        {
+          ConcretePolyList::Poly* poly = &elem.polys->mPolyList[i];
+
+          const PlaneF& plane = poly->plane;
+          if (zode->zflags & afxZodiacData::INTERIOR_HORIZ_ONLY)
+          {
+            if (!plane.isHorizontal())
+              continue;
+
+            if (zode->zflags & afxZodiacData::INTERIOR_BACK_IGNORE)
+            {
+              if (plane.whichSide(zode->pos) == PlaneF::Back)
+                continue;
+            }
+          }
+          else
+          {
+            if (zode->zflags & afxZodiacData::INTERIOR_VERT_IGNORE)
+            {
+              if (plane.isVertical())
+                continue;
+            }
+
+            if (zode->zflags & afxZodiacData::INTERIOR_BACK_IGNORE)
+            {
+              if (plane.whichSide(zode->pos) == PlaneF::Back)
+                continue;
+            }
+          }
+
+          S32 vertind[3];
+          vertind[0] = elem.polys->mIndexList[poly->vertexStart];
+          vertind[1] = elem.polys->mIndexList[poly->vertexStart + 1];
+          vertind[2] = elem.polys->mIndexList[poly->vertexStart + 2];
+
+          for (U32 j = 0; j < 3; j++) 
+          {
+            Point3F vtx = elem.polys->mVertexList[vertind[j]];
+
+            // compute UV
+            F32 u1 = (vtx.x - zode->pos.x)*inv_radius;
+            F32 v1 = (vtx.y - zode->pos.y)*inv_radius;
+            F32 ru1 = u1*cos_ang - v1*sin_ang;
+            F32 rv1 = u1*sin_ang + v1*cos_ang;
+
+            F32 uu = (ru1 + 1.0f)/2.0f;
+            F32 vv = 1.0f - (rv1 + 1.0f)/2.0f;
+
+            PrimBuild::texCoord2f(uu, vv);
+            PrimBuild::vertex3fv(vtx);
+          }
+        }
+        PrimBuild::end(false);
+      }
+
+      // NO FILTERING
+      else
+      {
+        PrimBuild::begin(GFXTriangleList, 3*elem.polys->mPolyList.size());
+        for (U32 i = 0; i < elem.polys->mPolyList.size(); i++) 
+        {
+          ConcretePolyList::Poly* poly = &elem.polys->mPolyList[i];
+
+          S32 vertind[3];
+          vertind[0] = elem.polys->mIndexList[poly->vertexStart];
+          vertind[1] = elem.polys->mIndexList[poly->vertexStart + 1];
+          vertind[2] = elem.polys->mIndexList[poly->vertexStart + 2];
+
+          for (U32 j = 0; j < 3; j++) 
+          {
+            Point3F vtx = elem.polys->mVertexList[vertind[j]];
+
+            // compute UV
+            F32 u1 = (vtx.x - zode->pos.x)*inv_radius;
+            F32 v1 = (vtx.y - zode->pos.y)*inv_radius;
+            F32 ru1 = u1*cos_ang - v1*sin_ang;
+            F32 rv1 = u1*sin_ang + v1*cos_ang;
+
+            F32 uu = (ru1 + 1.0f)/2.0f;
+            F32 vv = 1.0f - (rv1 + 1.0f)/2.0f;
+
+            PrimBuild::texCoord2f(uu, vv);
+            PrimBuild::vertex3fv(vtx);
+          }
+        }
+        PrimBuild::end(false);
+      }
+   }
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//

+ 92 - 0
Engine/source/afx/afxZodiacPolysoupRenderer_T3D.h

@@ -0,0 +1,92 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
+// Copyright (C) 2015 Faust Logic, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _AFX_ZODIAC_POLYSOUP_RENDERER_H_
+#define _AFX_ZODIAC_POLYSOUP_RENDERER_H_
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#include "renderInstance/renderBinManager.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+class ConcretePolyList;
+class TSStatic;
+
+class afxZodiacPolysoupRenderer : public RenderBinManager
+{
+   typedef RenderBinManager Parent;
+
+   struct PolysoupZodiacElem
+   {
+     const TSStatic*   tss;
+     U32               zode_idx;
+     ConcretePolyList* polys;
+     F32               ang;
+     F32               camDist;
+   };
+
+   Vector<PolysoupZodiacElem> polysoup_zodes;
+   static afxZodiacPolysoupRenderer* master;
+
+   GFXStateBlockRef norm_norefl_zb_SB, norm_refl_zb_SB;
+   GFXStateBlockRef add_norefl_zb_SB, add_refl_zb_SB;
+   GFXStateBlockRef sub_norefl_zb_SB, sub_refl_zb_SB;
+
+   ShaderData*    zodiac_shader;
+   GFXShaderConstBufferRef shader_consts;
+   GFXShaderConstHandle* projection_sc;
+   GFXShaderConstHandle* color_sc;
+
+   bool           shader_initialized;
+
+   GFXStateBlock* chooseStateBlock(U32 blend, bool isReflectPass);
+
+public:
+   static const RenderInstType RIT_PolysoupZodiac;
+
+   /*C*/          afxZodiacPolysoupRenderer();
+   /*C*/          afxZodiacPolysoupRenderer(F32 renderOrder, F32 processAddOrder);
+   /*D*/          ~afxZodiacPolysoupRenderer();
+
+   // RenderBinManager
+   virtual void   sort(){}  // don't sort them
+   virtual void   clear();
+
+   void           initShader();
+   void           addZodiac(U32 zode_idx, ConcretePolyList*, const Point3F& pos, F32 ang, const TSStatic*, F32 camDist);
+
+   virtual void   render(SceneRenderState*);
+
+   static afxZodiacPolysoupRenderer* getMaster();
+
+   // ConsoleObject
+   DECLARE_CONOBJECT(afxZodiacPolysoupRenderer);
+   DECLARE_CATEGORY("AFX");
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#endif // _AFX_ZODIAC_POLYSOUP_RENDERER_H_

+ 344 - 0
Engine/source/afx/afxZodiacTerrainRenderer_T3D.cpp

@@ -0,0 +1,344 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
+// Copyright (C) 2015 Faust Logic, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#include "afx/arcaneFX.h"
+
+#include "materials/shaderData.h"
+#include "gfx/gfxTransformSaver.h"
+#include "scene/sceneRenderState.h"
+#include "terrain/terrData.h"
+#include "terrain/terrCell.h"
+
+#include "gfx/primBuilder.h"
+
+#include "afx/ce/afxZodiacMgr.h"
+#include "afx/afxZodiacTerrainRenderer_T3D.h"
+#include "afx/util/afxTriBoxCheck2D_T3D.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+class TerrCellSpy : public TerrCell
+{
+public:
+  static const U32 getMinCellSize() { return smMinCellSize; }
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+const RenderInstType afxZodiacTerrainRenderer::RIT_TerrainZodiac("TerrainZodiac");
+
+afxZodiacTerrainRenderer* afxZodiacTerrainRenderer::master = 0;
+
+IMPLEMENT_CONOBJECT(afxZodiacTerrainRenderer);
+
+ConsoleDocClass( afxZodiacTerrainRenderer, 
+   "@brief A render bin for zodiac rendering on Terrain objects.\n\n"
+
+   "This bin renders instances of AFX zodiac effects onto Terrain surfaces.\n\n"
+
+   "@ingroup RenderBin\n"
+   "@ingroup AFX\n"
+);
+
+afxZodiacTerrainRenderer::afxZodiacTerrainRenderer()
+: RenderBinManager(RIT_TerrainZodiac, 1.0f, 1.0f)
+{
+   if (!master)
+     master = this;
+   shader_initialized = false;
+}
+
+afxZodiacTerrainRenderer::afxZodiacTerrainRenderer(F32 renderOrder, F32 processAddOrder)
+: RenderBinManager(RIT_TerrainZodiac, renderOrder, processAddOrder)
+{
+   if (!master)
+     master = this;
+   shader_initialized = false;
+}
+
+afxZodiacTerrainRenderer::~afxZodiacTerrainRenderer()
+{
+  if (this == master)
+    master = 0;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+void afxZodiacTerrainRenderer::initShader()
+{
+   if (shader_initialized)
+     return;
+
+   shader_initialized = true;
+
+   shader_consts = 0;
+   norm_norefl_zb_SB = norm_refl_zb_SB;
+   add_norefl_zb_SB = add_refl_zb_SB;
+   sub_norefl_zb_SB = sub_refl_zb_SB;
+
+   zodiac_shader = afxZodiacMgr::getTerrainZodiacShader();
+   if (!zodiac_shader)
+     return;
+
+   GFXStateBlockDesc d;
+
+   d.cullDefined = true;
+   d.ffLighting = false;
+   d.blendDefined = true;
+   d.blendEnable = true;
+   d.zDefined = false;
+   d.zEnable = true;
+   d.zWriteEnable = false;
+   d.zFunc = GFXCmpLessEqual;
+   d.zSlopeBias = 0;
+   d.alphaDefined = true;
+   d.alphaTestEnable = true; 
+   d.alphaTestRef = 0;
+   d.alphaTestFunc = GFXCmpGreater;
+   d.samplersDefined = true;
+   d.samplers[0] = GFXSamplerStateDesc::getClampLinear();
+
+   // normal
+   d.blendSrc = GFXBlendSrcAlpha;
+   d.blendDest = GFXBlendInvSrcAlpha;
+   //
+   d.cullMode = GFXCullCCW;
+   d.zBias = arcaneFX::sTerrainZodiacZBias;
+   norm_norefl_zb_SB = GFX->createStateBlock(d);
+   //
+   d.cullMode = GFXCullCW;
+   d.zBias = arcaneFX::sTerrainZodiacZBias;
+   norm_refl_zb_SB = GFX->createStateBlock(d);
+
+   // additive
+   d.blendSrc = GFXBlendSrcAlpha;
+   d.blendDest = GFXBlendOne;
+   //
+   d.cullMode = GFXCullCCW;
+   d.zBias = arcaneFX::sTerrainZodiacZBias;
+   add_norefl_zb_SB = GFX->createStateBlock(d);
+   //
+   d.cullMode = GFXCullCW;
+   d.zBias = arcaneFX::sTerrainZodiacZBias;
+   add_refl_zb_SB = GFX->createStateBlock(d);
+
+   // subtractive
+   d.blendSrc = GFXBlendZero;
+   d.blendDest = GFXBlendInvSrcColor;
+   //
+   d.cullMode = GFXCullCCW;
+   d.zBias = arcaneFX::sTerrainZodiacZBias;
+   sub_norefl_zb_SB = GFX->createStateBlock(d);
+   //
+   d.cullMode = GFXCullCW;
+   d.zBias = arcaneFX::sTerrainZodiacZBias;
+   sub_refl_zb_SB = GFX->createStateBlock(d);
+
+   shader_consts = zodiac_shader->getShader()->allocConstBuffer();
+   projection_sc = zodiac_shader->getShader()->getShaderConstHandle("$modelView");
+   color_sc = zodiac_shader->getShader()->getShaderConstHandle("$zodiacColor");
+}
+
+void afxZodiacTerrainRenderer::clear()
+{
+  Parent::clear();
+
+  terrain_zodes.clear();
+}
+
+void afxZodiacTerrainRenderer::addZodiac(U32 zode_idx, const Point3F& pos, F32 ang, 
+                                         const TerrainBlock* block, const TerrCell* cell, 
+                                         const MatrixF& mRenderObjToWorld, F32 camDist)
+{
+  terrain_zodes.increment();
+  TerrainZodiacElem& elem = terrain_zodes.last();
+
+  elem.block = block;
+  elem.cell = cell;
+  elem.zode_idx = zode_idx;
+  elem.ang = ang;
+  elem.mRenderObjToWorld = mRenderObjToWorld;
+  elem.camDist = camDist;
+}
+
+afxZodiacTerrainRenderer* afxZodiacTerrainRenderer::getMaster()
+{
+  if (!master)
+    master = new afxZodiacTerrainRenderer;
+  return master;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+GFXStateBlock* afxZodiacTerrainRenderer::chooseStateBlock(U32 blend, bool isReflectPass)
+{
+  GFXStateBlock* sb = 0;
+  
+  switch (blend)
+  {
+  case afxZodiacData::BLEND_ADDITIVE:
+    sb = (isReflectPass) ? add_refl_zb_SB : add_norefl_zb_SB;
+    break;
+  case afxZodiacData::BLEND_SUBTRACTIVE:
+    sb = (isReflectPass) ? sub_refl_zb_SB : sub_norefl_zb_SB;
+    break;
+  default: // afxZodiacData::BLEND_NORMAL:
+    sb = (isReflectPass) ? norm_refl_zb_SB : norm_norefl_zb_SB;
+    break;
+  }
+
+  return sb;
+}
+
+void afxZodiacTerrainRenderer::render(SceneRenderState* state)
+{
+   PROFILE_SCOPE(afxRenderZodiacTerrainMgr_render);
+
+   // Early out if no terrain zodiacs to draw.
+   if (terrain_zodes.size() == 0)
+     return;
+
+   initShader();
+   if (!zodiac_shader)
+     return;
+
+   bool is_reflect_pass = state->isReflectPass();
+
+   // Automagically save & restore our viewport and transforms.
+   GFXTransformSaver saver;
+
+   MatrixF proj = GFX->getProjectionMatrix();
+
+   // Set up world transform
+   MatrixF world = GFX->getWorldMatrix();
+   proj.mul(world);
+   shader_consts->set(projection_sc, proj);
+
+   //~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//
+   // RENDER EACH ZODIAC
+   //
+   for (S32 zz = 0; zz < terrain_zodes.size(); zz++)
+   {
+      TerrainZodiacElem& elem = terrain_zodes[zz];
+
+      TerrainBlock* block = (TerrainBlock*) elem.block;
+
+      afxZodiacMgr::ZodiacSpec* zode = &afxZodiacMgr::terr_zodes[elem.zode_idx];
+      if (!zode)
+         continue;
+
+      if (is_reflect_pass)
+      {
+         if ((zode->zflags & afxZodiacData::SHOW_IN_REFLECTIONS) == 0)
+            continue;
+      }
+      else
+      {
+         if ((zode->zflags & afxZodiacData::SHOW_IN_NON_REFLECTIONS) == 0)
+            continue;
+      }
+
+      F32 fadebias = zode->calcDistanceFadeBias(elem.camDist);
+      if (fadebias < 0.01f)
+        continue;
+
+      F32 cos_ang = mCos(elem.ang);
+      F32 sin_ang = mSin(elem.ang);
+
+      GFXStateBlock* sb = chooseStateBlock(zode->zflags & afxZodiacData::BLEND_MASK, is_reflect_pass);
+
+      GFX->setShader(zodiac_shader->getShader());
+      GFX->setStateBlock(sb);
+      GFX->setShaderConstBuffer(shader_consts);
+
+      // set the texture
+      GFX->setTexture(0, *zode->txr);
+      LinearColorF zode_color = (LinearColorF)zode->color;
+      zode_color.alpha *= fadebias;
+      shader_consts->set(color_sc, zode_color);
+
+      Point3F half_size(zode->radius_xy,zode->radius_xy,zode->radius_xy);
+
+      F32 inv_radius = 1.0f/zode->radius_xy;
+
+      GFXPrimitive cell_prim;
+      GFXVertexBufferHandle<TerrVertex> cell_verts;
+      GFXPrimitiveBufferHandle  primBuff;
+      elem.cell->getRenderPrimitive(&cell_prim, &cell_verts, &primBuff);
+
+      U32 n_nonskirt_tris = TerrCellSpy::getMinCellSize()*TerrCellSpy::getMinCellSize()*2;
+
+      const Point3F* verts = ((TerrCell*)elem.cell)->getZodiacVertexBuffer();
+      const U16 *tris = block->getZodiacPrimitiveBuffer();
+      if (!tris)
+         continue; 
+
+      PrimBuild::begin(GFXTriangleList, 3*n_nonskirt_tris);
+
+      /////////////////////////////////
+      U32 n_overlapping_tris = 0;
+      U32 idx = 0;
+      for (U32 i = 0; i < n_nonskirt_tris; i++)
+      {
+        Point3F tri_v[3];
+        tri_v[0] = verts[tris[idx++]];
+        tri_v[1] = verts[tris[idx++]];
+        tri_v[2] = verts[tris[idx++]];
+
+        elem.mRenderObjToWorld.mulP(tri_v[0]);
+        elem.mRenderObjToWorld.mulP(tri_v[1]);
+        elem.mRenderObjToWorld.mulP(tri_v[2]);
+
+        if (!afxTriBoxOverlap2D(zode->pos, half_size, tri_v[0], tri_v[1], tri_v[2]))
+          continue;
+
+        n_overlapping_tris++;
+
+        for (U32 j = 0; j < 3; j++)
+        {
+          // compute UV
+          F32 u1 = (tri_v[j].x - zode->pos.x)*inv_radius;
+          F32 v1 = (tri_v[j].y - zode->pos.y)*inv_radius;
+          F32 ru1 = u1*cos_ang - v1*sin_ang;
+          F32 rv1 = u1*sin_ang + v1*cos_ang;
+
+          F32 uu = (ru1 + 1.0f)/2.0f;
+          F32 vv = 1.0f - (rv1 + 1.0f)/2.0f;
+
+          PrimBuild::texCoord2f(uu, vv);
+          PrimBuild::vertex3fv(tri_v[j]);
+        }
+      }
+
+      /////////////////////////////////
+
+      PrimBuild::end(false);
+   }
+   //
+   // RENDER EACH ZODIAC
+   //~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//

+ 92 - 0
Engine/source/afx/afxZodiacTerrainRenderer_T3D.h

@@ -0,0 +1,92 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
+// Copyright (C) 2015 Faust Logic, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _AFX_ZODIAC_TERRAIN_RENDERER_H_
+#define _AFX_ZODIAC_TERRAIN_RENDERER_H_
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#include "renderInstance/renderBinManager.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+class TerrCell;
+
+class afxZodiacTerrainRenderer : public RenderBinManager
+{
+   typedef RenderBinManager Parent;
+
+   struct TerrainZodiacElem
+   {
+     const TerrainBlock* block;
+     const TerrCell*   cell;
+     U32               zode_idx;
+     F32               ang;
+     MatrixF           mRenderObjToWorld;
+     F32               camDist;
+   };
+
+   Vector<TerrainZodiacElem> terrain_zodes;
+   static afxZodiacTerrainRenderer* master;
+
+   GFXStateBlockRef norm_norefl_zb_SB, norm_refl_zb_SB;
+   GFXStateBlockRef add_norefl_zb_SB, add_refl_zb_SB;
+   GFXStateBlockRef sub_norefl_zb_SB, sub_refl_zb_SB;
+
+   ShaderData*    zodiac_shader;
+   GFXShaderConstBufferRef shader_consts;
+   GFXShaderConstHandle* projection_sc;
+   GFXShaderConstHandle* color_sc;
+
+   bool           shader_initialized;
+
+   GFXStateBlock* chooseStateBlock(U32 blend, bool isReflectPass);
+
+public:
+   static const RenderInstType RIT_TerrainZodiac;
+
+   /*C*/          afxZodiacTerrainRenderer();
+   /*C*/          afxZodiacTerrainRenderer(F32 renderOrder, F32 processAddOrder);
+   /*D*/          ~afxZodiacTerrainRenderer();
+
+   // RenderBinManager
+   virtual void   sort(){}  // don't sort them
+   virtual void   clear();
+
+   void           initShader();
+   void           addZodiac(U32 zode_idx, const Point3F& pos, F32 ang, const TerrainBlock*, const TerrCell*, const MatrixF& mRenderObjToWorld, F32 camDist);
+
+   virtual void   render(SceneRenderState*);
+
+   static afxZodiacTerrainRenderer* getMaster();
+
+   // ConsoleObject
+   DECLARE_CONOBJECT(afxZodiacTerrainRenderer);
+   DECLARE_CATEGORY("AFX");
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#endif // _AFX_ZODIAC_TERRAIN_RENDERER_H_

+ 959 - 0
Engine/source/afx/arcaneFX.cpp

@@ -0,0 +1,959 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
+// Copyright (C) 2015 Faust Logic, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#include "afx/arcaneFX.h"
+
+#include "scene/sceneObject.h"
+#include "scene/sceneManager.h"
+#include "T3D/gameBase/gameConnection.h"
+#include "T3D/gameBase/gameProcess.h"
+#include "T3D/player.h"
+#include "math/mathUtils.h"
+#include "console/compiler.h"
+#include "console/engineAPI.h"
+
+#include "afx/afxChoreographer.h"
+#include "afx/afxSelectron.h"
+#include "afx/afxResidueMgr.h"
+#include "afx/ce/afxZodiacMgr.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#define N_LIGHTING_MODELS 6
+//
+// "SG - Original Advanced (Lighting Pack)"
+// "SG - Original Stock (Lighting Pack)"
+// "SG - Inverse Square (Lighting Pack)"
+// "SG - Inverse Square Fast Falloff (Lighting Pack)"
+// "SG - Near Linear (Lighting Pack)"
+// "SG - Near Linear Fast Falloff (Lighting Pack)"
+static StringTableEntry lm_old_names[N_LIGHTING_MODELS];
+//
+// "Original Advanced"
+// "Original Stock"
+// "Inverse Square"
+// "Inverse Square Fast Falloff"
+// "Near Linear"
+// "Near Linear Fast Falloff"
+static StringTableEntry lm_new_names[N_LIGHTING_MODELS];
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+class ClientZoneInEvent : public NetEvent
+{
+  typedef NetEvent Parent;
+public:
+  ClientZoneInEvent() { mGuaranteeType = Guaranteed; }
+  ~ClientZoneInEvent() { }
+
+  virtual void pack(NetConnection*, BitStream*bstream) { }
+  virtual void write(NetConnection*, BitStream *bstream) { }
+  virtual void unpack(NetConnection* /*ps*/, BitStream *bstream) { }
+
+  virtual void process(NetConnection* conn)
+  {
+    GameConnection* game_conn = dynamic_cast<GameConnection*>(conn);
+    if (game_conn && !game_conn->isZonedIn())
+    {
+      arcaneFX::syncToNewConnection(game_conn);
+    }
+  }
+
+  DECLARE_CONOBJECT(ClientZoneInEvent);
+  DECLARE_CATEGORY("AFX");
+};
+IMPLEMENT_CO_SERVEREVENT_V1(ClientZoneInEvent);
+
+ConsoleDocClass( ClientZoneInEvent,
+				"@brief Event posted when player is fully loaded into the game and ready for interaction.\n\n"
+				"@internal");
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+Vector<afxChoreographer*> arcaneFX::active_choreographers;
+Vector<afxChoreographer*> arcaneFX::client_choreographers;
+Vector<afxSelectronData*> arcaneFX::selectrons;
+Vector<SceneObject*>      arcaneFX::scoped_objs;
+
+StringTableEntry arcaneFX::NULLSTRING = 0;
+U32              arcaneFX::sTargetSelectionMask = 0;
+U32              arcaneFX::sFreeTargetSelectionMask = 0;
+bool             arcaneFX::sIsFreeTargeting = false;
+Point3F          arcaneFX::sFreeTargetPos = Point3F(0.0f, 0.0f, 0.0f);
+bool             arcaneFX::sFreeTargetPosValid = false;
+F32              arcaneFX::sTargetSelectionRange = 200.0f;
+U32              arcaneFX::sTargetSelectionTimeoutMS = 500;
+bool             arcaneFX::sClickToTargetSelf = false;
+U32              arcaneFX::sMissileCollisionMask = 0;
+StringTableEntry arcaneFX::sParameterFieldPrefix = 0;
+F32              arcaneFX::sTerrainZodiacZBias = -0.00025f;
+F32              arcaneFX::sInteriorZodiacZBias = -0.0001f;
+F32              arcaneFX::sPolysoupZodiacZBias = -0.0001f;
+U32              arcaneFX::master_choreographer_id = 1;
+U16              arcaneFX::master_scope_id = 1;
+bool             arcaneFX::is_shutdown = true;
+
+bool             arcaneFX::sUsePlayerCentricListener = false;
+
+void arcaneFX::init()
+{
+  NULLSTRING = StringTable->insert("");
+  sParameterFieldPrefix = StringTable->insert("_");
+
+#if defined(TORQUE_OS_MAC)
+  arcaneFX::sTerrainZodiacZBias = -0.00025f;
+  arcaneFX::sInteriorZodiacZBias = -0.00025f;
+  arcaneFX::sPolysoupZodiacZBias = -0.00025f;
+#endif
+
+  Con::addVariable(  "pref::AFX::targetSelectionMask",      TypeS32,    &sTargetSelectionMask);
+  Con::addVariable(  "pref::AFX::freeTargetSelectionMask",  TypeS32,    &sFreeTargetSelectionMask);
+  Con::addVariable(  "pref::AFX::targetSelectionRange",     TypeF32,    &sTargetSelectionRange);
+  Con::addVariable(  "pref::AFX::targetSelectionTimeoutMS", TypeS32,    &sTargetSelectionTimeoutMS);
+  Con::addVariable(  "pref::AFX::missileCollisionMask",     TypeS32,    &sMissileCollisionMask);
+  Con::addVariable(  "pref::AFX::clickToTargetSelf",        TypeBool,   &sClickToTargetSelf);
+  Con::addVariable(  "Pref::Server::AFX::parameterFieldPrefix",     TypeString,  &sParameterFieldPrefix);
+
+  Con::addVariable(  "pref::AFX::terrainZodiacZBias",       TypeF32,    &sTerrainZodiacZBias);
+  Con::addVariable(  "pref::AFX::interiorZodiacZBias",      TypeF32,    &sInteriorZodiacZBias);
+  Con::addVariable(  "pref::AFX::polysoupZodiacZBias",      TypeF32,    &sPolysoupZodiacZBias);
+
+  Con::setIntVariable(    "$AFX::TARGETING_OFF",                TARGETING_OFF);
+  Con::setIntVariable(    "$AFX::TARGETING_STANDARD",           TARGETING_STANDARD);
+  Con::setIntVariable(    "$AFX::TARGETING_FREE",               TARGETING_FREE);
+  Con::setIntVariable(    "$AFX::TARGET_CHECK_POLL",            TARGET_CHECK_POLL);
+  Con::setIntVariable(    "$AFX::TARGET_CHECK_ON_MOUSE_MOVE",   TARGET_CHECK_ON_MOUSE_MOVE);
+
+  Con::setIntVariable(    "$AFX::IMPACTED_SOMETHING", afxEffectDefs::IMPACTED_SOMETHING);
+  Con::setIntVariable(    "$AFX::IMPACTED_TARGET",    afxEffectDefs::IMPACTED_TARGET);
+  Con::setIntVariable(    "$AFX::IMPACTED_PRIMARY",   afxEffectDefs::IMPACTED_PRIMARY);
+  Con::setIntVariable(    "$AFX::IMPACT_IN_WATER",    afxEffectDefs::IMPACT_IN_WATER);
+  Con::setIntVariable(    "$AFX::CASTER_IN_WATER",    afxEffectDefs::CASTER_IN_WATER);
+
+  Con::setIntVariable(    "$AFX::SERVER_ONLY",        afxEffectDefs::SERVER_ONLY);
+  Con::setIntVariable(    "$AFX::SCOPE_ALWAYS",       afxEffectDefs::SCOPE_ALWAYS);
+  Con::setIntVariable(    "$AFX::GHOSTABLE",          afxEffectDefs::GHOSTABLE);
+  Con::setIntVariable(    "$AFX::CLIENT_ONLY",        afxEffectDefs::CLIENT_ONLY);
+  Con::setIntVariable(    "$AFX::SERVER_AND_CLIENT",  afxEffectDefs::SERVER_AND_CLIENT);
+
+  Con::setIntVariable(    "$AFX::DELAY",              afxEffectDefs::TIMING_DELAY);
+  Con::setIntVariable(    "$AFX::LIFETIME",           afxEffectDefs::TIMING_LIFETIME);
+  Con::setIntVariable(    "$AFX::FADE_IN_TIME",       afxEffectDefs::TIMING_FADE_IN);
+  Con::setIntVariable(    "$AFX::FADE_OUT_TIME",      afxEffectDefs::TIMING_FADE_OUT);
+
+  Con::setFloatVariable(  "$AFX::INFINITE_TIME",      -1.0f);
+  Con::setIntVariable(    "$AFX::INFINITE_REPEATS",   -1);
+
+  Con::setIntVariable(    "$AFX::PLAYER_MOVE_TRIGGER_0",      Player::PLAYER_MOVE_TRIGGER_0);
+  Con::setIntVariable(    "$AFX::PLAYER_MOVE_TRIGGER_1",      Player::PLAYER_MOVE_TRIGGER_1);
+  Con::setIntVariable(    "$AFX::PLAYER_MOVE_TRIGGER_2",      Player::PLAYER_MOVE_TRIGGER_2);
+  Con::setIntVariable(    "$AFX::PLAYER_MOVE_TRIGGER_3",      Player::PLAYER_MOVE_TRIGGER_3);
+  Con::setIntVariable(    "$AFX::PLAYER_MOVE_TRIGGER_4",      Player::PLAYER_MOVE_TRIGGER_4);
+  Con::setIntVariable(    "$AFX::PLAYER_MOVE_TRIGGER_5",      Player::PLAYER_MOVE_TRIGGER_5);
+
+  Con::setIntVariable(    "$AFX::PLAYER_FIRE_S_TRIGGER",      Player::PLAYER_FIRE_S_TRIGGER);
+  Con::setIntVariable(    "$AFX::PLAYER_FIRE_ALT_S_TRIGGER",  Player::PLAYER_FIRE_ALT_S_TRIGGER);
+  Con::setIntVariable(    "$AFX::PLAYER_JUMP_S_TRIGGER",      Player::PLAYER_JUMP_S_TRIGGER);
+  Con::setIntVariable(    "$AFX::PLAYER_LANDING_S_TRIGGER",   Player::PLAYER_LANDING_S_TRIGGER);
+
+  Con::setIntVariable(    "$AFX::PLAYER_LF_FOOT_C_TRIGGER",   Player::PLAYER_LF_FOOT_C_TRIGGER);
+  Con::setIntVariable(    "$AFX::PLAYER_RT_FOOT_C_TRIGGER",   Player::PLAYER_RT_FOOT_C_TRIGGER);
+  Con::setIntVariable(    "$AFX::PLAYER_LANDING_C_TRIGGER",   Player::PLAYER_LANDING_C_TRIGGER);
+  Con::setIntVariable(    "$AFX::PLAYER_IDLE_C_TRIGGER",      Player::PLAYER_IDLE_C_TRIGGER);
+
+  Con::setIntVariable(    "$AFX::ILLUM_TERRAIN",      0);
+  Con::setIntVariable(    "$AFX::ILLUM_ATLAS",        0);
+  Con::setIntVariable(    "$AFX::ILLUM_DIF",          0);
+  Con::setIntVariable(    "$AFX::ILLUM_DTS",          0);
+  Con::setIntVariable(    "$AFX::ILLUM_ALL",          0);
+
+  Con::setIntVariable("$TypeMasks::TerrainLikeObjectType", TerrainLikeObjectType);
+  Con::setIntVariable("$TypeMasks::InteriorLikeObjectType", InteriorLikeObjectType);
+  Con::setIntVariable("$TypeMasks::PolysoupObjectType", InteriorLikeObjectType); // deprecated
+
+  Con::addVariable("$pref::Audio::usePlayerCentricListener", TypeBool, &sUsePlayerCentricListener);
+
+  afxResidueMgr* residue_mgr = new afxResidueMgr;
+  afxResidueMgr::setMaster(residue_mgr);
+
+  master_scope_id = 1;
+  master_choreographer_id = 1;
+  is_shutdown = false;
+
+  if (lm_old_names[0] == 0)
+  {
+    lm_old_names[0] = StringTable->insert("SG - Original Advanced (Lighting Pack)");
+    lm_old_names[1] = StringTable->insert("SG - Original Stock (Lighting Pack)");
+    lm_old_names[2] = StringTable->insert("SG - Inverse Square (Lighting Pack)");
+    lm_old_names[3] = StringTable->insert("SG - Inverse Square Fast Falloff (Lighting Pack)");
+    lm_old_names[4] = StringTable->insert("SG - Near Linear (Lighting Pack)");
+    lm_old_names[5] = StringTable->insert("SG - Near Linear Fast Falloff (Lighting Pack)");
+    //
+    lm_new_names[0] = StringTable->insert("Original Advanced");
+    lm_new_names[1] = StringTable->insert("Original Stock");
+    lm_new_names[2] = StringTable->insert("Inverse Square");
+    lm_new_names[3] = StringTable->insert("Inverse Square Fast Falloff");
+    lm_new_names[4] = StringTable->insert("Near Linear");
+    lm_new_names[5] = StringTable->insert("Near Linear Fast Falloff");
+  }
+}
+
+void arcaneFX::shutdown()
+{
+  is_shutdown = true;
+
+  for (S32 i = 0; i < scoped_objs.size(); i++)
+     if (scoped_objs[i])
+       scoped_objs[i]->setScopeRegistered(false);
+  scoped_objs.clear();
+
+  for (S32 i = 0; i < client_choreographers.size(); i++)
+     if (client_choreographers[i])
+       client_choreographers[i]->clearChoreographerId();
+  client_choreographers.clear();
+
+  for (S32 i = 0; i < selectrons.size(); i++)
+    if (selectrons[i])
+       selectrons[i]->registered = false;
+  selectrons.clear();
+
+  afxResidueMgr* residue_mgr = afxResidueMgr::getMaster();
+  delete residue_mgr;
+  afxResidueMgr::setMaster(NULL);
+}
+
+MODULE_BEGIN( arcaneFX )
+
+   MODULE_INIT
+   {
+      arcaneFX::init();
+   }
+
+   MODULE_SHUTDOWN
+   {
+      arcaneFX::shutdown();
+   }
+
+MODULE_END;
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+void arcaneFX::advanceTime(U32 delta)
+{
+  GameConnection* conn = GameConnection::getConnectionToServer();
+  if (conn && !conn->isZonedIn() && conn->getCameraObject() != 0)
+  {
+    conn->setZonedIn();
+    conn->postNetEvent(new ClientZoneInEvent());
+  }
+
+  afxZodiacMgr::frameReset();
+  afxResidueMgr::getMaster()->residueAdvanceTime();
+}
+
+//
+
+U32 arcaneFX::registerChoreographer(afxChoreographer* ch)
+{
+  if (!ch)
+    return 0;
+
+  active_choreographers.push_back(ch);
+
+  //Con::printf("registerChoreographer() -- size=%d %s", active_choreographers.size(),
+  //  (ch->isServerObject()) ? "server" : "client");
+
+  return master_choreographer_id++;
+}
+
+void arcaneFX::unregisterChoreographer(afxChoreographer* ch)
+{
+  if (!ch)
+    return;
+
+  for (U32 i = 0; i < active_choreographers.size(); i++)
+  {
+    if (ch == active_choreographers[i])
+    {
+      active_choreographers.erase_fast(i);
+      //Con::printf("unregisterChoreographer() -- size=%d %s", active_choreographers.size(),
+      //  (ch->isServerObject()) ? "server" : "client");
+      return;
+    }
+  }
+
+  Con::errorf("arcaneFX::unregisterChoreographer() -- failed to find choreographer in list.");
+}
+
+void arcaneFX::registerClientChoreographer(afxChoreographer* ch)
+{
+  if (!ch || ch->getChoreographerId() == 0)
+    return;
+
+  client_choreographers.push_back(ch);
+}
+
+void arcaneFX::unregisterClientChoreographer(afxChoreographer* ch)
+{
+  if (!ch || ch->getChoreographerId() == 0)
+    return;
+
+  for (U32 i = 0; i < client_choreographers.size(); i++)
+  {
+    if (ch == client_choreographers[i])
+    {
+      client_choreographers.erase_fast(i);
+      return;
+    }
+  }
+
+  Con::errorf("arcaneFX::unregisterClientChoreographer() -- failed to find choreographer in list.");
+}
+
+afxChoreographer* arcaneFX::findClientChoreographer(U32 id)
+{
+  for (U32 i = 0; i < client_choreographers.size(); i++)
+  {
+    if (id == client_choreographers[i]->getChoreographerId())
+      return client_choreographers[i];
+  }
+
+  return 0;
+}
+
+//
+
+void arcaneFX::registerSelectronData(afxSelectronData* selectron)
+{
+  if (!selectron)
+    return;
+
+  selectrons.push_back(selectron);
+}
+
+void arcaneFX::unregisterSelectronData(afxSelectronData* selectron)
+{
+  if (!selectron)
+    return;
+
+  for (U32 i = 0; i < selectrons.size(); i++)
+  {
+    if (selectron == selectrons[i])
+    {
+      selectrons.erase_fast(i);
+      return;
+    }
+  }
+
+  Con::errorf("arcaneFX::unregisterSelectronData() -- failed to find selectron in list.");
+}
+
+afxSelectronData* arcaneFX::findSelectronData(U32 mask, U8 style)
+{
+  for (U32 i = 0; i < selectrons.size(); i++)
+    if (selectrons[i]->matches(mask, style))
+      return selectrons[i];
+
+  return 0;
+}
+
+U16 arcaneFX::generateScopeId()
+{
+  U16 ret_id = master_scope_id++;
+  if (master_scope_id >= BIT(GameBase::SCOPE_ID_BITS))
+    master_scope_id = 1;
+  return ret_id;
+}
+
+void arcaneFX::registerScopedObject(SceneObject* object)
+{
+  scoped_objs.push_back(object);
+  object->setScopeRegistered(true);
+
+  for (S32 i = 0; i < client_choreographers.size(); i++)
+    if (client_choreographers[i])
+      client_choreographers[i]->restoreScopedObject(object);
+}
+
+SceneObject* arcaneFX::findScopedObject(U16 scope_id)
+{
+  if (scoped_objs.size() > 0)
+  {
+    for (S32 i = scoped_objs.size()-1; i >= 0; i--)
+      if (scoped_objs[i] && scoped_objs[i]->getScopeId() == scope_id)
+        return scoped_objs[i];
+  }
+  return 0;
+}
+
+void arcaneFX::unregisterScopedObject(SceneObject* object)
+{
+  if (scoped_objs.size() > 0)
+  {
+    for (S32 i = scoped_objs.size()-1; i >= 0; i--)
+      if (scoped_objs[i] == object)
+      {
+        scoped_objs.erase_fast(i);
+        if (object)
+          object->setScopeRegistered(false);
+        return;
+      }
+  }
+}
+
+void arcaneFX::syncToNewConnection(GameConnection* conn)
+{
+  if (conn)
+    conn->setZonedIn();
+
+  for (U32 i = 0; i < active_choreographers.size(); i++)
+  {
+    if (active_choreographers[i])
+      active_choreographers[i]->sync_with_clients();
+  }
+}
+
+void arcaneFX::endMissionNotify()
+{
+  for (S32 i = 0; i < scoped_objs.size(); i++)
+     if (scoped_objs[i])
+       scoped_objs[i]->setScopeRegistered(false);
+  scoped_objs.clear();
+
+  for (S32 i = 0; i < client_choreographers.size(); i++)
+     if (client_choreographers[i])
+       client_choreographers[i]->clearChoreographerId();
+  client_choreographers.clear();
+
+  for (S32 i = 0; i < selectrons.size(); i++)
+    if (selectrons[i])
+       selectrons[i]->registered = false;
+  selectrons.clear();
+
+  if (afxResidueMgr::getMaster())
+    afxResidueMgr::getMaster()->cleanup();
+  afxZodiacMgr::missionCleanup();
+}
+
+S32 arcaneFX::rolloverRayCast(Point3F start, Point3F end, U32 mask)
+{
+  sIsFreeTargeting = false;
+#if !defined(AFX_CAP_ROLLOVER_RAYCASTS)
+  return -1;
+#else
+  GameConnection* conn = GameConnection::getConnectionToServer();
+  SceneObject* ctrl_obj = NULL;
+
+  if (!arcaneFX::sClickToTargetSelf && conn != NULL)
+    ctrl_obj = conn->getControlObject();
+
+  if (ctrl_obj)
+    ctrl_obj->disableCollision();
+
+  SceneObject* rollover_obj = (conn) ? conn->getRolloverObj() : 0;
+  SceneObject* picked_obj = 0;
+
+  RayInfo hit_info;
+  if (gClientContainer.castRay(start, end, mask, &hit_info))
+    picked_obj = dynamic_cast<SceneObject*>(hit_info.object);
+
+  if (ctrl_obj)
+    ctrl_obj->enableCollision();
+
+  if (picked_obj != rollover_obj)
+  {
+    if (rollover_obj)
+      rollover_obj->setSelectionFlags(rollover_obj->getSelectionFlags() & ~SceneObject::PRE_SELECTED);
+    if (picked_obj)
+      picked_obj->setSelectionFlags(picked_obj->getSelectionFlags() | SceneObject::PRE_SELECTED);
+    rollover_obj = picked_obj;
+
+    if (conn)
+      conn->setRolloverObj(rollover_obj);
+  }
+
+  return (picked_obj) ? picked_obj->getId() : -1;
+#endif
+}
+
+bool arcaneFX::freeTargetingRayCast(Point3F start, Point3F end, U32 mask)
+{
+  sIsFreeTargeting = true;
+
+  RayInfo hit_info;
+  if (!gClientContainer.castRay(start, end, mask, &hit_info))
+  {
+    sFreeTargetPosValid = false;
+    return false;
+  }
+
+  sFreeTargetPosValid = true;
+  sFreeTargetPos = hit_info.point;
+
+  return true;
+}
+
+StringTableEntry arcaneFX::convertLightingModelName(StringTableEntry lm_name)
+{
+  for (U32 i = 0; i < N_LIGHTING_MODELS; i++)
+  {
+    if (lm_name == lm_old_names[i])
+      return lm_new_names[i];
+  }
+
+  return lm_name;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Console Functions
+
+DefineEngineFunction(afxEndMissionNotify, void, (),,
+                     "...\n\n"
+                     "@ingroup AFX")
+{
+  arcaneFX::endMissionNotify();
+}
+
+DefineEngineFunction(afxGetVersion, const char*, (),,
+                     "...\n\n"
+                     "@ingroup AFX")
+{
+  return AFX_VERSION_STRING;
+}
+
+DefineEngineFunction(afxGetEngine, const char*, (),,
+                     "...\n\n"
+                     "@ingroup AFX")
+{
+  return "T3D";
+}
+
+#if defined(AFX_CAP_ROLLOVER_RAYCASTS)
+DefineEngineFunction(rolloverRayCast, S32, (Point3F start, Point3F end, U32 mask),,
+                     "Performs a raycast from points start to end and returns the ID of nearest "
+                     "intersecting object with a type found in the specified mask. "
+                     "Returns -1 if no object is found.\n\n"
+                     "@ingroup AFX")
+{
+  return arcaneFX::rolloverRayCast(start, end, mask);
+}
+#endif
+
+DefineEngineFunction(getRandomF, F32, (float a, float b), (F32_MAX, F32_MAX),
+                "Get a random float number between a and b.\n\n"
+                "@ingroup AFX")
+{
+  if (b == F32_MAX)
+  {
+    if (a == F32_MAX)
+      return gRandGen.randF();
+
+    return gRandGen.randF(0.0f, a);
+  }
+
+  return (a + (b-a)*gRandGen.randF());
+}
+
+DefineEngineFunction(getRandomDir, Point3F, (Point3F axis, float thetaMin, float thetaMax, float phiMin, float phiMax),
+                     (Point3F(0.0f,0.0f,0.0f), 0.0f, 180.0f, 0.0f, 360.0f),
+                     "Get a random direction vector.\n\n"
+                     "@ingroup AFX")
+{
+  return MathUtils::randomDir(axis, thetaMin, thetaMax, phiMin, phiMax);
+}
+
+ConsoleFunction( MatrixInverseMulVector, const char*, 3, 3, "(MatrixF xfrm, Point3F vector)"
+                "@brief Multiply the vector by the affine inverse of the transform.\n\n"
+                "@ingroup AFX")
+{
+   Point3F pos1(0.0f,0.0f,0.0f);
+   AngAxisF aa1(Point3F(0.0f,0.0f,0.0f),0.0f);
+   dSscanf(argv[1], "%g %g %g %g %g %g %g", &pos1.x, &pos1.y, &pos1.z, &aa1.axis.x, &aa1.axis.y, &aa1.axis.z, &aa1.angle);
+
+   MatrixF temp1(true);
+   aa1.setMatrix(&temp1);
+   temp1.setColumn(3, pos1);
+
+   Point3F vec1(0.0f,0.0f,0.0f);
+   dSscanf(argv[2], "%g %g %g", &vec1.x, &vec1.y, &vec1.z);
+
+   temp1.affineInverse();
+
+   Point3F result;
+   temp1.mulV(vec1, &result);
+
+   char* ret = Con::getReturnBuffer(256);
+   dSprintf(ret, 255, "%g %g %g", result.x, result.y, result.z);
+   return ret;
+}
+
+ConsoleFunction(moveTransformAbs, const char*, 3, 3, "(MatrixF xfrm, Point3F pos)"
+                "@brief Move the transform to the new absolute position.\n\n"
+                "@ingroup AFX")
+{
+   Point3F pos1(0.0f,0.0f,0.0f);
+   AngAxisF aa1(Point3F(0.0f,0.0f,0.0f),0.0f);
+   dSscanf(argv[1], "%g %g %g %g %g %g %g", &pos1.x, &pos1.y, &pos1.z, &aa1.axis.x, &aa1.axis.y, &aa1.axis.z, &aa1.angle);
+
+   Point3F pos2(0.0f,0.0f,0.0f);
+   dSscanf(argv[2], "%g %g %g", &pos2.x, &pos2.y, &pos2.z);
+
+   char* returnBuffer = Con::getReturnBuffer(256);
+   dSprintf(returnBuffer, 255, "%g %g %g %g %g %g %g",
+            pos2.x, pos2.y, pos2.z,
+            aa1.axis.x, aa1.axis.y, aa1.axis.z,
+            aa1.angle);
+   return returnBuffer;
+}
+
+ConsoleFunction(moveTransformRel, const char*, 3, 3, "(MatrixF xfrm, Point3F pos)"
+                "@brief Move the transform to the new relative position.\n\n"
+                "@ingroup AFX")
+{
+   Point3F pos1(0.0f,0.0f,0.0f);
+   AngAxisF aa1(Point3F(0.0f,0.0f,0.0f),0.0f);
+   dSscanf(argv[1], "%g %g %g %g %g %g %g", &pos1.x, &pos1.y, &pos1.z, &aa1.axis.x, &aa1.axis.y, &aa1.axis.z, &aa1.angle);
+
+   Point3F pos2(0.0f,0.0f,0.0f);
+   dSscanf(argv[2], "%g %g %g", &pos2.x, &pos2.y, &pos2.z);
+
+   pos2 += pos1;
+
+   char* returnBuffer = Con::getReturnBuffer(256);
+   dSprintf(returnBuffer, 255, "%g %g %g %g %g %g %g",
+            pos2.x, pos2.y, pos2.z,
+            aa1.axis.x, aa1.axis.y, aa1.axis.z,
+            aa1.angle);
+   return returnBuffer;
+}
+
+DefineEngineFunction(getFreeTargetPosition, Point3F, (),,
+                     "@brief Returns the current location of the free target.\n\n"
+                     "@ingroup AFX")
+{
+  if (!arcaneFX::sFreeTargetPosValid)
+    return Point3F(0.0f, 0.0f, 0.0f);
+  return arcaneFX::sFreeTargetPos;
+}
+
+DefineEngineMethod(SceneObject, getSpeed, F32, (),,
+                   "Returns the velocity of a scene-object.\n\n"
+                   "@ingroup AFX")
+{
+   return object->getVelocity().len();
+}
+
+static S32 mark_modkey = -1;
+
+DefineEngineFunction(markDataBlocks, void, (),,
+                     "@brief Called before a series of datablocks are reloaded to "
+                     "help distinguish reloaded datablocks from already loaded ones.\n\n"
+                     "@ingroup AFX")
+{
+  mark_modkey = SimDataBlock::getNextModifiedKey();
+}
+
+DefineEngineFunction(touchDataBlocks, void, (),,
+                     "@brief Called after a series of datablocks are reloaded to "
+                     "trigger some important actions on the reloaded datablocks.\n\n"
+                     "@ingroup AFX")
+{
+  if (mark_modkey < 0)
+    return;
+
+  SimDataBlockGroup* g = Sim::getDataBlockGroup();
+
+  U32 groupCount = g->size();
+  for (S32 i = groupCount-1; i >= 0; i--)
+  {
+    SimDataBlock* simdb = (SimDataBlock*)(*g)[i];
+    if (simdb->getModifiedKey() > mark_modkey)
+    {
+      simdb->unregisterObject();
+      simdb->registerObject();
+    }
+  }
+
+  mark_modkey = -1;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//
+// Syntax Error Checking
+// (for checking eval() and compile() calls)
+
+DefineEngineFunction(wasSyntaxError, bool, (),,
+                     "@brief Returns true if script compiler had a syntax error. Useful "
+                     "for detecting syntax errors after reloading a script.\n\n"
+                     "@ingroup AFX")
+{
+  return Compiler::gSyntaxError;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//
+// Network Object Identification
+
+//  These useful console methods come from the following code resource:
+//
+//  How to Identify Objects from Client to Server or Server to Client by Nathan Davies
+//    http://www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=4852
+//
+
+DefineEngineMethod(NetConnection, GetGhostIndex, S32, (NetObject* obj),,
+                   "Returns the ghost-index for an object.\n\n"
+                   "@ingroup AFX")
+{
+  if (obj)
+    return object->getGhostIndex(obj);
+  return 0;
+}
+
+DefineEngineMethod(NetConnection, ResolveGhost, S32, (int ghostIndex),,
+                   "Resolves a ghost-index into an object ID.\n\n"
+                   "@ingroup AFX")
+{
+  if (ghostIndex != -1)
+  {
+    NetObject* pObject = NULL;
+    if( object->isGhostingTo())
+      pObject = object->resolveGhost(ghostIndex);
+    else if( object->isGhostingFrom())
+      pObject = object->resolveObjectFromGhostIndex(ghostIndex);
+    if (pObject)
+      return pObject->getId();
+  }
+  return 0;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+//////////////////////////////////////////////////////////////////////////
+// TypeByteRange
+//////////////////////////////////////////////////////////////////////////
+
+IMPLEMENT_STRUCT( ByteRange, ByteRange,,
+   "" )
+END_IMPLEMENT_STRUCT;
+
+ConsoleType( ByteRange, TypeByteRange, ByteRange, "")
+ConsoleType( ByteRange, TypeByteRange2, ByteRange, "")
+
+ConsoleGetType( TypeByteRange )
+{
+   ByteRange* pt = (ByteRange *) dptr;
+   char* returnBuffer = Con::getReturnBuffer(256);
+   dSprintf(returnBuffer, 256, "%u %u", pt->low, pt->high);
+   return returnBuffer;
+}
+
+ConsoleSetType( TypeByteRange )
+{
+  if(argc == 1)
+  {
+    ByteRange* range = (ByteRange*) dptr;
+    U32 lo, hi;
+    S32 args = dSscanf(argv[0], "%u %u", &lo, &hi);
+    range->low = (args > 0) ? lo : 0;
+    range->high = (args > 1) ? hi : 255;
+  }
+  else
+    Con::printf("ByteRange must be set as \"low\" or \"low high\"");
+}
+
+ConsoleGetType( TypeByteRange2 )
+{
+   ByteRange* pt = (ByteRange *) dptr;
+   char* returnBuffer = Con::getReturnBuffer(256);
+   dSprintf(returnBuffer, 256, "%u %u", pt->low, pt->high);
+   return returnBuffer;
+}
+
+ConsoleSetType( TypeByteRange2 )
+{
+  if(argc == 1)
+  {
+    ByteRange* range = (ByteRange*) dptr;
+    U32 lo, hi;
+    S32 args = dSscanf(argv[0], "%u %u", &lo, &hi);
+    range->low = (args > 0) ? lo : 0;
+    range->high = (args > 1) ? hi : lo;
+  }
+  else
+    Con::printf("ByteRange must be set as \"low\" or \"low high\"");
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+static void HSVtoRGB(F32 h, F32 s, F32 v, F32& r, F32& g, F32& b)
+{
+  h = mFmod(h, 360.0f);
+
+  if (v == 0.0f)
+    r = g = b = 0.0f;
+  else if (s == 0.0f)
+    r = g = b = v;
+  else
+  {
+    F32 hf = h/60.0f;
+    S32 i = (S32) mFloor(hf);
+    F32 f = hf - i;
+
+    F32 pv = v*(1.0f - s);
+    F32 qv = v*(1.0f - s*f);
+    F32 tv = v*(1.0f - s*(1.0f - f));
+
+    switch (i)
+    {
+    case 0:
+      r = v;  g = tv; b = pv;
+      break;
+    case 1:
+      r = qv; g = v;  b = pv;
+      break;
+    case 2:
+      r = pv; g = v;  b = tv;
+      break;
+    case 3:
+      r = pv; g = qv; b = v;
+      break;
+    case 4:
+      r = tv; g = pv; b = v;
+      break;
+    case 5:
+      r = v;  g = pv; b = qv;
+      break;
+    default:
+      r = g = b = 0.0f;
+      break;
+    }
+  }
+}
+
+DefineEngineFunction(getColorFromHSV, const char*, (float hue, float sat, float val, float alpha), (0.0, 0.0, 1.0, 1.0),
+                     "Coverts an HSV formatted color into an RBG color.\n\n"
+                     "@param hue The hue of the color (0-360).\n"
+                     "@param sat The saturation of the color (0-1).\n"
+                     "@param val The value of the color (0-1).\n"
+                     "@param alpha The alpha of the color (0-1).\n"
+                     "@ingroup AFX")
+{
+  LinearColorF rgb;
+  HSVtoRGB(hue, sat, val, rgb.red, rgb.green, rgb.blue);
+  rgb.alpha = alpha;
+
+  char* returnBuffer = Con::getReturnBuffer(256);
+  dSprintf(returnBuffer, 256, "%g %g %g %g", rgb.red, rgb.green, rgb.blue, rgb.alpha);
+
+  return returnBuffer;
+}
+
+DefineEngineFunction(ColorScale, const char*, ( LinearColorF color, float scalar ),,
+                     "Returns color scaled by scalar (color*scalar).\n\n"
+                     "@param color The color to be scaled.\n"
+                     "@param scalar The amount to scale the color.\n"
+                     "@ingroup AFX")
+{
+  color *= scalar;
+
+  char* returnBuffer = Con::getReturnBuffer(256);
+  dSprintf(returnBuffer, 256, "%g %g %g %g", color.red, color.green, color.blue, color.alpha);
+
+  return returnBuffer;
+}
+
+DefineEngineFunction(getMinF, F32, (float a, float b),,
+                     "Returns the lesser of the two arguments.\n\n"
+                     "@ingroup AFX")
+{
+   return getMin(a, b);
+}
+
+DefineEngineFunction(getMaxF, F32, (float a, float b),,
+                     "Returns the greater of the two arguments.\n\n"
+                     "@ingroup AFX")
+{
+   return getMax(a, b);
+}
+
+ConsoleFunction(echoThru, const char*, 2, 0, "(string passthru, string text...)"
+                "Like echo(), but first argument is returned.\n"
+                "@ingroup AFX")
+{
+   U32 len = 0;
+   S32 i;
+   for(i = 2; i < argc; i++)
+      len += dStrlen(argv[i]);
+
+   char *ret = Con::getReturnBuffer(len + 1);
+   ret[0] = 0;
+   for(i = 2; i < argc; i++)
+      dStrcat(ret, argv[i]);
+
+   Con::printf("%s -- [%s]", ret, argv[1].getStringValue());
+   ret[0] = 0;
+
+   return argv[1];
+}
+
+ConsoleFunction(warnThru, const char*, 2, 0, "(string passthru, string text...)"
+                "Like warn(), but first argument is returned.\n"
+                "@ingroup AFX")
+{
+   U32 len = 0;
+   S32 i;
+   for(i = 2; i < argc; i++)
+      len += dStrlen(argv[i]);
+
+   char *ret = Con::getReturnBuffer(len + 1);
+   ret[0] = 0;
+   for(i = 2; i < argc; i++)
+      dStrcat(ret, argv[i]);
+
+   Con::warnf("%s -- [%s]", ret, argv[1].getStringValue());
+   ret[0] = 0;
+
+   return argv[1];
+}
+
+ConsoleFunction(errorThru, const char*, 2, 0, "(string passthru, string text...)"
+                "Like error(), but first argument is returned.\n"
+                "@ingroup AFX")
+{
+   U32 len = 0;
+   S32 i;
+   for(i = 2; i < argc; i++)
+      len += dStrlen(argv[i]);
+
+   char *ret = Con::getReturnBuffer(len + 1);
+   ret[0] = 0;
+   for(i = 2; i < argc; i++)
+      dStrcat(ret, argv[i]);
+
+   Con::errorf("%s -- [%s]", ret, argv[1].getStringValue());
+   ret[0] = 0;
+
+   return argv[1];
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//

+ 214 - 0
Engine/source/afx/arcaneFX.h

@@ -0,0 +1,214 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
+// Copyright (C) 2015 Faust Logic, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _ARCANE_FX_H_
+#define _ARCANE_FX_H_
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _PLATFORM_H_
+#include "platform/platform.h"
+#endif
+
+#define AFX_VERSION_STRING "2.0"
+#define AFX_VERSION         2.0
+
+// #define AFX_CUSTOMIZED_BRANCH
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//
+#if defined(AFX_CUSTOMIZED_BRANCH)
+
+#elif (TORQUE_GAME_ENGINE == 1100 || TORQUE_GAME_ENGINE >= 3000)
+
+#define AFX_CAP_SCOPE_TRACKING
+#define AFX_CAP_ROLLOVER_RAYCASTS
+//#define AFX_CAP_AFXMODEL_TYPE
+//#define BROKEN_POINT_IN_WATER
+#define BROKEN_DAMAGEFLASH_WHITEOUT_BLACKOUT
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//
+#else
+
+// This version of AFX source only supports T3D 1.1
+
+#endif
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _CONSOLETYPES_H_
+#include "console/consoleTypes.h"
+#endif
+
+#ifndef _ENGINEAPI_H_
+#include "console/engineAPI.h"
+#endif
+
+#ifndef _SIMBASE_H_
+#include "console/simBase.h"
+#endif
+
+#ifndef _BITSTREAM_H_
+#include "core/stream/bitStream.h"
+#endif
+
+#ifndef _GAMEBASE_H_
+#include "T3D/gameBase/gameBase.h"
+#endif
+
+#if defined(DGL_GRAPHICS_LAYER)
+#ifndef _DGL_H_
+#include "dgl/dgl.h"
+#endif
+#endif
+
+class afxChoreographer;
+class afxSelectronData;
+class GameConnection;
+class SceneObject;
+
+class arcaneFX
+{
+public:
+  enum {
+    TARGETING_OFF,
+    TARGETING_STANDARD,
+    TARGETING_FREE
+  };
+  enum {
+    TARGET_CHECK_POLL,
+    TARGET_CHECK_ON_MOUSE_MOVE
+  };
+
+private:
+  static Vector<afxChoreographer*> active_choreographers;
+  static Vector<afxChoreographer*> client_choreographers;
+  static Vector<afxSelectronData*> selectrons;
+  static Vector<SceneObject*>      scoped_objs;
+  static bool                      is_shutdown;
+
+public:
+  static StringTableEntry   NULLSTRING;
+  static U32                sTargetSelectionMask;
+  static U32                sFreeTargetSelectionMask;
+  static bool               sIsFreeTargeting;
+  static Point3F            sFreeTargetPos;
+  static bool               sFreeTargetPosValid;
+  static F32                sTargetSelectionRange;
+  static U32                sTargetSelectionTimeoutMS;
+  static bool               sClickToTargetSelf;
+  static U32                sMissileCollisionMask;
+  static StringTableEntry   sParameterFieldPrefix;
+  static F32                sTerrainZodiacZBias;
+  static F32                sInteriorZodiacZBias;
+  static F32                sPolysoupZodiacZBias;
+  static U32                master_choreographer_id;
+  static U16                master_scope_id;
+
+public:
+  static void init();
+  static void shutdown();
+  static void advanceTime(U32 delta);
+
+  static U32  registerChoreographer(afxChoreographer*);
+  static void unregisterChoreographer(afxChoreographer*);
+  static void registerClientChoreographer(afxChoreographer*);
+  static void unregisterClientChoreographer(afxChoreographer*);
+  static afxChoreographer* findClientChoreographer(U32 id);
+
+  static void registerSelectronData(afxSelectronData*);
+  static void unregisterSelectronData(afxSelectronData*);
+  static afxSelectronData* findSelectronData(U32 obj_type_mask, U8 code);
+
+  static U16            generateScopeId();
+  static void           registerScopedObject(SceneObject*);
+  static SceneObject*   findScopedObject(U16 scope_id);
+  static void           unregisterScopedObject(SceneObject*);
+
+  static void syncToNewConnection(GameConnection* conn);
+  static void endMissionNotify();
+  static S32  rolloverRayCast(Point3F start, Point3F end, U32 mask);
+  static bool freeTargetingRayCast(Point3F start, Point3F end, U32 mask);
+
+  static bool isShutdown() { return is_shutdown; }
+  static StringTableEntry convertLightingModelName(StringTableEntry lm_name);
+
+private:
+  static bool sUsePlayerCentricListener;
+public:
+  static bool usePlayerCentricListener() { return sUsePlayerCentricListener; }
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//
+
+class ByteRange
+{
+public:
+  U8     low;
+  U8     high;
+
+public:
+  /*C*/  ByteRange() { low = 0; high = 255; }
+  /*C*/  ByteRange(U8 l, U8 h=255) { low = l; high = h; }
+
+  void   set(U8 l, U8 h=255) { low = l; high = h; }
+  bool   outOfRange(U8 v) { return (v < low || v > high); }
+  bool   inRange(U8 v) { return !outOfRange(v); }
+  S32    getSpan() const { return high - low; }
+};
+
+DefineConsoleType(TypeByteRange, ByteRange)
+DefineConsoleType(TypeByteRange2, ByteRange)
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//
+
+inline void writeDatablockID(BitStream* s, SimObject* simobj, bool packed=false)
+{
+  if (s->writeFlag(simobj))
+    s->writeRangedU32(packed ? SimObjectId((uintptr_t)simobj) : simobj->getId(),
+                      DataBlockObjectIdFirst, DataBlockObjectIdLast);
+}
+
+inline S32 readDatablockID(BitStream* s)
+{
+  return (!s->readFlag()) ? 0 : ((S32)s->readRangedU32(DataBlockObjectIdFirst,
+          DataBlockObjectIdLast));
+}
+
+inline void registerForCleanup(SimObject* obj)
+{
+  SimGroup* cleanup_grp = dynamic_cast<SimGroup*>(Sim::findObject("MissionCleanup"));
+  if (cleanup_grp)
+    cleanup_grp->addObject(obj);
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//
+
+#define ST_NULLSTRING (arcaneFX::NULLSTRING)
+
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#endif // _ARCANE_FX_H_
+

+ 217 - 0
Engine/source/afx/ce/afxAnimClip.cpp

@@ -0,0 +1,217 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
+// Copyright (C) 2015 Faust Logic, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#include "afx/arcaneFX.h"
+
+#include "console/consoleTypes.h"
+#include "core/stream/bitStream.h"
+
+#include "afx/ce/afxAnimClip.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxAnimClipData
+
+IMPLEMENT_CO_DATABLOCK_V1(afxAnimClipData);
+
+ConsoleDocClass( afxAnimClipData,
+   "@brief A datablock that specifies an Animation Clip effect.\n\n"
+
+   "An Animation Clip forces a target ShapeBase-derived object, such as Player or AIPlayer, to perform a particular "
+   "animation sequence. Animation Clip does not supply any new animation data, but simply selects, by name, a "
+   "sequence that is already defined in the target. Animation Clip can also target afxModel effects within the same "
+   "choreographer."
+   "\n\n"
+
+   "The target of an Animation Clip is the constraint source object specified by the posConstraint field of the enclosing "
+   "effect wrapper. The target must be a ShapeBase-derived object, or an afxModel and it must contain an animation "
+   "sequence with the same name as the clipName field."
+   "\n\n"
+
+   "Animation Clip controls the rate of animation playback and can even play a sequence in reverse. When an Animation "
+   "Clip selects a blended animation sequence, it is mixed with the current animation instead of replacing it. Animation "
+   "Clips can be used to activate multiple, overlapping blend sequences."
+   "\n\n"
+
+   "Normally when an Animation Clip is applied to a user-controlled Player, any interactive user actions will override the "
+   "animation selected by the clip, but Animation Clips can be configured to temporarily block out some user actions for "
+   "the duration of the clip."
+   "\n\n"
+
+   "@ingroup afxEffects\n"
+   "@ingroup AFX\n"
+   "@ingroup Datablocks\n"
+);
+
+afxAnimClipData::afxAnimClipData()
+{
+  clip_name = ST_NULLSTRING;
+  rate = 1.0f;
+  pos_offset = 0.0;
+  trans = 0.12f;
+  flags = 0;
+
+  ignore_disabled = false;
+  ignore_enabled = false;
+  is_death_anim = false;
+  lock_anim = false;
+  ignore_first_person = false;
+  ignore_third_person = false;
+}
+
+afxAnimClipData::afxAnimClipData(const afxAnimClipData& other, bool temp_clone) : GameBaseData(other, temp_clone)
+{
+  clip_name = other.clip_name;
+  rate = other.rate;
+  pos_offset = other.pos_offset;
+  trans = other.trans;
+  flags = other.flags;
+
+  expand_flags();
+}
+
+void afxAnimClipData::onStaticModified(const char* slot, const char* newValue)
+{
+  Parent::onStaticModified(slot, newValue);
+  merge_flags();
+}
+
+#define myOffset(field) Offset(field, afxAnimClipData)
+
+void afxAnimClipData::initPersistFields()
+{
+  addField("clipName",          TYPEID< StringTableEntry >(),  myOffset(clip_name),
+    "The name of an animation sequence to be played by a ShapeBase-derived object to which this effect is "
+    "constrained. Also works on afxModel effects.\n"
+    "default: \"\"\n");
+  addField("rate",              TYPEID< F32 >(),               myOffset(rate),                  
+    "The desired playback speed for the sequence. A value of 1.0 indicates forward playback at a normal rate. Negative "
+    "values cause the sequence to play backwards.\n"
+    "default: 1.0\n");
+  addField("posOffset",         TYPEID< F32 >(),               myOffset(pos_offset),
+    "Sets a starting offset for the selected animation clip. It directly specifies an animation thread position in the 0.0 to "
+    "1.0 range as a fraction of the clip's duration.\n"
+    "default: 1.0\n");
+  addField("transitionTime",    TYPEID< F32 >(),               myOffset(trans),
+    "The duration in which the active animation overlaps and blends into the sequence selected by the animation clip.\n"
+    "default: 0.12\n");
+  addField("ignoreCorpse",      TYPEID< bool >(),              myOffset(ignore_disabled),
+    "Specifies if the animation clip should not be applied to corpses or anything else with a disabled damage state.\n"
+    "default: false\n");
+  addField("ignoreLiving",      TYPEID< bool >(),              myOffset(ignore_enabled),
+    "Specifies if the animation clip should not be applied to living objects or anything else with an enabled damage "
+    "state.\n"
+    "default: false\n");
+  addField("treatAsDeathAnim",  TYPEID< bool >(),              myOffset(is_death_anim),
+    "Indicates if the animation clip is a death animation. If the target object dies during the effect, this will prevent "
+    "the object from playing another standard death animation after this clip finishes.\n"
+    "default: false\n");
+  addField("lockAnimation",     TYPEID< bool >(),              myOffset(lock_anim),
+    "Indicates if user control of a Player should be temporarily blocked during the clip. (See afxAnimLockData.)\n"
+    "default: false\n");
+  addField("ignoreFirstPerson", TYPEID< bool >(),              myOffset(ignore_first_person),   
+    "If true, the clip will not be played on targets that are the control object and the camera is in first person mode.\n"
+    "default: false\n");
+  addField("ignoreThirdPerson", TYPEID< bool >(),              myOffset(ignore_third_person),   
+    "If true, the clip will not be played on targets that are the control object and the camera is in third person mode.\n"
+    "default: false\n");
+
+  // synonyms
+  addField("ignoreDisabled",    TYPEID< bool >(),              myOffset(ignore_disabled),
+    "A synonym for ignoreLiving.");
+  addField("ignoreEnabled",     TYPEID< bool >(),              myOffset(ignore_enabled),
+    "A synonym for ignoreCorpse.");
+
+  Parent::initPersistFields();
+}
+
+bool afxAnimClipData::onAdd()
+{
+  if (Parent::onAdd() == false)
+    return false;
+
+  return true;
+}
+
+void afxAnimClipData::packData(BitStream* stream)
+{
+	Parent::packData(stream);
+
+  merge_flags();
+
+  stream->writeString(clip_name);
+  stream->write(rate);
+  stream->write(pos_offset);
+  stream->write(trans);
+  stream->write(flags);
+}
+
+void afxAnimClipData::unpackData(BitStream* stream)
+{
+  Parent::unpackData(stream);
+
+  clip_name = stream->readSTString();
+  stream->read(&rate);
+  stream->read(&pos_offset);
+  stream->read(&trans);
+  stream->read(&flags);
+
+  expand_flags();
+}
+
+bool afxAnimClipData::writeField(StringTableEntry fieldname, const char* value)
+{
+   if (!Parent::writeField(fieldname, value))
+      return false;
+
+   // don't write the synonyms
+   if( fieldname == StringTable->insert("ignoreDisabled") )
+      return false;
+   if( fieldname == StringTable->insert("ignoreEnabled") )
+      return false;
+
+   return true;
+}
+
+void afxAnimClipData::expand_flags()
+{
+  ignore_disabled = ((flags & IGNORE_DISABLED) != 0);
+  ignore_enabled = ((flags & IGNORE_ENABLED) != 0);
+  lock_anim = ((flags & BLOCK_USER_CONTROL) != 0);
+  is_death_anim = ((flags & IS_DEATH_ANIM) != 0);
+  ignore_first_person = ((flags & IGNORE_FIRST_PERSON) != 0);
+  ignore_third_person = ((flags & IGNORE_THIRD_PERSON) != 0);
+}
+
+void afxAnimClipData::merge_flags()
+{
+  flags = (((ignore_disabled) ? IGNORE_DISABLED : 0) | 
+           ((ignore_enabled) ? IGNORE_ENABLED : 0) | 
+           ((lock_anim) ? BLOCK_USER_CONTROL : 0) | 
+           ((ignore_first_person) ? IGNORE_FIRST_PERSON : 0) |
+           ((ignore_third_person) ? IGNORE_THIRD_PERSON : 0) |
+           ((is_death_anim) ? IS_DEATH_ANIM : 0));
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//

+ 81 - 0
Engine/source/afx/ce/afxAnimClip.h

@@ -0,0 +1,81 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
+// Copyright (C) 2015 Faust Logic, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _AFX_ANIM_CLIP_H_
+#define _AFX_ANIM_CLIP_H_
+
+class afxAnimClipData : public GameBaseData
+{
+  typedef GameBaseData  Parent;
+
+  enum 
+  {
+    IGNORE_DISABLED     = BIT(0),
+    IGNORE_ENABLED      = BIT(1),
+    IS_DEATH_ANIM       = BIT(2),
+    BLOCK_USER_CONTROL  = BIT(3),
+    IGNORE_FIRST_PERSON = BIT(4),
+    IGNORE_THIRD_PERSON = BIT(5)
+  };
+
+public:
+  StringTableEntry      clip_name;
+  F32                   rate;
+  F32                   pos_offset;
+  F32                   trans;
+  U8                    flags;
+
+  bool                  ignore_disabled;
+  bool                  ignore_enabled;
+  bool                  is_death_anim;
+  bool                  lock_anim;
+  bool                  ignore_first_person;
+  bool                  ignore_third_person;
+
+  void                  expand_flags();
+  void                  merge_flags();
+
+public:
+  /*C*/                 afxAnimClipData();
+  /*C*/                 afxAnimClipData(const afxAnimClipData&, bool = false);
+
+  virtual bool          onAdd();
+  virtual void          packData(BitStream*);
+  virtual void          unpackData(BitStream*);
+  virtual bool          writeField(StringTableEntry fieldname, const char* value);
+
+  virtual void          onStaticModified(const char* slotName, const char* newValue = NULL);
+
+  virtual bool          allowSubstitutions() const { return true; }
+
+  static void           initPersistFields();
+
+  DECLARE_CONOBJECT(afxAnimClipData);
+  DECLARE_CATEGORY("AFX");
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#endif // _AFX_ANIM_CLIP_H_

+ 95 - 0
Engine/source/afx/ce/afxAnimLock.cpp

@@ -0,0 +1,95 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
+// Copyright (C) 2015 Faust Logic, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#include "afx/arcaneFX.h"
+
+#include "console/consoleTypes.h"
+#include "core/stream/bitStream.h"
+
+#include "afx/ce/afxAnimLock.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxAnimLockData
+
+IMPLEMENT_CO_DATABLOCK_V1(afxAnimLockData);
+
+ConsoleDocClass( afxAnimLockData,
+   "@brief A datablock that specifies an Animation Lock effect.\n\n"
+
+   "Animation Lock is used to temporarily lock out user-controlled Player actions, usually while an Animation Clip is "
+   "concurrently playing. Animation Clips can already do this, but must lock out user actions for the entire clip length. "
+   "Sometimes you only want to block user actions for a short section of a longer playing animation, such as the part where "
+   "the Player is thrown into the air from an impact. With Animation Lock, you can set a specific timespan for when user "
+   "actions are blocked, independent of any Animation Clip timing."
+   "\n\n"
+
+   "The target of an Animation Lock is the constraint source object specified by the posConstraint field of the enclosing effect "
+   "wrapper. The target must be a Player, a subclass of Player, or an afxModel."
+   "\n\n"
+
+   "The timing of the Animation Lock is determined by the timing fields of the enclosing effect wrapper."
+   "\n\n"
+
+   "Locking behavior timing is set by fields of the enclosing effect wrapper, so afxAnimLockData does not require any fields. "
+   "However, TorqueScript syntax disallows the declaration of an empty datablock. Therefore, it is recommended that you set "
+   "a dynamic field named 'priority' to zero in the body of the datablock as a workaround to this limitation."
+   "\n\n"
+
+   "@ingroup afxEffects\n"
+   "@ingroup AFX\n"
+   "@ingroup Datablocks\n"
+);
+
+afxAnimLockData::afxAnimLockData()
+{
+}
+
+#define myOffset(field) Offset(field, afxAnimLockData)
+
+void afxAnimLockData::initPersistFields()
+{
+  Parent::initPersistFields();
+}
+
+bool afxAnimLockData::onAdd()
+{
+  if (Parent::onAdd() == false)
+    return false;
+
+  return true;
+}
+
+void afxAnimLockData::packData(BitStream* stream)
+{
+	Parent::packData(stream);
+}
+
+void afxAnimLockData::unpackData(BitStream* stream)
+{
+  Parent::unpackData(stream);
+}
+
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//

+ 48 - 0
Engine/source/afx/ce/afxAnimLock.h

@@ -0,0 +1,48 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
+// Copyright (C) 2015 Faust Logic, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _AFX_ANIM_LOCK_H_
+#define _AFX_ANIM_LOCK_H_
+
+class afxAnimLockData : public GameBaseData
+{
+  typedef GameBaseData  Parent;
+
+public:
+  /*C*/                 afxAnimLockData();
+
+  virtual bool          onAdd();
+  virtual void          packData(BitStream*);
+  virtual void          unpackData(BitStream*);
+
+  static void           initPersistFields();
+
+  DECLARE_CONOBJECT(afxAnimLockData);
+  DECLARE_CATEGORY("AFX");
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#endif // _AFX_ANIM_LOCK_H_

+ 121 - 0
Engine/source/afx/ce/afxAreaDamage.cpp

@@ -0,0 +1,121 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
+// Copyright (C) 2015 Faust Logic, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#include "afx/arcaneFX.h"
+
+#include "afx/ce/afxAreaDamage.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxAreaDamageData
+
+IMPLEMENT_CO_DATABLOCK_V1(afxAreaDamageData);
+
+ConsoleDocClass( afxAreaDamageData,
+   "@brief A datablock that specifies an Area Damage effect.\n\n"
+
+   "An Area Damage effect is useful for assigning area damage with unusual timing that must be synchronized with other "
+   "effects. Negative damage amounts can be used for healing effects."
+   "\n\n"
+
+   "The primary difference between afxAreaDamageData and afxDamageData, which is also capable of inflicting area damage, "
+   "is that afxAreaDamageData effects calculate the area damage in C++ code rather than calling out to the script function "
+   "radiusDamage(). In cases where area damage needs to be inflicted repeatedly or in areas crowded with many targets, "
+   "afxAreaDamageData is likely to get better performance."
+   "\n\n"
+
+   "@ingroup afxEffects\n"
+   "@ingroup AFX\n"
+   "@ingroup Datablocks\n"
+);
+
+afxAreaDamageData::afxAreaDamageData()
+{
+  flavor = ST_NULLSTRING;
+  amount = 0;
+  radius = 0;
+  impulse = 0;
+  notify_damage_src = false;
+  exclude_cons_obj = false;
+}
+
+afxAreaDamageData::afxAreaDamageData(const afxAreaDamageData& other, bool temp_clone) : GameBaseData(other, temp_clone)
+{
+  flavor = other.flavor;
+  amount = other.amount;
+  radius = other.radius;
+  impulse = other.impulse;
+  notify_damage_src = other.notify_damage_src;
+  exclude_cons_obj = other.exclude_cons_obj;
+}
+
+#define myOffset(field) Offset(field, afxAreaDamageData)
+
+void afxAreaDamageData::initPersistFields()
+{
+  addField("flavor",                    TypeString,     myOffset(flavor),
+    "An arbitrary string which is passed as an argument to a spell's onDamage() script "
+    "method. It is used to classify a type of damage such as 'melee', 'magical', or "
+    "'fire'.");
+  addField("damage",                    TypeF32,        myOffset(amount),
+    "An amount of area damage to inflict on a target. Objects within half the radius "
+    "receive full damage which then diminishes out to the full distance of the specified "
+    "radius.");
+  addField("radius",                    TypeF32,        myOffset(radius),
+    "Radius centered at the effect position in which damage will be applied.");
+  addField("impulse",                   TypeF32,        myOffset(impulse),
+    "Specifies an amount of force to apply to damaged objects. Objects within half the "
+    "radius receive full impulse which then diminishes out to the full distance of the "
+    "specified radius.");
+  addField("notifyDamageSource",        TypeBool,       myOffset(notify_damage_src),
+    "When true, the onInflictedAreaDamage() method of the damaged object will be called "
+    "to notify it of the damage. This is useful for starting some effects or action that "
+    "responds to the damage.");
+  addField("excludeConstraintObject",   TypeBool,       myOffset(exclude_cons_obj),
+    "When true, the object specified as the effect's primary position constraint will not "
+    "receive any damage.");
+
+  Parent::initPersistFields();
+}
+
+bool afxAreaDamageData::onAdd()
+{
+  if (Parent::onAdd() == false)
+    return false;
+
+  return true;
+}
+
+void afxAreaDamageData::packData(BitStream* stream)
+{
+	Parent::packData(stream);
+}
+
+void afxAreaDamageData::unpackData(BitStream* stream)
+{
+  Parent::unpackData(stream);
+}
+
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//

+ 62 - 0
Engine/source/afx/ce/afxAreaDamage.h

@@ -0,0 +1,62 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
+// Copyright (C) 2015 Faust Logic, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _AFX_AREA_DAMAGE_H_
+#define _AFX_AREA_DAMAGE_H_
+
+#include "afx/afxEffectDefs.h"
+
+class afxAreaDamageData : public GameBaseData, public afxEffectDefs
+{
+  typedef GameBaseData  Parent;
+
+public:
+  StringTableEntry  flavor;
+
+  F32           amount;
+  F32           radius;
+  F32           impulse;
+  bool          notify_damage_src;
+  bool          exclude_cons_obj;
+
+public:
+  /*C*/         afxAreaDamageData();
+  /*C*/         afxAreaDamageData(const afxAreaDamageData&, bool = false);
+
+  virtual bool  onAdd();
+  virtual void  packData(BitStream*);
+  virtual void  unpackData(BitStream*);
+
+  virtual bool  allowSubstitutions() const { return true; }
+
+  static void   initPersistFields();
+
+  DECLARE_CONOBJECT(afxAreaDamageData);
+  DECLARE_CATEGORY("AFX");
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#endif // _AFX_AREA_DAMAGE_H_

+ 246 - 0
Engine/source/afx/ce/afxAudioBank.cpp

@@ -0,0 +1,246 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
+// Copyright (C) 2015 Faust Logic, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#include "afx/arcaneFX.h"
+
+#include "console/consoleTypes.h"
+#include "sim/netConnection.h"
+#include "sfx/sfxDescription.h"
+
+#include "afx/ce/afxAudioBank.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+IMPLEMENT_CO_DATABLOCK_V1(afxAudioBank);
+
+ConsoleDocClass( afxAudioBank,
+   "@brief A datablock that specifies an Audio Bank effect.\n\n"
+
+   "afxAudioBank is very similar to the stock Torque SFXProfile datablock but it allows specification of up to 32 different sound "
+   "files. The sound that actually plays is determined by the playIndex field."
+   "\n\n"
+
+   "afxAudioBank is most useful when used in combination with field substitutions, whereby a substitution statement "
+   "assigned to playIndex selects a different sound (perhaps randomly) each time the effect is used."
+   "\n\n"
+
+   "@ingroup afxEffects\n"
+   "@ingroup AFX\n"
+   "@ingroup Datablocks\n"
+);
+
+afxAudioBank::afxAudioBank()
+{
+  mPath = ST_NULLSTRING;
+  mDescriptionObjectID = 0;
+  mDescriptionObject = NULL;
+  mPreload = false;
+  play_index = -1;
+
+  for (S32 i = 0; i < 32; i++)
+    mFilenames[i] = ST_NULLSTRING;
+}
+
+afxAudioBank::afxAudioBank(const afxAudioBank& other, bool temp_clone) : SimDataBlock(other, temp_clone)
+{
+  mPath = other.mPath;
+  mDescriptionObject = other.mDescriptionObject;
+  mDescriptionObjectID = other.mDescriptionObjectID; // -- for pack/unpack of mDescriptionObject ptr
+  mPreload = other.mPreload;
+  play_index = other.play_index;
+
+  for (S32 i = 0; i < 32; i++)
+    mFilenames[i] = other.mFilenames[i];
+}
+
+afxAudioBank::~afxAudioBank()
+{
+  if (!isTempClone())
+    return;
+
+  if (mDescriptionObject && mDescriptionObject->isTempClone())
+  {
+    delete mDescriptionObject;
+    mDescriptionObject = 0;
+  }
+}
+
+afxAudioBank* afxAudioBank::cloneAndPerformSubstitutions(const SimObject* owner, S32 index)
+{
+  if (!owner)
+    return this;
+
+  afxAudioBank* sub_profile_db = this;
+
+  SFXDescription* desc_db;
+  if (mDescriptionObject && mDescriptionObject->getSubstitutionCount() > 0)
+  {
+    SFXDescription* orig_db = mDescriptionObject;
+    desc_db = new SFXDescription(*orig_db, true);
+    orig_db->performSubstitutions(desc_db, owner, index);
+  }
+  else
+    desc_db = 0;
+
+  if (this->getSubstitutionCount() > 0 || desc_db)
+  {
+    sub_profile_db = new afxAudioBank(*this, true);
+    performSubstitutions(sub_profile_db, owner, index);
+    if (desc_db)
+      sub_profile_db->mDescriptionObject = desc_db;
+  }
+
+  return sub_profile_db;
+}
+
+void afxAudioBank::onPerformSubstitutions() 
+{ 
+}
+
+void afxAudioBank::initPersistFields()
+{
+  addField("path",        TypeFilename,             Offset(mPath, afxAudioBank),
+    "A filesystem path to the folder containing the sound files specified by the "
+    "filenames[] field. All sound files used in a single AudioBank must be located in "
+    "the same folder.");
+  addField("filenames",   TypeString,               Offset(mFilenames, afxAudioBank), 32,
+    "Up to 32 names of sound files found in the path folder. The sound that is actually "
+    "played by an Audio Bank effect is determined by the playIndex field.");
+  addField("description", TYPEID<SFXDescription>(), Offset(mDescriptionObject, afxAudioBank),
+    "SFXDescription datablock to use with this set of sounds.");
+  addField("preload",     TypeBool,                 Offset(mPreload, afxAudioBank),
+    "If set to true, file is pre-loaded, otherwise it is loaded on-demand.");
+  addField("playIndex",   TypeS32,                  Offset(play_index, afxAudioBank),
+    "An array index that selects a sound to play from the filenames[] field. Values "
+    "outside of the range of assigned filename[] entries will not play any sound.");
+
+  Parent::initPersistFields();
+}
+
+bool afxAudioBank::preload(bool server, String &errorStr)
+{
+  if(!Parent::preload(server, errorStr))
+    return false;
+
+  return true;
+}
+
+bool afxAudioBank::onAdd()
+{
+  if (!Parent::onAdd())
+    return false;
+
+  if (!mDescriptionObject && mDescriptionObjectID)
+    Sim::findObject(mDescriptionObjectID , mDescriptionObject);
+
+  // if this is client side, make sure that description is as well
+  if(mDescriptionObject)
+  {  // client side dataBlock id's are not in the dataBlock id range
+    if (getId() >= DataBlockObjectIdFirst && getId() <= DataBlockObjectIdLast)
+    {
+      SimObjectId pid = mDescriptionObject->getId();
+      if (pid < DataBlockObjectIdFirst || pid > DataBlockObjectIdLast)
+      {
+        Con::errorf(ConsoleLogEntry::General,"afxAudioBank: data dataBlock not networkable (use datablock to create).");
+        return false;
+      }
+    }
+  }
+
+  return(true);
+}
+
+void afxAudioBank::packData(BitStream* stream)
+{
+  Parent::packData(stream);
+
+  if (stream->writeFlag(mDescriptionObject))
+    stream->writeRangedU32(mDescriptionObject->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast);
+
+  /*
+  char buffer[256];
+  if(!mFilename)
+    buffer[0] = 0;
+  else
+    dStrcpy(buffer, mFilename);
+  stream->writeString(buffer);
+  */
+
+  stream->writeString(mPath);
+
+  for (S32 i = 0; i < 32; i++)
+  {
+    stream->writeString(mFilenames[i]);
+    if (mFilenames[i] == ST_NULLSTRING)
+      break;
+  }
+
+  stream->writeFlag(mPreload);
+
+  if (stream->writeFlag(play_index >= 0 && play_index < 32))
+    stream->writeInt(play_index, 5);
+}
+
+void afxAudioBank::unpackData(BitStream* stream)
+{
+  Parent::unpackData(stream);
+
+  if (stream->readFlag()) // AudioDescription
+  {
+    SimObjectId id = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast);
+    mDescriptionObjectID = id;
+    Sim::findObject(id, mDescriptionObject);
+  }
+
+  // Filename
+  /*
+  char buffer[256];
+  stream->readString(buffer);
+  mFilename = StringTable->insert(buffer);
+  */
+
+  char buffer[256]; 
+
+  stream->readString(buffer);
+  mPath = StringTable->insert(buffer);
+
+  for (S32 i = 0; i < 32; i++)
+  {
+    stream->readString(buffer);
+    mFilenames[i] = StringTable->insert(buffer);
+    if (mFilenames[i] == ST_NULLSTRING)
+      break;
+  }
+
+  mPreload = stream->readFlag(); // Preload
+
+  if (stream->readFlag())
+    play_index = stream->readInt(5);
+  else
+    play_index = -1;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+

+ 67 - 0
Engine/source/afx/ce/afxAudioBank.h

@@ -0,0 +1,67 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
+// Copyright (C) 2015 Faust Logic, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _AFX_AUDIO_BANK_H_
+#define _AFX_AUDIO_BANK_H_
+
+class SFXDescription;
+
+class afxAudioBank: public SimDataBlock
+{
+private:
+   typedef SimDataBlock Parent;
+
+public:
+   SFXDescription*    mDescriptionObject;
+   U32                mDescriptionObjectID;
+   StringTableEntry   mPath;
+   StringTableEntry   mFilenames[32];
+   bool               mPreload;
+   S32                play_index;
+
+public:
+   /*C*/              afxAudioBank();
+   /*C*/              afxAudioBank(const afxAudioBank&, bool = false);
+   /*D*/              ~afxAudioBank();
+
+   static void        initPersistFields();
+
+   virtual bool       onAdd();
+   virtual void       packData(BitStream* stream);
+   virtual void       unpackData(BitStream* stream);
+
+   bool               preload(bool server, String &errorStr);
+
+   afxAudioBank*      cloneAndPerformSubstitutions(const SimObject*, S32 index=0);
+   virtual void       onPerformSubstitutions();
+   virtual bool       allowSubstitutions() const { return true; }
+
+   DECLARE_CONOBJECT(afxAudioBank);
+   DECLARE_CATEGORY("AFX");
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#endif  // _AFX_AUDIO_BANK_H_

+ 294 - 0
Engine/source/afx/ce/afxBillboard.cpp

@@ -0,0 +1,294 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
+// Copyright (C) 2015 Faust Logic, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#include "afx/arcaneFX.h"
+
+#include "gfx/gfxAPI.h"
+#include "math/mathIO.h"
+
+#include "afx/afxChoreographer.h"
+#include "afx/ce/afxBillboard.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxBillboardData
+
+IMPLEMENT_CO_DATABLOCK_V1(afxBillboardData);
+
+ConsoleDocClass( afxBillboardData,
+   "@brief A datablock that specifies a Billboard effect.\n\n"
+
+   "A Billboard effect is a textured quadrangle which is always aligned to face towards the camera. It is much like a single "
+   "static particle and is rendered in a similar fashion."
+   "\n\n"
+
+   "@ingroup afxEffects\n"
+   "@ingroup AFX\n"
+   "@ingroup Datablocks\n"
+);
+
+afxBillboardData::afxBillboardData()
+{
+  color.set(1.0f, 1.0f, 1.0f, 1.0f);
+  txr_name = ST_NULLSTRING;
+  dimensions.set(1.0f, 1.0f);
+  texCoords[0].set(0.0f, 0.0f);
+  texCoords[1].set(0.0f, 1.0f);
+  texCoords[2].set(1.0f, 1.0f);
+  texCoords[3].set(1.0f, 0.0f);
+  blendStyle = BlendUndefined;
+  srcBlendFactor = BLEND_UNDEFINED;
+  dstBlendFactor = BLEND_UNDEFINED;
+  texFunc = TexFuncModulate;
+}
+
+afxBillboardData::afxBillboardData(const afxBillboardData& other, bool temp_clone)
+  : GameBaseData(other, temp_clone)
+{
+  color = other.color;
+  txr_name = other.txr_name;
+  txr = other.txr;
+  dimensions = other.dimensions;
+  texCoords[0] = other.texCoords[0];
+  texCoords[1] = other.texCoords[1];
+  texCoords[2] = other.texCoords[2];
+  texCoords[3] = other.texCoords[3];
+  blendStyle = other.blendStyle;
+  srcBlendFactor = other.srcBlendFactor;
+  dstBlendFactor = other.dstBlendFactor;
+  texFunc = other.texFunc;
+}
+
+#define myOffset(field) Offset(field, afxBillboardData)
+
+extern EnumTable srcBlendFactorTable;
+extern EnumTable dstBlendFactorTable;
+
+ImplementEnumType( afxBillboard_BlendStyle, "Possible blending types.\n" "@ingroup afxBillboard\n\n" )
+    { afxBillboardData::BlendNormal,         "NORMAL",         "..." },
+    { afxBillboardData::BlendAdditive,       "ADDITIVE",       "..." },
+    { afxBillboardData::BlendSubtractive,    "SUBTRACTIVE",    "..." },
+    { afxBillboardData::BlendPremultAlpha,   "PREMULTALPHA",   "..." },
+EndImplementEnumType;
+
+ImplementEnumType( afxBillboard_TexFuncType, "Possible texture function types.\n" "@ingroup afxBillboard\n\n" )
+    { afxBillboardData::TexFuncReplace,   "replace",     "..." },
+    { afxBillboardData::TexFuncModulate,  "modulate",    "..." },
+    { afxBillboardData::TexFuncAdd,       "add",         "..." },
+EndImplementEnumType;
+
+void afxBillboardData::initPersistFields()
+{
+  addField("color",           TypeColorF,     myOffset(color),
+    "The color assigned to the quadrangle geometry. The way it combines with the given "
+    "texture varies according to the setting of the textureFunction field.");
+  addField("texture",         TypeFilename,   myOffset(txr_name),
+    "An image to use as the billboard's texture.");
+  addField("dimensions",      TypePoint2F,    myOffset(dimensions),
+    "A value-pair that specifies the horizontal and vertical dimensions of the billboard "
+    "in scene units.");
+  addField("textureCoords",   TypePoint2F,    myOffset(texCoords),  4,
+    "An array of four value-pairs that specify the UV texture coordinates for the four "
+    "corners of the billboard's quadrangle.");
+
+  addField("blendStyle",      TYPEID<afxBillboardData::BlendStyle>(),   myOffset(blendStyle),
+    "Selects a common blend factor preset. When set to 'user', srcBlendFactor and "
+    "dstBlendFactor can be used to set additional blend factor combinations.\n"
+    "Possible values: normal, additive, subtractive, premultalpha, or user.");
+  addField("srcBlendFactor",  TYPEID<GFXBlend>(),   myOffset(srcBlendFactor),
+    "Specifies source blend factor when blendStyle is set to 'user'.\n"
+    "Possible values: GFXBlendZero, GFXBlendOne, GFXBlendDestColor, GFXBlendInvDestColor, GFXBlendSrcAlpha, GFXBlendInvSrcAlpha, GFXBlendDestAlpha, GFXBlendInvDestAlpha, or GFXBlendSrcAlphaSat");
+  addField("dstBlendFactor",  TYPEID<GFXBlend>(),   myOffset(dstBlendFactor),
+    "Specifies destination blend factor when blendStyle is set to 'user'.\n"
+    "Possible values: GFXBlendZero, GFXBlendOne, GFXBlendSrcColor, GFXBlendInvSrcColor, GFXBlendSrcAlpha, GFXBlendInvSrcAlpha, GFXBlendDestAlpha, or GFXBlendInvDestAlpha");
+
+  addField("textureFunction", TYPEID<afxBillboardData::TexFuncType>(),  myOffset(texFunc),
+    "Selects a texture function that determines how the texture pixels are combined "
+    "with the shaded color of the billboard's quadrangle geometry.\n"
+    "Possible values: replace, modulate, or add.");
+
+  Parent::initPersistFields();
+}
+
+void afxBillboardData::packData(BitStream* stream)
+{
+	Parent::packData(stream);
+
+  stream->write(color);
+  stream->writeString(txr_name);
+  mathWrite(*stream, dimensions);
+  mathWrite(*stream, texCoords[0]);
+  mathWrite(*stream, texCoords[1]);
+  mathWrite(*stream, texCoords[2]);
+  mathWrite(*stream, texCoords[3]);
+
+  stream->writeInt(srcBlendFactor, 4);
+  stream->writeInt(dstBlendFactor, 4);
+  stream->writeInt(texFunc, 4);
+}
+
+void afxBillboardData::unpackData(BitStream* stream)
+{
+  Parent::unpackData(stream);
+
+  stream->read(&color);
+  txr_name = stream->readSTString();
+  txr = GFXTexHandle();
+  mathRead(*stream, &dimensions);
+  mathRead(*stream, &texCoords[0]);
+  mathRead(*stream, &texCoords[1]);
+  mathRead(*stream, &texCoords[2]);
+  mathRead(*stream, &texCoords[3]);
+
+  srcBlendFactor = (GFXBlend) stream->readInt(4);
+  dstBlendFactor = (GFXBlend) stream->readInt(4);
+  texFunc = stream->readInt(4);
+}
+
+bool afxBillboardData::preload(bool server, String &errorStr)
+{
+  if (!Parent::preload(server, errorStr))
+    return false;
+
+  if (!server)
+  {
+    if (txr_name && txr_name[0] != '\0')
+    {
+      txr.set(txr_name, &GFXStaticTextureSRGBProfile, "Billboard Texture");
+    }
+  }
+
+   // if blend-style is set to User, check for defined blend-factors
+   if (blendStyle == BlendUser && (srcBlendFactor == BLEND_UNDEFINED || dstBlendFactor == BLEND_UNDEFINED))
+   {
+      blendStyle = BlendUndefined;
+      Con::warnf(ConsoleLogEntry::General, "afxBillboardData(%s) incomplete blend factor specification.", getName());
+   }
+
+   // silently switch Undefined blend-style to User if blend factors are both defined
+   if (blendStyle == BlendUndefined && srcBlendFactor != BLEND_UNDEFINED && dstBlendFactor != BLEND_UNDEFINED)
+   {
+      blendStyle = BlendUser;
+   }
+
+   // set pre-defined blend-factors 
+   switch (blendStyle)
+   {
+   case BlendNormal:
+      srcBlendFactor = GFXBlendSrcAlpha;
+      dstBlendFactor = GFXBlendInvSrcAlpha;
+      break;
+   case BlendSubtractive:
+      srcBlendFactor = GFXBlendZero;
+      dstBlendFactor = GFXBlendInvSrcColor;
+      break;
+   case BlendPremultAlpha:
+      srcBlendFactor = GFXBlendOne;
+      dstBlendFactor = GFXBlendInvSrcAlpha;
+      break;
+   case BlendUser:
+      break;
+   case BlendAdditive:
+      srcBlendFactor = GFXBlendSrcAlpha;
+      dstBlendFactor = GFXBlendOne;
+      break;
+   case BlendUndefined:
+   default:
+      blendStyle = BlendNormal;
+      srcBlendFactor = GFXBlendSrcAlpha;
+      dstBlendFactor = GFXBlendInvSrcAlpha;
+      break;
+   }
+
+  return true;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxBillboard
+
+IMPLEMENT_CO_NETOBJECT_V1(afxBillboard);
+
+ConsoleDocClass( afxBillboard,
+   "@brief A Billboard effect as defined by an afxBillboardData datablock.\n\n"
+
+   "A Billboard effect is a textured quadrangle which is always aligned to "
+   "face towards the camera. It is much like a single static particle and is rendered "
+   "in a similar fashion.\n"
+
+   "@ingroup afxEffects\n"
+   "@ingroup AFX\n"
+);
+
+afxBillboard::afxBillboard()
+{
+  mNetFlags.clear();
+  mNetFlags.set(IsGhost);
+
+  mDataBlock = 0;
+  fade_amt = 1.0f;
+  is_visible = true;
+  sort_priority = 0;
+  live_color.set(1.0f, 1.0f, 1.0f, 1.0f);
+}
+
+afxBillboard::~afxBillboard()
+{
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//
+
+bool afxBillboard::onNewDataBlock(GameBaseData* dptr, bool reload)
+{
+  mDataBlock = dynamic_cast<afxBillboardData*>(dptr);
+  if (!mDataBlock || !Parent::onNewDataBlock(dptr, reload))
+    return false;
+
+  live_color = mDataBlock->color;
+
+  return true;
+}
+
+bool afxBillboard::onAdd()
+{
+  if(!Parent::onAdd())
+    return false;
+
+  F32 width = mDataBlock->dimensions.x * 0.5f;
+  F32 height = mDataBlock->dimensions.y * 0.5f;
+  mObjBox = Box3F(Point3F(-width, -0.01f, -height), Point3F(width, 0.01f, height));
+  
+  addToScene();
+  
+  return true;
+}
+
+void afxBillboard::onRemove()
+{
+  removeFromScene();
+  
+  Parent::onRemove();
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//

+ 131 - 0
Engine/source/afx/ce/afxBillboard.h

@@ -0,0 +1,131 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
+// Copyright (C) 2015 Faust Logic, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _AFX_BILLBOARD_H_
+#define _AFX_BILLBOARD_H_
+
+#include "afx/afxEffectDefs.h"
+
+#define BLEND_UNDEFINED GFXBlend_COUNT
+
+class afxBillboardData : public GameBaseData, public afxEffectDefs
+{
+  typedef GameBaseData  Parent;
+
+public:
+   // This enum specifies common blend settings with predefined values
+   // for src/dst blend factors. 
+   enum BlendStyle {
+     BlendUndefined,
+     BlendNormal,
+     BlendAdditive,
+     BlendSubtractive,
+     BlendPremultAlpha,
+     BlendUser,
+   };
+
+   enum TexFuncType {
+     TexFuncReplace,
+     TexFuncModulate,
+     TexFuncAdd,
+   };  
+
+public:
+  StringTableEntry  txr_name;
+  GFXTexHandle      txr;
+
+  LinearColorF            color;
+  Point2F           texCoords[4];
+  Point2F           dimensions;
+  S32               blendStyle; 
+  GFXBlend          srcBlendFactor;
+  GFXBlend          dstBlendFactor;
+  S32               texFunc;
+
+public:
+  /*C*/             afxBillboardData();
+  /*C*/             afxBillboardData(const afxBillboardData&, bool = false);
+
+  virtual void      packData(BitStream*);
+  virtual void      unpackData(BitStream*);
+
+  bool              preload(bool server, String &errorStr);
+
+  virtual bool      allowSubstitutions() const { return true; }
+
+  static void       initPersistFields();
+
+  DECLARE_CONOBJECT(afxBillboardData);
+  DECLARE_CATEGORY("AFX");
+};
+
+typedef afxBillboardData::BlendStyle afxBillboard_BlendStyle;
+DefineEnumType( afxBillboard_BlendStyle );
+
+typedef afxBillboardData::TexFuncType afxBillboard_TexFuncType;
+DefineEnumType( afxBillboard_TexFuncType );
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxBillboard
+
+class afxBillboard : public GameBase, public afxEffectDefs
+{
+  typedef GameBase Parent;
+  friend class afxEA_Billboard;
+
+private:
+  afxBillboardData* mDataBlock;
+
+  F32               fade_amt;
+  bool              is_visible;
+  S8                sort_priority;
+  LinearColorF            live_color;
+
+  GFXStateBlockRef  normal_sb;
+  GFXStateBlockRef  reflected_sb;
+
+public:
+  /*C*/             afxBillboard();
+  /*D*/             ~afxBillboard();
+
+  virtual bool      onNewDataBlock(GameBaseData* dptr, bool reload);
+  virtual bool      onAdd();
+  virtual void      onRemove();
+
+  void              setFadeAmount(F32 amt) { fade_amt = amt; }
+  void              setSortPriority(S8 priority) { sort_priority = priority; }
+  void              setVisibility(bool flag) { is_visible = flag; }
+
+  virtual void      prepRenderImage(SceneRenderState*);
+
+  void              _renderBillboard(ObjectRenderInst*, SceneRenderState*, BaseMatInstance*);
+
+  DECLARE_CONOBJECT(afxBillboard);
+  DECLARE_CATEGORY("AFX");
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#endif // _AFX_BILLBOARD_H_

+ 145 - 0
Engine/source/afx/ce/afxBillboard_T3D.cpp

@@ -0,0 +1,145 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
+// Copyright (C) 2015 Faust Logic, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#include "afx/arcaneFX.h"
+
+#include "gfx/gfxTransformSaver.h"
+#include "gfx/primBuilder.h"
+
+#include "afx/afxChoreographer.h"
+#include "afx/ce/afxBillboard.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+void afxBillboard::prepRenderImage(SceneRenderState* state)
+{
+  if (!is_visible)
+    return;
+
+  ObjectRenderInst *ri = state->getRenderPass()->allocInst<ObjectRenderInst>();
+  ri->renderDelegate.bind(this, &afxBillboard::_renderBillboard);
+  ri->type = RenderPassManager::RIT_ObjectTranslucent;
+  ri->translucentSort = true;
+  ri->defaultKey = (U32)(dsize_t)mDataBlock;
+  ri->sortDistSq = getWorldBox().getSqDistanceToPoint( state->getCameraPosition() );      
+  state->getRenderPass()->addInst(ri);
+}
+
+void afxBillboard::_renderBillboard(ObjectRenderInst *ri, SceneRenderState* state, BaseMatInstance* overrideMat)
+{
+  if (overrideMat)
+    return;
+
+  // predraw
+  if (normal_sb.isNull())
+  {
+    GFXStateBlockDesc desc;
+
+    // Culling -- it's a billboard, so no backfaces
+    desc.setCullMode(GFXCullCW);
+
+    // Blending
+    desc.setBlend(true, mDataBlock->srcBlendFactor, mDataBlock->dstBlendFactor);
+    desc.alphaTestEnable = (desc.blendSrc == GFXBlendSrcAlpha && 
+                            (desc.blendDest == GFXBlendInvSrcAlpha || desc.blendDest == GFXBlendOne));
+    desc.alphaTestRef = 1;
+    desc.alphaTestFunc = GFXCmpGreaterEqual;
+    
+    desc.setZReadWrite(true);
+    desc.zFunc = GFXCmpLessEqual;
+    desc.zWriteEnable = false;
+
+    desc.samplersDefined = true;
+    switch (mDataBlock->texFunc)
+    {
+    case afxBillboardData::TexFuncReplace:
+      desc.samplers[0].textureColorOp = GFXTOPDisable;
+      break;
+    case afxBillboardData::TexFuncModulate:
+      desc.samplers[0].textureColorOp = GFXTOPModulate;
+      break;
+    case afxBillboardData::TexFuncAdd:
+      desc.samplers[0].textureColorOp = GFXTOPAdd;
+      break;
+    }
+
+    desc.samplers[1].textureColorOp = GFXTOPDisable;
+
+    normal_sb = GFX->createStateBlock(desc);
+
+    desc.setCullMode(GFXCullCCW);
+    reflected_sb = GFX->createStateBlock(desc);
+  }
+
+  if (state->isReflectPass())
+    GFX->setStateBlock(reflected_sb);
+  else
+    GFX->setStateBlock(normal_sb);
+
+  GFXTransformSaver saver;
+  GFX->multWorld(getRenderTransform());
+
+  GFX->setTexture(0, mDataBlock->txr);
+
+	MatrixF worldmod = GFX->getWorldMatrix();
+	MatrixF viewmod = GFX->getViewMatrix();
+
+  Point4F Position;
+  MatrixF ModelView;
+	ModelView.mul(viewmod, worldmod);
+	ModelView.getColumn(3, &Position);
+	ModelView.identity();
+	ModelView.setColumn(3, Position);
+
+	GFX->setWorldMatrix(ModelView);
+	MatrixF ident;
+	ident.identity();
+	GFX->setViewMatrix(ident);
+
+  F32 width = mDataBlock->dimensions.x * 0.5f * mObjScale.x;
+  F32 height = mDataBlock->dimensions.y * 0.5f * mObjScale.z;
+
+  Point3F points[4];
+  points[0].set( width, 0.0f, -height);
+  points[1].set(-width, 0.0f, -height);
+  points[2].set(-width, 0.0f,  height);
+  points[3].set( width, 0.0f,  height);
+
+  PrimBuild::begin(GFXTriangleStrip, 4);
+  {
+	  PrimBuild::color4f(live_color.red, live_color.green, live_color.blue, live_color.alpha*fade_amt);
+    PrimBuild::texCoord2f(mDataBlock->texCoords[1].x, mDataBlock->texCoords[1].y);
+    PrimBuild::vertex3fv(points[1]);
+    PrimBuild::texCoord2f(mDataBlock->texCoords[2].x, mDataBlock->texCoords[2].y);
+    PrimBuild::vertex3fv(points[0]);
+    PrimBuild::texCoord2f(mDataBlock->texCoords[0].x, mDataBlock->texCoords[0].y);
+    PrimBuild::vertex3fv(points[2]);
+    PrimBuild::texCoord2f(mDataBlock->texCoords[3].x, mDataBlock->texCoords[3].y);
+    PrimBuild::vertex3fv(points[3]);
+  }
+  PrimBuild::end();
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//

+ 132 - 0
Engine/source/afx/ce/afxCameraPuppet.cpp

@@ -0,0 +1,132 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
+// Copyright (C) 2015 Faust Logic, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#include "afx/arcaneFX.h"
+
+#include "console/consoleTypes.h"
+#include "core/stream/bitStream.h"
+#include "scene/sceneRenderState.h"
+#include "math/mathIO.h"
+
+#include "afx/afxChoreographer.h"
+#include "afx/ce/afxCameraPuppet.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxCameraPuppetData
+
+IMPLEMENT_CO_DATABLOCK_V1(afxCameraPuppetData);
+
+ConsoleDocClass( afxCameraPuppetData,
+   "@brief A datablock that specifies a Camera Puppet effect.\n\n"
+
+   "A Camera Puppet effect is used to control the position and orientation of the camera using the AFX constraint system. "
+   "Camera Puppet effects are useful for creating small cut-scenes and can add a lot of visual drama to a spell or effectron "
+   "effect."
+   "\n\n"
+
+   "Effective use of Camera Puppet effects require a fairly advanced understanding of how Torque cameras work in a "
+   "server-client context. Care must be taken to prevent client cameras from drifting too far out of sync from the server camera. "
+   "Otherwise, obvious discontinuities in the motion will result when the Camera Puppet ends and control is restored to the "
+   "server camera. Scoping problems can also result if a client camera is moved to a location that is inconsistent with the "
+   "scene scoping done by the server camera."
+   "\n\n"
+
+   "Often it is useful to manage camera controlling in an isolated effectron rather than directly incorporated into a magic-spell. "
+   "This way the camera controlling effectron can target the specific client associated with the spellcaster. The spellcasting "
+   "player observes the spell in a dramatic cut-scene-like fashion while other players continue to observe from their own "
+   "viewing locations."
+   "\n\n"
+
+   "@ingroup afxEffects\n"
+   "@ingroup AFX\n"
+   "@ingroup Datablocks\n"
+);
+
+afxCameraPuppetData::afxCameraPuppetData()
+{
+  cam_spec = ST_NULLSTRING;
+  networking = SERVER_AND_CLIENT;
+}
+
+afxCameraPuppetData::afxCameraPuppetData(const afxCameraPuppetData& other, bool temp_clone) : GameBaseData(other, temp_clone)
+{
+  cam_spec = other.cam_spec;
+  networking = other.networking;
+}
+
+#define myOffset(field) Offset(field, afxCameraPuppetData)
+
+void afxCameraPuppetData::initPersistFields()
+{
+  addField("cameraSpec",          TypeString,   myOffset(cam_spec),
+    "This field is like the effect-wrapper fields for specifying constraint sources, "
+    "but here it specifies a target for the camera-puppet effect.");
+  addField("networking",          TypeS8,       myOffset(networking),
+    "Specifies the networking model used for the camerapuppet effect. The effect can "
+    "puppet just the server camera, just the client camera, or both.\n"
+    "Possible values: $AFX::SERVER_ONLY, $AFX::CLIENT_ONLY, or $AFX::SERVER_AND_CLIENT.");
+
+  // disallow some field substitutions
+  disableFieldSubstitutions("cameraSpec");
+  disableFieldSubstitutions("networking");
+
+  Parent::initPersistFields();
+}
+
+bool afxCameraPuppetData::onAdd()
+{
+  if (Parent::onAdd() == false)
+    return false;
+
+  bool runs_on_s = ((networking & (SERVER_ONLY | SERVER_AND_CLIENT)) != 0);
+  bool runs_on_c = ((networking & (CLIENT_ONLY | SERVER_AND_CLIENT)) != 0);
+  cam_def.parseSpec(cam_spec, runs_on_s, runs_on_c);
+
+  return true;
+}
+
+void afxCameraPuppetData::packData(BitStream* stream)
+{
+	Parent::packData(stream);
+
+  stream->writeString(cam_spec);
+  stream->write(networking);
+}
+
+void afxCameraPuppetData::unpackData(BitStream* stream)
+{
+  Parent::unpackData(stream);
+
+  cam_spec = stream->readSTString();
+  stream->read(&networking);
+}
+
+void afxCameraPuppetData::gather_cons_defs(Vector<afxConstraintDef>& defs)
+{ 
+  if (cam_def.isDefined())
+    defs.push_back(cam_def);
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//

+ 64 - 0
Engine/source/afx/ce/afxCameraPuppet.h

@@ -0,0 +1,64 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
+// Copyright (C) 2015 Faust Logic, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _AFX_CAMERA_PUPPET_H_
+#define _AFX_CAMERA_PUPPET_H_
+
+#include "afx/ce/afxComponentEffect.h"
+#include "afx/afxEffectDefs.h"
+#include "afx/afxConstraint.h"
+
+class afxCameraPuppetData : public GameBaseData, public afxEffectDefs, public afxComponentEffectData
+{
+  typedef GameBaseData  Parent;
+
+public:
+  StringTableEntry  cam_spec;
+  afxConstraintDef  cam_def;
+
+  U8                networking;
+
+  virtual void      gather_cons_defs(Vector<afxConstraintDef>& defs);
+
+public:
+  /*C*/             afxCameraPuppetData();
+  /*C*/             afxCameraPuppetData(const afxCameraPuppetData&, bool = false);
+
+  virtual bool      onAdd();
+  virtual void      packData(BitStream*);
+  virtual void      unpackData(BitStream*);
+
+  virtual bool      allowSubstitutions() const { return true; }
+
+  static void       initPersistFields();
+
+  DECLARE_CONOBJECT(afxCameraPuppetData);
+  DECLARE_CATEGORY("AFX");
+};
+
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#endif // _AFX_CAMERA_PUPPET_H_

+ 118 - 0
Engine/source/afx/ce/afxCameraShake.cpp

@@ -0,0 +1,118 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
+// Copyright (C) 2015 Faust Logic, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#include "afx/arcaneFX.h"
+
+#include "console/consoleTypes.h"
+#include "core/stream/bitStream.h"
+
+#include "afx/ce/afxCameraShake.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxCameraShakeData
+
+IMPLEMENT_CO_DATABLOCK_V1(afxCameraShakeData);
+
+ConsoleDocClass( afxCameraShakeData,
+   "@brief A datablock that specifies a Camera Shake effect.\n\n"
+
+   "Camera Shake internally utilizes the standard Torque CameraShake class to implement a shaken camera effect."
+   "\n\n"
+
+   "@ingroup afxEffects\n"
+   "@ingroup AFX\n"
+   "@ingroup Datablocks\n"
+);
+
+afxCameraShakeData::afxCameraShakeData()
+{
+  camShakeFreq.set( 10.0, 10.0, 10.0 );
+  camShakeAmp.set( 1.0, 1.0, 1.0 );
+  camShakeRadius = 10.0;
+  camShakeFalloff = 10.0;
+}
+
+afxCameraShakeData::afxCameraShakeData(const afxCameraShakeData& other, bool temp_clone) : GameBaseData(other, temp_clone)
+{
+  camShakeFreq = other.camShakeFreq;
+  camShakeAmp = other.camShakeAmp;
+  camShakeRadius = other.camShakeRadius;
+  camShakeFalloff = other.camShakeFalloff;
+}
+
+#define myOffset(field) Offset(field, afxCameraShakeData)
+
+void afxCameraShakeData::initPersistFields()
+{
+  addField("frequency", TypePoint3F,   Offset(camShakeFreq,       afxCameraShakeData),
+    "The camera shake frequencies for all three axes: X, Y, Z.");
+  addField("amplitude", TypePoint3F,   Offset(camShakeAmp,        afxCameraShakeData),
+    "The camera shake amplitudes for all three axes: X, Y, Z.");
+  addField("radius",    TypeF32,       Offset(camShakeRadius,     afxCameraShakeData),
+    "Radius about the effect position in which shaking will be applied.");
+  addField("falloff",   TypeF32,       Offset(camShakeFalloff,    afxCameraShakeData),
+    "Magnitude by which shaking decreases over distance to radius.");
+
+  Parent::initPersistFields();
+}
+
+bool afxCameraShakeData::onAdd()
+{
+  if (Parent::onAdd() == false)
+    return false;
+
+  return true;
+}
+
+void afxCameraShakeData::packData(BitStream* stream)
+{
+	Parent::packData(stream);
+
+  stream->write(camShakeFreq.x);
+  stream->write(camShakeFreq.y);
+  stream->write(camShakeFreq.z);
+  stream->write(camShakeAmp.x);
+  stream->write(camShakeAmp.y);
+  stream->write(camShakeAmp.z);
+  stream->write(camShakeRadius);
+  stream->write(camShakeFalloff);
+}
+
+void afxCameraShakeData::unpackData(BitStream* stream)
+{
+  Parent::unpackData(stream);
+
+  stream->read(&camShakeFreq.x);
+  stream->read(&camShakeFreq.y);
+  stream->read(&camShakeFreq.z);
+  stream->read(&camShakeAmp.x);
+  stream->read(&camShakeAmp.y);
+  stream->read(&camShakeAmp.z);
+  stream->read(&camShakeRadius);
+  stream->read(&camShakeFalloff);
+}
+
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//

+ 57 - 0
Engine/source/afx/ce/afxCameraShake.h

@@ -0,0 +1,57 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
+// Copyright (C) 2015 Faust Logic, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _AFX_CAMERA_SHAKE_H_
+#define _AFX_CAMERA_SHAKE_H_
+
+class afxCameraShakeData : public GameBaseData
+{
+  typedef GameBaseData  Parent;
+
+public:
+  VectorF               camShakeFreq;
+  VectorF               camShakeAmp;
+  F32                   camShakeRadius;
+  F32                   camShakeFalloff;
+
+public:
+  /*C*/                 afxCameraShakeData();
+  /*C*/                 afxCameraShakeData(const afxCameraShakeData&, bool = false);
+
+  virtual bool          onAdd();
+  virtual void          packData(BitStream*);
+  virtual void          unpackData(BitStream*);
+
+  virtual bool          allowSubstitutions() const { return true; }
+
+  static void           initPersistFields();
+
+  DECLARE_CONOBJECT(afxCameraShakeData);
+  DECLARE_CATEGORY("AFX");
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#endif // _AFX_CAMERA_SHAKE_H_

+ 103 - 0
Engine/source/afx/ce/afxCollisionEvent.cpp

@@ -0,0 +1,103 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
+// Copyright (C) 2015 Faust Logic, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#include "afx/arcaneFX.h"
+
+#include "console/consoleTypes.h"
+#include "core/stream/bitStream.h"
+
+#include "afx/ce/afxCollisionEvent.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxCollisionEventData
+
+IMPLEMENT_CO_DATABLOCK_V1(afxCollisionEventData);
+
+ConsoleDocClass( afxCollisionEventData,
+   "@brief A datablock that specifies a Collision Event effect.\n\n"
+
+   "MORE NEEDED HERE.\n"
+
+   "@ingroup afxEffects\n"
+   "@ingroup AFX\n"
+   "@ingroup Datablocks\n"
+);
+
+afxCollisionEventData::afxCollisionEventData()
+{
+  method_name = ST_NULLSTRING;
+  script_data = ST_NULLSTRING;
+  gen_trigger = false;
+  trigger_bit = 0;
+}
+
+afxCollisionEventData::afxCollisionEventData(const afxCollisionEventData& other, bool temp_clone) : GameBaseData(other, temp_clone)
+{
+  method_name = other.method_name;
+  script_data = other.script_data;
+  gen_trigger = other.gen_trigger;
+  trigger_bit = other.trigger_bit;
+}
+
+#define myOffset(field) Offset(field, afxCollisionEventData)
+
+void afxCollisionEventData::initPersistFields()
+{
+  addField("methodName",        TypeString,   myOffset(method_name),
+    "...");
+  addField("scriptData",        TypeString,   myOffset(script_data),
+    "...");
+  addField("generateTrigger",   TypeBool,     myOffset(gen_trigger),
+    "...");
+  addField("triggerBit",        TypeS8,       myOffset(trigger_bit),
+    "...");
+
+  Parent::initPersistFields();
+}
+
+void afxCollisionEventData::packData(BitStream* stream)
+{
+  Parent::packData(stream);
+
+  stream->writeString(method_name);
+  stream->writeString(script_data);
+  if (stream->writeFlag(gen_trigger))
+    stream->write(trigger_bit);
+}
+
+void afxCollisionEventData::unpackData(BitStream* stream)
+{
+  Parent::unpackData(stream);
+
+  method_name = stream->readSTString();
+  script_data = stream->readSTString();
+  gen_trigger = stream->readFlag();
+  if (gen_trigger)
+    stream->read(&trigger_bit);
+  else
+    trigger_bit = 0;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//

+ 59 - 0
Engine/source/afx/ce/afxCollisionEvent.h

@@ -0,0 +1,59 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
+// Copyright (C) 2015 Faust Logic, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _AFX_COLLISION_EVENT_H_
+#define _AFX_COLLISION_EVENT_H_
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxCollisionEventData
+
+struct afxCollisionEventData : public GameBaseData
+{
+  typedef GameBaseData Parent;
+
+public:
+  StringTableEntry      method_name;
+  StringTableEntry      script_data;
+  bool                  gen_trigger;
+  U8                    trigger_bit;
+
+public:
+  /*C*/                 afxCollisionEventData();
+  /*C*/                 afxCollisionEventData(const afxCollisionEventData&, bool = false);
+
+  void                  packData(BitStream* stream);
+  void                  unpackData(BitStream* stream);
+
+  virtual bool          allowSubstitutions() const { return true; }
+
+  static void           initPersistFields();
+
+  DECLARE_CONOBJECT(afxCollisionEventData);
+  DECLARE_CATEGORY("AFX");
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#endif // _AFX_COLLISION_EVENT_H_

+ 41 - 0
Engine/source/afx/ce/afxComponentEffect.h

@@ -0,0 +1,41 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
+// Copyright (C) 2015 Faust Logic, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _AFX_COMPONENT_EFFECT_H_
+#define _AFX_COMPONENT_EFFECT_H_
+
+#include "core/util/tVector.h"
+
+#include "afx/afxConstraint.h"
+
+class afxComponentEffectData
+{
+public:
+  virtual void  gather_cons_defs(Vector<afxConstraintDef>& defs) { };
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#endif // _AFX_COMPONENT_EFFECT_H_

+ 93 - 0
Engine/source/afx/ce/afxConsoleMessage.cpp

@@ -0,0 +1,93 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
+// Copyright (C) 2015 Faust Logic, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#include "afx/arcaneFX.h"
+
+#include "console/consoleTypes.h"
+#include "core/stream/bitStream.h"
+
+#include "afx/ce/afxConsoleMessage.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxConsoleMessageData
+
+IMPLEMENT_CO_DATABLOCK_V1(afxConsoleMessageData);
+
+ConsoleDocClass( afxConsoleMessageData,
+   "@brief A datablock that specifies a Console Message effect.\n\n"
+
+   "Console Message effects are useful for debugging purposes when you want to make sure that an effect with a certain kind "
+   "of timing is actually getting executed and for evaluating some kinds of field substitutions."
+   "\n\n"
+
+   "@ingroup afxEffects\n"
+   "@ingroup AFX\n"
+   "@ingroup Datablocks\n"
+);
+
+afxConsoleMessageData::afxConsoleMessageData()
+{
+  message_str = ST_NULLSTRING;
+}
+
+afxConsoleMessageData::afxConsoleMessageData(const afxConsoleMessageData& other, bool temp_clone) : GameBaseData(other, temp_clone)
+{
+  message_str = other.message_str;
+}
+
+#define myOffset(field) Offset(field, afxConsoleMessageData)
+
+void afxConsoleMessageData::initPersistFields()
+{
+  addField("message",    TypeString,     myOffset(message_str),
+    "A text message to be displayed when the effect is executed.");
+
+  Parent::initPersistFields();
+}
+
+bool afxConsoleMessageData::onAdd()
+{
+  if (Parent::onAdd() == false)
+    return false;
+
+  return true;
+}
+
+void afxConsoleMessageData::packData(BitStream* stream)
+{
+	Parent::packData(stream);
+
+  stream->writeString(message_str);
+}
+
+void afxConsoleMessageData::unpackData(BitStream* stream)
+{
+  Parent::unpackData(stream);
+
+  message_str = stream->readSTString();
+}
+
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//

+ 54 - 0
Engine/source/afx/ce/afxConsoleMessage.h

@@ -0,0 +1,54 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
+// Copyright (C) 2015 Faust Logic, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _AFX_CONSOLE_MESSAGE_H_
+#define _AFX_CONSOLE_MESSAGE_H_
+
+class afxConsoleMessageData : public GameBaseData
+{
+  typedef GameBaseData  Parent;
+
+public:
+  StringTableEntry      message_str;
+
+public:
+  /*C*/                 afxConsoleMessageData();
+  /*C*/                 afxConsoleMessageData(const afxConsoleMessageData&, bool = false);
+
+  virtual bool          onAdd();
+  virtual void          packData(BitStream*);
+  virtual void          unpackData(BitStream*);
+
+  virtual bool          allowSubstitutions() const { return true; }
+
+  static void           initPersistFields();
+
+  DECLARE_CONOBJECT(afxConsoleMessageData);
+  DECLARE_CATEGORY("AFX");
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#endif // _AFX_CONSOLE_MESSAGE_H_

+ 140 - 0
Engine/source/afx/ce/afxDamage.cpp

@@ -0,0 +1,140 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
+// Copyright (C) 2015 Faust Logic, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#include "afx/arcaneFX.h"
+
+#include "console/consoleTypes.h"
+#include "core/stream/bitStream.h"
+
+#include "afx/ce/afxDamage.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxDamageData
+
+IMPLEMENT_CO_DATABLOCK_V1(afxDamageData);
+
+ConsoleDocClass( afxDamageData,
+   "@brief A datablock that specifies a Damage effect.\n\n"
+
+   "A Damage effect is useful for assigning damage with unusual timing that must be synchronized with other effects. They " 
+   "can be used to deal direct damage, radius damage, and damage over time. Negative damage amounts can be used for "
+   "healing effects."
+   "\n\n"
+
+   "@ingroup afxEffects\n"
+   "@ingroup AFX\n"
+   "@ingroup Datablocks\n"
+);
+
+afxDamageData::afxDamageData()
+{
+  label = ST_NULLSTRING;
+  flavor = ST_NULLSTRING;
+  amount = 0;
+  repeats = 1;
+  ad_amount = 0;
+  radius = 0;
+  impulse = 0;
+}
+
+afxDamageData::afxDamageData(const afxDamageData& other, bool temp_clone) : GameBaseData(other, temp_clone)
+{
+  label = other.label;
+  flavor = other.flavor;
+  amount = other.amount;
+  repeats = other.repeats;
+  ad_amount = other.ad_amount;
+  radius = other.radius;
+  impulse = other.impulse;
+}
+
+#define myOffset(field) Offset(field, afxDamageData)
+
+void afxDamageData::initPersistFields()
+{
+  addField("label",               TypeString,     myOffset(label),
+    "An arbitrary string which is passed as an argument to a spell's onDamage() script "
+    "method. It can be used to identify which damage effect the damage came from in "
+    "cases where more than one damage effect is used in a single spell.");
+  addField("flavor",              TypeString,     myOffset(flavor),
+    "An arbitrary string which is passed as an argument to a spell's onDamage() script "
+    "method. It is used to classify a type of damage such as 'melee', 'magical', or "
+    "'fire'.");
+  addField("directDamage",        TypeF32,        myOffset(amount),
+    "An amount of direct damage to inflict on a target.");
+  addField("directDamageRepeats", TypeS8,         myOffset(repeats),
+    "The number of times to inflict the damage specified by directDamage. Values "
+    "greater than 1 inflict damage over time, with the amount of directDamage "
+    "repeatedly dealt at evenly spaced intervals over the lifetime of the effect.");
+  addField("areaDamage",          TypeF32,        myOffset(ad_amount),
+    "An amount of area damage to inflict on a target. Objects within half the radius "
+    "receive full damage which then diminishes out to the full distance of "
+    "areaDamageRadius.");
+  addField("areaDamageRadius",    TypeF32,        myOffset(radius),
+    "Radius centered at the effect position in which damage will be applied.");
+  addField("areaDamageImpulse",   TypeF32,        myOffset(impulse),
+    "Specifies an amount of force to apply to damaged objects. Objects within half the "
+    "radius receive full impulse which then diminishes out to the full distance of "
+    "areaDamageRadius.");
+
+  Parent::initPersistFields();
+}
+
+bool afxDamageData::onAdd()
+{
+  if (Parent::onAdd() == false)
+    return false;
+
+  return true;
+}
+
+void afxDamageData::packData(BitStream* stream)
+{
+	Parent::packData(stream);
+
+  stream->writeString(label);
+  stream->writeString(flavor);
+  stream->write(amount);
+  stream->write(repeats);
+  stream->write(ad_amount);
+  stream->write(radius);
+  stream->write(impulse);
+}
+
+void afxDamageData::unpackData(BitStream* stream)
+{
+  Parent::unpackData(stream);
+
+  label = stream->readSTString();
+  flavor = stream->readSTString();
+  stream->read(&amount);
+  stream->read(&repeats);
+  stream->read(&ad_amount);
+  stream->read(&radius);
+  stream->read(&impulse);
+}
+
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//

+ 63 - 0
Engine/source/afx/ce/afxDamage.h

@@ -0,0 +1,63 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
+// Copyright (C) 2015 Faust Logic, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _AFX_DAMAGE_H_
+#define _AFX_DAMAGE_H_
+
+#include "afx/afxEffectDefs.h"
+
+class afxDamageData : public GameBaseData, public afxEffectDefs
+{
+  typedef GameBaseData  Parent;
+
+public:
+  StringTableEntry  label;
+  StringTableEntry  flavor;
+
+  F32           amount;
+  U8            repeats;
+  F32           ad_amount;
+  F32           radius;
+  F32           impulse;
+
+public:
+  /*C*/         afxDamageData();
+  /*C*/         afxDamageData(const afxDamageData&, bool = false);
+
+  virtual bool  onAdd();
+  virtual void  packData(BitStream*);
+  virtual void  unpackData(BitStream*);
+
+  virtual bool  allowSubstitutions() const { return true; }
+
+  static void   initPersistFields();
+
+  DECLARE_CONOBJECT(afxDamageData);
+  DECLARE_CATEGORY("AFX");
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#endif // _AFX_DAMAGE_H_

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно