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

Merge pull request #2056 from Bloodknight/afx_merge_main

Afx merge main
Areloch 8 жил өмнө
parent
commit
35d649b57e
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.
 //-----------------------------------------------------------------------------
 
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
+// Copyright (C) 2015 Faust Logic, Inc.
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
 #include "platform/platform.h"
 #include "T3D/aiPlayer.h"
 
@@ -97,6 +102,9 @@ AIPlayer::AIPlayer()
    mMoveSlowdown = true;
    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;
    mAimLocationSet = false;
    mTargetInLOS = false;
@@ -547,23 +555,27 @@ bool AIPlayer::getAIMove(Move *movePtr)
             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 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
                // we should TRY to check if we've moved. This could use better detection.
                if ( mMoveState != ModeSlowing || locationDelta == 0 )
-               {
-                  mMoveState = ModeStuck;
-                  onStuck();
-               }
-            }
+	               {
+	                  mMoveState = ModeStuck;
+	                  onStuck();
+	               }
+	            }
+	         }
          }
       }
    }
@@ -626,6 +638,7 @@ bool AIPlayer::getAIMove(Move *movePtr)
    }
 #endif // TORQUE_NAVIGATION_ENABLED
 
+   if (!(anim_clip_flags & ANIM_OVERRIDDEN) && !isAnimationLocked())
    mLastLocation = location;
 
    return true;
@@ -1415,6 +1428,47 @@ DefineEngineMethod( AIPlayer, clearMoveTriggers, void, ( ),,
    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)
 {
    if (!isServerObject()) return false;

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

@@ -20,6 +20,11 @@
 // 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_
 #define _AIPLAYER_H_
 
@@ -225,6 +230,18 @@ public:
 
    /// @}
 #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

+ 7 - 0
Engine/source/T3D/camera.h

@@ -20,6 +20,11 @@
 // 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_
 #define _CAMERA_H_
 
@@ -246,6 +251,8 @@ class Camera: public ShapeBase
       DECLARE_CONOBJECT( Camera );
       DECLARE_CATEGORY( "Game" );
       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;

+ 8 - 1
Engine/source/T3D/containerQuery.cpp

@@ -20,6 +20,11 @@
 // 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 "T3D/containerQuery.h"
 
@@ -91,7 +96,9 @@ void physicalZoneFind(SceneObject* obj, void *key)
 
    if (pz->isActive()) {
       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.
 //-----------------------------------------------------------------------------
 
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
+// Copyright (C) 2015 Faust Logic, Inc.
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
 #include "platform/platform.h"
 #include "T3D/debris.h"
 
@@ -113,6 +118,85 @@ DebrisData::DebrisData()
    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()
 {
    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.");
    endGroup("Behavior");
 
+   // disallow some field substitutions
+   onlyKeepClearSubstitutions("emitters"); // subs resolving to "~~", or "~0" are OK
+   onlyKeepClearSubstitutions("explosion");
    Parent::initPersistFields();
 }
 
@@ -451,6 +538,8 @@ Debris::Debris()
 
    // Only allocated client side.
    mNetFlags.set( IsGhost );
+   ss_object = 0;
+   ss_index = 0;
 }
 
 Debris::~Debris()
@@ -466,6 +555,12 @@ Debris::~Debris()
       delete mPart;
       mPart = NULL;
    }
+   
+   if (mDataBlock && mDataBlock->isTempClone())
+   { 
+      delete mDataBlock;
+      mDataBlock = 0;
+   }
 }
 
 void Debris::initPersistFields()
@@ -495,6 +590,8 @@ bool Debris::onNewDataBlock( GameBaseData *dptr, bool reload )
    if( !mDataBlock || !Parent::onNewDataBlock( dptr, reload ) )
       return false;
 
+   if (mDataBlock->isTempClone())
+      return true;
    scriptOnNewDataBlock();
    return true;
 
@@ -519,7 +616,7 @@ bool Debris::onAdd()
       if( mDataBlock->emitterList[i] != NULL )
       {
          ParticleEmitter * pEmitter = new ParticleEmitter;
-         pEmitter->onNewDataBlock( mDataBlock->emitterList[i], false );
+         pEmitter->onNewDataBlock(mDataBlock->emitterList[i]->cloneAndPerformSubstitutions(ss_object, ss_index), false);
          if( !pEmitter->registerObject() )
          {
             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[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 );
    }
@@ -546,7 +644,8 @@ bool Debris::onAdd()
    {
       sizeList[0] = 0.0;
       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 );
    }
@@ -798,7 +897,8 @@ void Debris::explode()
    Point3F explosionPos = getPosition();
 
    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 );
    trans.setPosition( getPosition() );

+ 16 - 0
Engine/source/T3D/debris.h

@@ -20,6 +20,11 @@
 // 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_
 #define _DEBRIS_H_
 
@@ -97,6 +102,12 @@ struct DebrisData : public GameBaseData
 
    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);
 
+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.
 //-----------------------------------------------------------------------------
 
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
+// Copyright (C) 2015 Faust Logic, Inc.
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
 #include "platform/platform.h"
 #include "T3D/fx/explosion.h"
 
@@ -50,6 +55,8 @@
 #include "renderInstance/renderPassManager.h"
 #include "console/engineAPI.h"
 
+#include "sfx/sfxProfile.h"
+
 IMPLEMENT_CONOBJECT(Explosion);
 
 ConsoleDocClass( Explosion,
@@ -281,6 +288,105 @@ ExplosionData::ExplosionData()
    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()
 {
    addField( "explosionShape", TypeShapeFilename, Offset(dtsFileName, ExplosionData),
@@ -412,6 +518,12 @@ void ExplosionData::initPersistFields()
       "Distance (in the explosion normal direction) of the PointLight position "
       "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();
 }
 
@@ -808,6 +920,10 @@ Explosion::Explosion()
    mLight = LIGHTMGR->createLightInfo();
 
    mNetFlags.set( IsGhost );
+   ss_object = 0;
+   ss_index = 0;
+   mDataBlock = 0;
+   soundProfile_clone = 0;
 }
 
 Explosion::~Explosion()
@@ -820,6 +936,18 @@ Explosion::~Explosion()
    }
    
    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 ))
       return false;
 
+   if (mDataBlock->isTempClone())
+      return true;
    scriptOnNewDataBlock();
    return true;
 }
@@ -1190,7 +1320,8 @@ void Explosion::launchDebris( Point3F &axis )
       launchDir *= debrisVel;
 
       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->init( pos, launchDir );
 
@@ -1218,7 +1349,8 @@ void Explosion::spawnSubExplosions()
       {
          MatrixF trans = getTransform();
          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->setInitialState( trans.getPosition(), mInitialNormal, 1);
          if (!pExplosion->registerObject())
@@ -1256,12 +1388,18 @@ bool Explosion::explode()
       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) {
       mMainEmitter = new ParticleEmitter;
-      mMainEmitter->setDataBlock(mDataBlock->particleEmitter);
+      mMainEmitter->setDataBlock(mDataBlock->particleEmitter->cloneAndPerformSubstitutions(ss_object, ss_index));
       mMainEmitter->registerObject();
 
       mMainEmitter->emitParticles(getPosition(), mInitialNormal, mDataBlock->particleRadius,
@@ -1273,7 +1411,7 @@ bool Explosion::explode()
       if( mDataBlock->emitterList[i] != NULL )
       {
          ParticleEmitter * pEmitter = new ParticleEmitter;
-         pEmitter->setDataBlock( mDataBlock->emitterList[i] );
+         pEmitter->setDataBlock(mDataBlock->emitterList[i]->cloneAndPerformSubstitutions(ss_object, ss_index));
          if( !pEmitter->registerObject() )
          {
             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.
 //-----------------------------------------------------------------------------
 
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
+// Copyright (C) 2015 Faust Logic, Inc.
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
 #ifndef _EXPLOSION_H_
 #define _EXPLOSION_H_
 
@@ -42,6 +47,7 @@ class TSThread;
 class SFXTrack;
 struct DebrisData;
 
+class SFXProfile;
 //--------------------------------------------------------------------------
 class ExplosionData : public GameBaseData {
   public:
@@ -126,6 +132,11 @@ class ExplosionData : public GameBaseData {
    static void  initPersistFields();
    virtual void packData(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);
    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

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

@@ -43,6 +43,11 @@
 // POTENTIAL TODO LIST:
 //   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 "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." );
    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.
    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));
 
             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);
 
@@ -1705,6 +1718,7 @@ U32 fxFoliageReplicator::packUpdate(NetConnection * con, U32 mask, BitStream * s
       stream->writeFlag(mFieldData.mShowPlacementArea);				// Show Placement Area Flag.
       stream->write(mFieldData.mPlacementBandHeight);					// Placement Area Height.
       stream->write(mFieldData.mPlaceAreaColour);						// Placement Area Colour.
+      stream->write(mFieldData.mAmbientModulationBias);
    }
 
    // Were done ...
@@ -1782,6 +1796,7 @@ void fxFoliageReplicator::unpackUpdate(NetConnection * con, BitStream * stream)
       stream->read(&mFieldData.mPlacementBandHeight);					// Placement Area Height.
       stream->read(&mFieldData.mPlaceAreaColour);
 
+      stream->read(&mFieldData.mAmbientModulationBias);
       // Calculate Fade-In/Out Gradients.
       mFadeInGradient		= 1.0f / mFieldData.mFadeInRegion;
       mFadeOutGradient	= 1.0f / mFieldData.mFadeOutRegion;

+ 7 - 0
Engine/source/T3D/fx/fxFoliageReplicator.h

@@ -20,6 +20,11 @@
 // 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_
 #define _FOLIAGEREPLICATOR_H_
 
@@ -319,6 +324,7 @@ public:
       U32             mPlacementBandHeight;
       LinearColorF          mPlaceAreaColour;
 
+      F32             mAmbientModulationBias;
       tagFieldData()
       {
          // Set Defaults.
@@ -377,6 +383,7 @@ public:
          mShowPlacementArea    = true;
          mPlacementBandHeight  = 25;
          mPlaceAreaColour      .set(0.4f, 0, 0.8f);
+         mAmbientModulationBias = 1.0f;
       }
 
    } 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
 // 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 "console/consoleTypes.h"
 #include "console/typeValidators.h"
@@ -72,6 +78,8 @@ static const F32 sgDefaultSpinSpeed = 1.f;
 static const F32 sgDefaultSpinRandomMin = 0.f;
 static const F32 sgDefaultSpinRandomMax = 0.f;
 
+static const F32 sgDefaultSpinBias = 1.0f;
+static const F32 sgDefaultSizeBias = 1.0f;
 
 //-----------------------------------------------------------------------------
 // Constructor
@@ -102,9 +110,9 @@ ParticleData::ParticleData()
    }
 
    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[1].set(0.0,1.0);   // of particle quad
@@ -115,18 +123,20 @@ ParticleData::ParticleData()
    animTexUVs = NULL;           // array of tile vertex UVs
    textureName = NULL;          // texture filename
    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
 //-----------------------------------------------------------------------------
-ParticleData::~ParticleData()
-{
-   if (animTexUVs)
-   {
-      delete [] animTexUVs;
-   }
-}
+
 
 FRangeValidator dragCoefFValidator(0.f, 5.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"
       "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();
 }
 
@@ -243,18 +262,22 @@ void ParticleData::packData(BitStream* stream)
       stream->writeInt((S32)(spinRandomMin + 1000), 11);
       stream->writeInt((S32)(spinRandomMax + 1000), 11);
    }
+   if(stream->writeFlag(spinBias != sgDefaultSpinBias))
+      stream->write(spinBias);
+   stream->writeFlag(randomizeSpinDir);
    stream->writeFlag(useInvAlpha);
 
    S32 i, count;
 
    // 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)
          break;
 
    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++ )
    {
@@ -262,7 +285,8 @@ void ParticleData::packData(BitStream* stream)
       stream->writeFloat( colors[i].green, 7);
       stream->writeFloat( colors[i].blue, 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);
    }
 
@@ -279,6 +303,13 @@ void ParticleData::packData(BitStream* stream)
       mathWrite(*stream, animTexTiling);
       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;
    }
 
+   if(stream->readFlag())
+      stream->read(&spinBias);
+   else
+      spinBias = sgDefaultSpinBias;
+   randomizeSpinDir = stream->readFlag();
    useInvAlpha = stream->readFlag();
 
    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++)
    {
       colors[i].red = stream->readFloat(7);
       colors[i].green = stream->readFloat(7);
       colors[i].blue = 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);
    }
    textureName = (stream->readFlag()) ? stream->readSTString() : 0;
@@ -346,6 +384,14 @@ void ParticleData::unpackData(BitStream* stream)
      mathRead(*stream, &animTexTiling);
      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) 
@@ -427,11 +473,33 @@ bool ParticleData::onAdd()
    }
 
    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
@@ -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;
 }
 
@@ -495,6 +567,15 @@ bool ParticleData::preload(bool server, String &errorStr)
           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) 
       {
@@ -606,6 +687,11 @@ void ParticleData::initializeParticle(Particle* init, const Point3F& inheritVelo
 
    // assign spin amount
    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])
@@ -653,3 +739,78 @@ DefineEngineMethod(ParticleData, reload, void, (),,
    char errorBuffer[256];
    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.
 //-----------------------------------------------------------------------------
 
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
+// Copyright (C) 2015 Faust Logic, Inc.
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
 #ifndef _PARTICLE_H_
 #define _PARTICLE_H_
 
@@ -44,7 +49,9 @@ class ParticleData : public SimDataBlock
   public:
    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;
@@ -97,6 +104,23 @@ class ParticleData : public SimDataBlock
    static void  initPersistFields();
 
    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;
    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.
 //-----------------------------------------------------------------------------
 
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
+// Copyright (C) 2015 Faust Logic, Inc.
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
 #include "platform/platform.h"
 #include "T3D/fx/particleEmitter.h"
 
@@ -38,6 +43,10 @@
 #include "lighting/lightInfo.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 );
 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;
    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" );
 
+   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();
 }
 
@@ -358,6 +402,22 @@ void ParticleEmitterData::packData(BitStream* stream)
    stream->writeFlag(renderReflection);
    stream->writeFlag(glow);
    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();
    glow = stream->readFlag();
    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 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
      if (textureName && textureName[0])
      {
@@ -669,6 +761,8 @@ void ParticleEmitterData::allocPrimBuffer( S32 overrideSize )
 
    partListInitSize = maxPartLife / (ejectionPeriodMS - periodVarianceMS);
    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( overrideSize != -1 )
@@ -705,6 +799,134 @@ void ParticleEmitterData::allocPrimBuffer( S32 overrideSize )
    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
@@ -736,6 +958,16 @@ ParticleEmitter::ParticleEmitter()
 
    // ParticleEmitter should be allocated on the client only.
    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];
    }
+   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);
    resetWorldBox();
 
+#if defined(AFX_CAP_PARTICLE_POOLS) 
+   if (pool)
+     pool->addParticleEmitter(this);
+#endif
+
    return true;
 }
 
@@ -780,6 +1030,14 @@ bool ParticleEmitter::onAdd()
 //-----------------------------------------------------------------------------
 void ParticleEmitter::onRemove()
 {
+#if defined(AFX_CAP_PARTICLE_POOLS) 
+  if (pool)
+  {
+    pool->removeParticleEmitter(this);
+    pool = 0;
+  }
+#endif
+
    removeFromScene();
    Parent::onRemove();
 }
@@ -825,6 +1083,11 @@ bool ParticleEmitter::onNewDataBlock( GameBaseData *dptr, bool reload )
       part_list_head.next = NULL;
       n_parts = 0;
    }
+   if (mDataBlock->isTempClone())
+   {
+     db_temp_clone = true;
+     return true;
+   }
 
    scriptOnNewDataBlock();
    return true;
@@ -861,6 +1124,11 @@ LinearColorF ParticleEmitter::getCollectiveColor()
 //-----------------------------------------------------------------------------
 void ParticleEmitter::prepRenderImage(SceneRenderState* state)
 {
+#if defined(AFX_CAP_PARTICLE_POOLS)
+   if (pool)
+     return;
+#endif 
+
    if( state->isReflectPass() && !getDataBlock()->renderReflection )
       return;
 
@@ -889,6 +1157,7 @@ void ParticleEmitter::prepRenderImage(SceneRenderState* state)
    ri->translucentSort = true;
    ri->type = RenderPassManager::RIT_Particle;
    ri->sortDistSq = getRenderWorldBox().getSqDistanceToPoint( camPos );
+   ri->defaultKey = (-sort_priority*100);
 
    // Draw the system offscreen unless the highResOnly flag is set on the datablock
    ri->systemState = ( getDataBlock()->highResOnly ? PSS_AwaitingHighResDraw : PSS_AwaitingOffscreenDraw );
@@ -992,6 +1261,7 @@ void ParticleEmitter::emitParticles(const Point3F& point,
       return;
    }
 
+   pos_pe = point;
    Point3F realStart;
    if( useLastPosition && mHasLastPosition )
       realStart = mLastPosition;
@@ -1059,7 +1329,8 @@ void ParticleEmitter::emitParticles(const Point3F& start,
          // Create particle at the correct position
          Point3F pos;
          pos.interpolate(start, end, F32(currTime) / F32(numMilliseconds));
-         addParticle(pos, axis, velocity, axisx);
+         addParticle(pos, axis, velocity, axisx, numMilliseconds-currTime);
+
          particlesAdded = true;
          mNextParticleTime = 0;
       }
@@ -1089,7 +1360,7 @@ void ParticleEmitter::emitParticles(const Point3F& start,
       // Create particle at the correct position
       Point3F pos;
       pos.interpolate(start, end, F32(currTime) / F32(numMilliseconds));
-      addParticle(pos, axis, velocity, axisx);
+      addParticle(pos, axis, velocity, axisx, numMilliseconds-currTime);
       particlesAdded = true;
 
       //   This override-advance code is restored in order to correctly adjust
@@ -1114,17 +1385,27 @@ void ParticleEmitter::emitParticles(const Point3F& start,
          {
             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();
       pos += rCenter;
 
-      addParticle(pos, axis, velocity, axisz);
+      addParticle(pos, axis, velocity, axisz, 0);
    }
 
    // Set world bounding box
@@ -1219,6 +1500,8 @@ void ParticleEmitter::emitParticles(const Point3F& rCenter,
 //-----------------------------------------------------------------------------
 void ParticleEmitter::updateBBox()
 {
+   if (forced_bbox)
+     return;
    Point3F minPt(1e10,   1e10,  1e10);
    Point3F maxPt(-1e10, -1e10, -1e10);
 
@@ -1239,15 +1522,18 @@ void ParticleEmitter::updateBBox()
    boxScale.y = getMax(boxScale.y, 1.0f);
    boxScale.z = getMax(boxScale.z, 1.0f);
    mBBObjToWorld.scale(boxScale);
+
+#if defined(AFX_CAP_PARTICLE_POOLS)
+   if (pool)
+     pool->updatePoolBBox(this);
+#endif
 }
 
 //-----------------------------------------------------------------------------
 // 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++;
    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;
    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;
    F32 theta = (mDataBlock->thetaMax - mDataBlock->thetaMin) * gRandGen.randF() +
                mDataBlock->thetaMin;
@@ -1290,14 +1586,17 @@ void ParticleEmitter::addParticle(const Point3F& pos,
    F32 initialVel = mDataBlock->ejectionVelocity;
    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->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);
    updateKeyData( pNew );
 
@@ -1379,8 +1678,10 @@ void ParticleEmitter::updateKeyData( Particle *part )
 	if( 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++ )
    {
@@ -1412,7 +1713,25 @@ void ParticleEmitter::updateKeyData( Particle *part )
          {
             part->size = (part->dataBlock->sizes[i-1] * (1.0 - 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;
 
       }
@@ -1424,19 +1743,25 @@ void ParticleEmitter::updateKeyData( Particle *part )
 //-----------------------------------------------------------------------------
 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)
    {
-      F32 t = F32(ms) / 1000.0;
-
       Point3F a = part->acc;
-      a -= part->vel        * part->dataBlock->dragCoefficient;
+      a -= part->vel * part->dataBlock->dragCoefficient;
       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->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 );
    }
@@ -1999,3 +2324,43 @@ DefineEngineMethod(ParticleEmitterData, reload, void,(),,
 {
    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.
 //-----------------------------------------------------------------------------
 
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
+// Copyright (C) 2015 Faust Logic, Inc.
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
 #ifndef _H_PARTICLE_EMITTER
 #define _H_PARTICLE_EMITTER
 
@@ -42,6 +47,12 @@
 class RenderPassManager;
 class ParticleData;
 
+#define AFX_CAP_PARTICLE_POOLS
+#if defined(AFX_CAP_PARTICLE_POOLS)
+class afxParticlePoolData;
+class afxParticlePool;
+#endif
+
 //*****************************************************************************
 // Particle Emitter Data
 //*****************************************************************************
@@ -113,6 +124,26 @@ class ParticleEmitterData : public GameBaseData
    bool glow;                                ///< Renders this emitter into the glow buffer.
 
    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
 {
    typedef GameBase Parent;
+#if defined(AFX_CAP_PARTICLE_POOLS) 
+   friend class afxParticlePool;
+#endif 
 
   public:
 
@@ -190,7 +224,7 @@ class ParticleEmitter : public GameBase
    /// @param   axis
    /// @param   vel   Initial velocity
    /// @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,
@@ -227,7 +261,12 @@ class ParticleEmitter : public GameBase
    // PEngine interface
   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 );
+protected:
    inline void updateKeyData( Particle *part );
  
 
@@ -239,25 +278,30 @@ class ParticleEmitter : public GameBase
 
    ParticleEmitterData* mDataBlock;
 
+protected: 
    U32       mInternalClock;
 
    U32       mNextParticleTime;
 
    Point3F   mLastPosition;
    bool      mHasLastPosition;
+private:   
    MatrixF   mBBObjToWorld;
 
    bool      mDeleteWhenEmpty;
    bool      mDeleteOnTick;
 
+protected: 
    S32       mLifetimeMS;
    S32       mElapsedTimeMS;
 
+private:  
    F32       sizes[ ParticleData::PDC_NUM_KEYS ];
    LinearColorF    colors[ ParticleData::PDC_NUM_KEYS ];
 
    GFXVertexBufferHandle<ParticleVertexType> mVertBuff;
 
+protected:
    //   These members are for implementing a link-list of the active emitter 
    //   particles. Member part_store contains blocks of particles that can be
    //   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;
    S32        n_part_capacity;
    S32        n_parts;
+private:    
    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

+ 39 - 2
Engine/source/T3D/gameBase/gameBase.cpp

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

+ 11 - 1
Engine/source/T3D/gameBase/gameBase.h

@@ -20,6 +20,11 @@
 // 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_
 #define _GAMEBASE_H_
 
@@ -113,6 +118,8 @@ public:
    DECLARE_CALLBACK( void, onMount, ( 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 {      
       DataBlockMask     = Parent::NextFreeMask << 0,
       ExtendedInfoMask  = Parent::NextFreeMask << 1,
-      NextFreeMask      = Parent::NextFreeMask << 2
+      ScopeIdMask       = Parent::NextFreeMask << 2,
+      NextFreeMask      = Parent::NextFreeMask << 3,
    };
 
    // net flags added by game base
@@ -453,6 +461,8 @@ private:
    /// within this callback.
    ///   
    void _onDatablockModified();
+protected:
+   void    onScopeIdChange() { setMaskBits(ScopeIdMask); }
 };
 
 

+ 609 - 1
Engine/source/T3D/gameBase/gameConnection.cpp

@@ -20,6 +20,11 @@
 // 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 "T3D/gameBase/gameConnection.h"
 
@@ -52,6 +57,11 @@
    #include "T3D/gameBase/std/stdMoveList.h"
 #endif
 
+#ifdef AFX_CAP_DATABLOCK_CACHE 
+#include "core/stream/fileStream.h"
+#endif 
+
+#include "afx/arcaneFX.h"
 //----------------------------------------------------------------------------
 #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"
    "@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()
 {
+   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;
    mControlObject = NULL;
    mCameraObject = NULL;
@@ -246,6 +273,10 @@ GameConnection::~GameConnection()
       dFree(mConnectArgv[i]);
    dFree(mJoinPassword);
    delete mMoveList;
+
+#ifdef AFX_CAP_DATABLOCK_CACHE
+   delete client_db_stream;
+#endif 
 }
 
 //----------------------------------------------------------------------------
@@ -1144,6 +1175,17 @@ void GameConnection::readPacket(BitStream *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;
       mDamageFlash = 0;
       mWhiteOut = 0;
@@ -1387,6 +1429,35 @@ void GameConnection::writePacket(BitStream *bstream, PacketNotify *note)
       // all the damage flash & white out
 
       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())
       {
          gIndex = getGhostIndex(mControlObject);
@@ -1608,6 +1679,14 @@ void GameConnection::preloadNextDataBlock(bool hadNewFiles)
          sendConnectionMessage(DataBlocksDownloadDone, mDataBlockSequence);
 
 //          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;
       }
       mFilesWereDownloaded = hadNewFiles;
@@ -1771,7 +1850,11 @@ DefineEngineMethod( GameConnection, transmitDataBlocks, void, (S32 sequence),,
     const U32 iCount = pGroup->size();
 
     // If this is the local client...
+#ifdef AFX_CAP_DATABLOCK_CACHE 
+    if (GameConnection::getLocalClientConnection() == object && !GameConnection::serverCacheEnabled())
+#else
     if (GameConnection::getLocalClientConnection() == object)
+#endif 
     {
         // Set up a pointer to the datablock.
         SimDataBlock* pDataBlock = 0;
@@ -2166,6 +2249,13 @@ void GameConnection::consoleInit()
       "@ingroup Networking\n");
 
    // 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),,
@@ -2360,4 +2450,522 @@ DefineEngineMethod( GameConnection, getVisibleGhostDistance, F32, (),,
    )
 {
    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.
 //-----------------------------------------------------------------------------
 
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
+// Copyright (C) 2015 Faust Logic, Inc.
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
 #ifndef _GAMECONNECTION_H_
 #define _GAMECONNECTION_H_
 
@@ -55,6 +60,14 @@ class MoveList;
 struct Move;
 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 MaxCameraFov              = 179.f;    ///< max camera FOV
 
@@ -372,6 +385,61 @@ protected:
    DECLARE_CALLBACK( void, setLagIcon, (bool state) );
    DECLARE_CALLBACK( void, onDataBlocksDone, (U32 sequence) );
    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

+ 21 - 0
Engine/source/T3D/gameBase/gameConnectionEvents.cpp

@@ -20,6 +20,11 @@
 // 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 "core/dnet.h"
 #include "core/stream/bitStream.h"
@@ -136,6 +141,9 @@ void SimDataBlockEvent::notifyDelivered(NetConnection *conn, bool )
 
 void SimDataBlockEvent::pack(NetConnection *conn, BitStream *bstream)
 {
+#ifdef AFX_CAP_DATABLOCK_CACHE 
+   ((GameConnection *)conn)->tempDisableStringBuffering(bstream);
+#endif 
    SimDataBlock* obj;
    Sim::findObject(id,obj);
    GameConnection *gc = (GameConnection *) conn;
@@ -157,10 +165,18 @@ void SimDataBlockEvent::pack(NetConnection *conn, BitStream *bstream)
       bstream->writeInt(classId ^ DebugChecksum, 32);
 #endif
    }
+#ifdef AFX_CAP_DATABLOCK_CACHE 
+   ((GameConnection *)conn)->restoreStringBuffering(bstream);
+#endif 
 }
 
 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())
    {
       mProcess = true;
@@ -215,6 +231,11 @@ void SimDataBlockEvent::unpack(NetConnection *cptr, BitStream *bstream)
 #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)

+ 20 - 0
Engine/source/T3D/gameBase/processList.cpp

@@ -20,6 +20,11 @@
 // 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 "T3D/gameBase/processList.h"
 
@@ -284,5 +289,20 @@ void ProcessList::advanceObjects()
    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.
 //-----------------------------------------------------------------------------
 
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
+// Copyright (C) 2015 Faust Logic, Inc.
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
 #ifndef _PROCESSLIST_H_
 #define _PROCESSLIST_H_
 
@@ -188,6 +193,9 @@ protected:
 
    PreTickSignal mPreTick;
    PostTickSignal mPostTick;
+   // JTF: still needed?
+public:
+   ProcessObject* findNearestToEnd(Vector<ProcessObject*>& objs) const;
 };
 
 #endif // _PROCESSLIST_H_

+ 8 - 0
Engine/source/T3D/groundPlane.cpp

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

+ 7 - 1
Engine/source/T3D/lightBase.cpp

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

+ 7 - 0
Engine/source/T3D/lightBase.h

@@ -20,6 +20,11 @@
 // 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_
 #define _LIGHTBASE_H_
 
@@ -132,6 +137,8 @@ public:
    virtual void pauseAnimation( void );
    virtual void playAnimation( void );
    virtual void playAnimation( LightAnimData *animData );
+protected:
+   bool mLocalRenderViz;
 };
 
 #endif // _LIGHTBASE_H_

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

@@ -20,11 +20,19 @@
 // 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_
 #define _OBJECTTYPES_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)
 ///
 /// @note If a new object type is added, don't forget to add it to
@@ -149,6 +157,11 @@ enum SceneObjectTypes
 
    EntityObjectType = BIT(23),
    /// @}
+   InteriorLikeObjectType =  BIT(24),
+   TerrainLikeObjectType = BIT(25),
+#if defined(AFX_CAP_AFXMODEL_TYPE) 
+   afxModelObjectType = BIT(26)
+#endif 
 };
 
 enum SceneObjectTypeMasks : U32

+ 200 - 24
Engine/source/T3D/physicalZone.cpp

@@ -20,6 +20,11 @@
 // 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 "core/stream/bitStream.h"
 #include "collision/boxConvex.h"
@@ -33,6 +38,8 @@
 #include "gfx/gfxDrawUtil.h"
 #include "console/engineAPI.h"
 
+//#include "console/engineTypes.h"
+#include "sim/netConnection.h"
 IMPLEMENT_CO_NETOBJECT_V1(PhysicalZone);
 
 ConsoleDocClass( PhysicalZone,
@@ -103,6 +110,10 @@ PhysicalZone::PhysicalZone()
 
    mConvexList = new Convex;
    mActive = true;
+   force_type = VECTOR;
+   force_mag = 0.0f;
+   orient_force = false;
+   fade_amt = 1.0f;
 }
 
 PhysicalZone::~PhysicalZone()
@@ -111,6 +122,16 @@ PhysicalZone::~PhysicalZone()
    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()
 {
@@ -129,6 +150,10 @@ void PhysicalZone::initPersistFields()
       "point followed by three vectors representing the edges extending from the corner." );
    endGroup("Misc");
 
+   addGroup("AFX");
+   addField("forceType", TYPEID<PhysicalZone::ForceType>(), Offset(force_type, PhysicalZone));
+   addField("orientForce", TypeBool, Offset(orient_force, PhysicalZone));
+   endGroup("AFX");
    Parent::initPersistFields();
 }
 
@@ -158,6 +183,19 @@ bool PhysicalZone::onAdd()
    Polyhedron temp = mPolyhedron;
    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();
 
    return true;
@@ -191,7 +229,7 @@ void PhysicalZone::setTransform(const MatrixF & mat)
       mClippedList.setBaseTransform(base);
 
    if (isServerObject())
-      setMaskBits(InitialUpdateMask);
+      setMaskBits(MoveMask);
 }
 
 
@@ -242,12 +280,8 @@ U32 PhysicalZone::packUpdate(NetConnection* con, U32 mask, BitStream* stream)
    U32 i;
    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
       stream->write(mPolyhedron.pointList.size());
       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[1]);
       }
+   }
 
+   if (stream->writeFlag(mask & MoveMask))
+   {
+      stream->writeAffineTransform(mObjToWorld);
+      mathWrite(*stream, mObjScale);
+   }
+
+   if (stream->writeFlag(mask & SettingsMask))
+   {
       stream->write(mVelocityMod);
       stream->write(mGravityMod);
       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)
 {
    Parent::unpackUpdate(con, stream);
 
-   if (stream->readFlag()) {
+   bool new_ph = false;
+   if (stream->readFlag()) // PolyhedronMask
+   {
       U32 i, size;
-      MatrixF temp;
-      Point3F tempScale;
       Polyhedron tempPH;
 
-      // Transform
-      mathRead(*stream, &temp);
-      mathRead(*stream, &tempScale);
-
       // Read the polyhedron
       stream->read(&size);
       tempPH.pointList.setSize(size);
@@ -314,17 +360,46 @@ void PhysicalZone::unpackUpdate(NetConnection* con, BitStream* stream)
          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(&mGravityMod);
       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;
 }
 
+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.
 //-----------------------------------------------------------------------------
 
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
+// Copyright (C) 2015 Faust Logic, Inc.
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
 #ifndef _H_PHYSICALZONE
 #define _H_PHYSICALZONE
 
@@ -40,9 +45,14 @@ class PhysicalZone : public SceneObject
 {
    typedef SceneObject Parent;
 
-   enum UpdateMasks {      
+   enum UpdateMasks {
       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:
@@ -83,7 +93,10 @@ class PhysicalZone : public SceneObject
 
    inline F32 getVelocityMod() const      { return mVelocityMod; }
    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&);
    bool testObject(SceneObject*);
@@ -96,7 +109,25 @@ class PhysicalZone : public SceneObject
    void deactivate();
    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
 

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

@@ -20,6 +20,10 @@
 // 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 "T3D/player.h"
 
@@ -1656,6 +1660,8 @@ Player::Player()
    mLastAbsoluteYaw = 0.0f;
    mLastAbsolutePitch = 0.0f;
    mLastAbsoluteRoll = 0.0f;
+   
+   afx_init();
 }
 
 Player::~Player()
@@ -2070,6 +2076,32 @@ void Player::processTick(const Move* 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
    if (delta.warpTicks > 0) {
       delta.warpTicks--;
@@ -2085,7 +2117,10 @@ void Player::processTick(const Move* move)
       else if (delta.rot.z > M_PI_F)
          delta.rot.z -= M_2PI_F;
 
-      setPosition(delta.pos,delta.rot);
+      if (!ignore_updates)
+      {
+         setPosition(delta.pos,delta.rot);
+      }
       updateDeathOffsets();
       updateLookAnimation();
 
@@ -2183,7 +2218,8 @@ void Player::interpolateTick(F32 dt)
    Point3F pos = delta.pos + delta.posVec * 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
@@ -2208,6 +2244,9 @@ void Player::advanceTime(F32 dt)
 {
    // Client side animations
    Parent::advanceTime(dt);
+   // Increment timer for triggering idle events.
+   if (idle_timer >= 0.0f)
+      idle_timer += dt;
    updateActionThread();
    updateAnimation(dt);
    updateSplash();
@@ -2498,6 +2537,30 @@ AngAxisF gPlayerMoveRot;
 
 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;
 
 #ifdef TORQUE_OPENVR
@@ -2740,7 +2803,12 @@ void Player::updateMove(const Move* move)
    // Desired move direction & speed
    VectorF moveVec;
    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);
       moveVec *= (move->x * (mPose == SprintPose ? mDataBlock->sprintStrafeScale : 1.0f));
@@ -2799,6 +2867,9 @@ void Player::updateMove(const Move* move)
       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
    VectorF acc(0.0f, 0.0f, mGravity * mGravityMod * TickSec);
 
@@ -3025,7 +3096,9 @@ void Player::updateMove(const Move* move)
       mContactTimer++;   
 
    // 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
       F32 zSpeedScale = mVelocity.z;
@@ -3076,6 +3149,9 @@ void Player::updateMove(const Move* move)
          setActionThread( seq, true, false, true );
 
          mJumpSurfaceLastContact = JumpSkipContactsMax;
+         // Flag the jump event trigger.
+         fx_s_triggers |= PLAYER_JUMP_S_TRIGGER;
+         setMaskBits(TriggerMask);
       }
    }
    else
@@ -3493,6 +3569,19 @@ void Player::updateDamageState()
 
 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.
    Point3F renderHead = delta.head + delta.headVec * dt;
 
@@ -3533,6 +3622,8 @@ void Player::updateLookAnimation(F32 dt)
 
 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.action < mDataBlock->actionCount)
          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)
 {
+   if (anim_clip_flags & ANIM_OVERRIDDEN)
+      return false;
    for (U32 i = 1; i < mDataBlock->actionCount; 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;
    }
 
+   if (isClientObject())
+   {
+      mark_idle = (action == PlayerData::RootAnim);
+      idle_timer = (mark_idle) ? 0.0f : -1.0f;
+   }
    PlayerData::ActionAnimation &anim = mDataBlock->actionList[action];
    if (anim.sequence != -1)
    {
@@ -3858,7 +3956,8 @@ void Player::updateActionThread()
          offset = mDataBlock->decalOffset * getScale().x;
       }
 
-      if( triggeredLeft || triggeredRight )
+      process_client_triggers(triggeredLeft, triggeredRight);
+      if ((triggeredLeft || triggeredRight) && !noFootfallFX)
       {
          Point3F rot, pos;
          RayInfo rInfo;
@@ -3875,7 +3974,7 @@ void Player::updateActionThread()
             // Put footprints on surface, if appropriate for material.
 
             if( material && material->mShowFootprints
-                && mDataBlock->decalData )
+                && mDataBlock->decalData && !footfallDecalOverride )
             {
                Point3F normal;
                Point3F tangent;
@@ -3886,8 +3985,8 @@ void Player::updateActionThread()
             
             // 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
                ParticleEmitter * emitter = new ParticleEmitter;
@@ -3920,6 +4019,7 @@ void Player::updateActionThread()
 
             // Play footstep sound.
             
+            if (footfallSoundOverride <= 0)
             playFootstepSound( triggeredLeft, material, rInfo.object );
          }
       }
@@ -3941,8 +4041,10 @@ void Player::updateActionThread()
       pickActionAnimation();
    }
 
+   // prevent scaling of AFX picked actions
    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
       PlayerData::ActionAnimation &anim =
@@ -4560,6 +4662,10 @@ void Player::updateAnimation(F32 dt)
    if (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
    // to make sure the transforms are up to date as they are used
    // to setup the camera.
@@ -4573,6 +4679,11 @@ void Player::updateAnimation(F32 dt)
       else
       {
          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
          // and query 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
          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)
 {
-   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;
 
    // 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(&rot.z);
    rot.x = rot.y = 0;
-   setPosition(pos,rot);
+   if (!ignore_updates)
+      setPosition(pos,rot);
    delta.head = mHead;
    delta.rot = rot;
 
@@ -6188,6 +6310,7 @@ U32 Player::packUpdate(NetConnection *con, U32 mask, BitStream *stream)
          mArmAnimation.action != mDataBlock->lookAction))) {
       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.
    // 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;
    }
 
+   afx_unpackUpdate(con, stream);
    // Done if controlled by client ( and not initial update )
    if(stream->readFlag())
       return;
@@ -6391,7 +6515,8 @@ void Player::unpackUpdate(NetConnection *con, BitStream *stream)
             }
             delta.pos = pos;
             delta.rot = rot;
-            setPosition(pos,rot);
+            if (!ignore_updates)
+               setPosition(pos,rot);
          }
       }
       else 
@@ -6403,7 +6528,8 @@ void Player::unpackUpdate(NetConnection *con, BitStream *stream)
          delta.rotVec.set(0.0f, 0.0f, 0.0f);
          delta.warpTicks = 0;
          delta.dt = 0.0f;
-         setPosition(pos,rot);
+         if (!ignore_updates)
+            setPosition(pos,rot);
       }
    }
    F32 energy = stream->readFloat(EnergyLevelBits) * mDataBlock->maxEnergy;
@@ -6819,6 +6945,7 @@ void Player::consoleInit()
    Con::addVariable("$player::extendedMoveHeadPosRotIndex", TypeS32, &smExtendedMoveHeadPosRotIndex, 
       "@brief The ExtendedMove position/rotation index used for head movements.\n\n"
       "@ingroup GameObjects\n");
+   afx_consoleInit();
 }
 
 //--------------------------------------------------------------------------
@@ -6863,6 +6990,8 @@ void Player::calcClassRenderData()
 
 void Player::playFootstepSound( bool triggeredLeft, Material* contactMaterial, SceneObject* contactObject )
 {
+   if (footfallSoundOverride > 0)
+      return;
    MatrixF footMat = getTransform();
    if( mWaterCoverage > 0.0 )
    {
@@ -7172,6 +7301,340 @@ void Player::renderConvex( ObjectRenderInst *ri, SceneRenderState *state, BaseMa
    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
 void Player::setControllers(Vector<OpenVRTrackedObject*> controllerList)
 {

+ 90 - 1
Engine/source/T3D/player.h

@@ -20,6 +20,11 @@
 // 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_
 #define _PLAYER_H_
 
@@ -401,7 +406,8 @@ protected:
       ActionMask   = Parent::NextFreeMask << 0,
       MoveMask     = Parent::NextFreeMask << 1,
       ImpactMask   = Parent::NextFreeMask << 2,
-      NextFreeMask = Parent::NextFreeMask << 3
+      TriggerMask      = Parent::NextFreeMask << 3,
+      NextFreeMask     = Parent::NextFreeMask << 4
    };
 
    SimObjectPtr<ParticleEmitter> mSplashEmitter[PlayerData::NUM_SPLASH_EMITTERS];
@@ -780,6 +786,89 @@ public:
    virtual void prepRenderImage( SceneRenderState* state );
    virtual void renderConvex( ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance *overrideMat );   
    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;

+ 62 - 3
Engine/source/T3D/projectile.cpp

@@ -20,6 +20,11 @@
 // 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 "T3D/projectile.h"
 
@@ -190,6 +195,40 @@ ProjectileData::ProjectileData()
    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()
@@ -274,6 +313,13 @@ void ProjectileData::initPersistFields()
       "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"
       "@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();
 }
@@ -574,6 +620,11 @@ Projectile::Projectile()
 
    mLightState.clear();
    mLightState.setLightInfo( mLight );
+
+   mDataBlock = 0;
+   ignoreSourceTimeout = false;
+   dynamicCollisionMask = csmDynamicCollisionMask;
+   staticCollisionMask = csmStaticCollisionMask;
 }
 
 Projectile::~Projectile()
@@ -582,6 +633,11 @@ Projectile::~Projectile()
 
    delete mProjectileShape;
    mProjectileShape = NULL;
+   if (mDataBlock && mDataBlock->isTempClone())
+   {
+      delete mDataBlock;
+      mDataBlock = 0;
+   }
 }
 
 //--------------------------------------------------------------------------
@@ -609,6 +665,7 @@ void Projectile::initPersistFields()
    addField("sourceSlot",       TypeS32,     Offset(mSourceObjectSlot, Projectile),
       "@brief The sourceObject's weapon slot that the projectile originates from.\n\n");
 
+   addField("ignoreSourceTimeout",  TypeBool,   Offset(ignoreSourceTimeout, Projectile));
    endGroup("Source");
 
 
@@ -1088,7 +1145,7 @@ void Projectile::simulate( F32 dt )
    // disable the source objects collision reponse for a short time while we
    // determine if the projectile is capable of moving from the old position
    // to the new position, otherwise we'll hit ourself
-   bool disableSourceObjCollision = (mSourceObject.isValid() && mCurrTick <= SourceIdTimeoutTicks);
+   bool disableSourceObjCollision = (mSourceObject.isValid() && (ignoreSourceTimeout || mCurrTick <= SourceIdTimeoutTicks));
    if ( disableSourceObjCollision )
       mSourceObject->disableCollision();
    disableCollision();
@@ -1105,12 +1162,12 @@ void Projectile::simulate( F32 dt )
    if ( mPhysicsWorld )
       hit = mPhysicsWorld->castRay( oldPosition, newPosition, &rInfo, Point3F( newPosition - oldPosition) * mDataBlock->impactForce );            
    else 
-      hit = getContainer()->castRay(oldPosition, newPosition, csmDynamicCollisionMask | csmStaticCollisionMask, &rInfo);
+      hit = getContainer()->castRay(oldPosition, newPosition, dynamicCollisionMask | staticCollisionMask, &rInfo);
 
    if ( hit )
    {
       // make sure the client knows to bounce
-      if ( isServerObject() && ( rInfo.object->getTypeMask() & csmStaticCollisionMask ) == 0 )
+      if(isServerObject() && (rInfo.object->getTypeMask() & staticCollisionMask) == 0)
          setMaskBits( BounceMask );
 
       MatrixF xform( true );
@@ -1301,6 +1358,7 @@ U32 Projectile::packUpdate( NetConnection *con, U32 mask, BitStream *stream )
             stream->writeRangedU32( U32(mSourceObjectSlot),
                                     0, 
                                     ShapeBase::MaxMountedImages - 1 );
+            stream->writeFlag(ignoreSourceTimeout);
          }
          else 
             // 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 );
          mSourceObjectSlot = stream->readRangedU32( 0, ShapeBase::MaxMountedImages - 1 );
 
+         ignoreSourceTimeout = stream->readFlag();
          NetObject* pObject = con->resolveGhost( mSourceObjectId );
          if ( pObject != NULL )
             mSourceObject = dynamic_cast<ShapeBase*>( pObject );

+ 12 - 0
Engine/source/T3D/projectile.h

@@ -20,6 +20,11 @@
 // 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_
 #define _PROJECTILE_H_
 
@@ -144,6 +149,9 @@ public:
    
    DECLARE_CALLBACK( void, onExplode, ( Projectile* proj, Point3F pos, F32 fade ) );
    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 mExplosionNormal;
    U32     mCollideHitType;   
+public:
+   bool   ignoreSourceTimeout;
+   U32    dynamicCollisionMask;
+   U32    staticCollisionMask;
 };
 
 #endif // _PROJECTILE_H_

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

@@ -20,6 +20,11 @@
 // 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 "T3D/shapeBase.h"
 
@@ -194,6 +199,69 @@ ShapeBaseData::ShapeBaseData()
    inheritEnergyFromMount( false )
 {      
    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
@@ -228,6 +296,8 @@ static ShapeBaseDataProto gShapeBaseDataProto;
 ShapeBaseData::~ShapeBaseData()
 {
 
+   if (remap_buffer && !isTempClone())
+      dFree(remap_buffer);
 }
 
 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 (!silent_bbox_check)
                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;
             }
             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());
                collisionBounds.last() = mShape->bounds;
             }
@@ -413,6 +485,29 @@ bool ShapeBaseData::preload(bool server, String &errorStr)
       F32 w = mShape->bounds.len_y() / 2;
       if (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)
@@ -586,6 +681,12 @@ void ShapeBaseData::initPersistFields()
 
    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();
 }
 
@@ -738,6 +839,8 @@ void ShapeBaseData::packData(BitStream* stream)
    //stream->write(reflectMinDist);
    //stream->write(reflectMaxDist);
    //stream->write(reflectDetailAdjust);
+   stream->writeString(remap_txr_tags);
+   stream->writeFlag(silent_bbox_check);
 }
 
 void ShapeBaseData::unpackData(BitStream* stream)
@@ -839,6 +942,8 @@ void ShapeBaseData::unpackData(BitStream* stream)
    //stream->read(&reflectMinDist);
    //stream->read(&reflectMaxDist);
    //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++)
       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 )
 {
+   // 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 );
 
    bool isInitialDataBlock = ( mDataBlock == 0 );
@@ -1098,10 +1220,61 @@ bool ShapeBase::onNewDataBlock( GameBaseData *dptr, bool reload )
    // a shape assigned to this object.
    if (bool(mDataBlock->mShape)) {
       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());
       if (isClientObject())
+      {
          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;
       resetWorldBox();
 
@@ -3550,6 +3723,31 @@ void ShapeBase::setCurrentWaterObject( WaterObject *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 ),,
@@ -4945,3 +5143,202 @@ DefineEngineMethod( ShapeBase, getModelFile, const char *, (),,
    const char *fieldName = StringTable->insert( String("shapeFile") );
    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.
 //-----------------------------------------------------------------------------
 
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
+// Copyright (C) 2015 Faust Logic, Inc.
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
 #ifndef _SHAPEBASE_H_
 #define _SHAPEBASE_H_
 
@@ -654,6 +658,17 @@ public:
    DECLARE_CALLBACK(void, onEndSequence, (ShapeBase* obj, S32 slot, const char* name));
    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:
    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.
 //-----------------------------------------------------------------------------
 
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
+// Copyright (C) 2015 Faust Logic, Inc.
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
 #include "platform/platform.h"
 #include "core/dnet.h"
 #include "core/stream/bitStream.h"
@@ -97,6 +101,14 @@ StaticShapeData::StaticShapeData()
    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()
 {
    addField("noIndividualDamage",   TypeBool, Offset(noIndividualDamage,   StaticShapeData), "Deprecated\n\n @internal");

+ 9 - 0
Engine/source/T3D/staticShape.h

@@ -20,6 +20,11 @@
 // 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_
 #define _STATICSHAPE_H_
 
@@ -38,12 +43,16 @@ struct StaticShapeData: public ShapeBaseData {
    bool  noIndividualDamage;
    S32   dynamicTypeField;
    bool  isShielded;
+   F32   energyPerDamagePoint;	// Re-added for AFX
 
    //
    DECLARE_CONOBJECT(StaticShapeData);
    static void initPersistFields();
    virtual void packData(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.
 //-----------------------------------------------------------------------------
 
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
+// Copyright (C) 2015 Faust Logic, Inc.
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
 #include "platform/platform.h"
 #include "T3D/tsStatic.h"
 
@@ -54,6 +59,8 @@ using namespace Torque;
 
 extern bool gEditingMission;
 
+#include "afx/ce/afxZodiacMgr.h"
+
 IMPLEMENT_CO_NETOBJECT_V1(TSStatic);
 
 ConsoleDocClass( TSStatic,
@@ -124,6 +131,12 @@ TSStatic::TSStatic()
 
    mCollisionType = CollisionMesh;
    mDecalType = CollisionMesh;
+
+   mIgnoreZodiacs = false;
+   mHasGradients = false;
+   mInvertGradientRange = false;
+   mGradientRangeUser.set(0.0f, 180.0f);
+   afxZodiacData::convertGradientRangeFromDegrees(mGradientRange, mGradientRangeUser);
 }
 
 TSStatic::~TSStatic()
@@ -222,6 +235,12 @@ void TSStatic::initPersistFields()
 
    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();
 }
 
@@ -323,6 +342,8 @@ bool TSStatic::_createShape()
 {
    // Cleanup before we create.
    mCollisionDetails.clear();
+   mDecalDetails.clear();
+   mDecalDetailsPtr = 0;
    mLOSDetails.clear();
    SAFE_DELETE( mPhysicsRep );
    SAFE_DELETE( mShapeInstance );
@@ -354,6 +375,8 @@ bool TSStatic::_createShape()
 
    mShapeInstance = new TSShapeInstance( mShape, isClientObject() );
 
+   if (isClientObject())
+      mShapeInstance->cloneMaterialList();
    if( isGhost() )
    {
       // Reapply the current skin
@@ -396,11 +419,29 @@ void TSStatic::prepCollision()
 
    // Cleanup any old collision data
    mCollisionDetails.clear();
+   mDecalDetails.clear();
+   mDecalDetailsPtr = 0;
    mLOSDetails.clear();
    mConvexList->nukeList();
 
    if ( mCollisionType == CollisionMesh || mCollisionType == VisibleMesh )
+   {
       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();
 }
@@ -681,6 +722,8 @@ void TSStatic::prepRenderImage( SceneRenderState* state )
    }
    mShapeInstance->render( rdata );
 
+   if (!mIgnoreZodiacs && mDecalDetailsPtr != 0)
+      afxZodiacMgr::renderPolysoupZodiacs(state, this);
    if ( mRenderNormalScalar > 0 )
    {
       ObjectRenderInst *ri = state->getRenderPass()->allocInst<ObjectRenderInst>();
@@ -786,6 +829,13 @@ U32 TSStatic::packUpdate(NetConnection *con, U32 mask, BitStream *stream)
       stream->write(mInvertAlphaFade);  
    } 
 
+   stream->writeFlag(mIgnoreZodiacs);
+   if (stream->writeFlag(mHasGradients))
+   {
+      stream->writeFlag(mInvertGradientRange);
+      stream->write(mGradientRange.x);
+      stream->write(mGradientRange.y);
+   }
    if ( mLightPlugin )
       retMask |= mLightPlugin->packUpdate(this, AdvancedStaticOptionsMask, con, mask, stream);
 
@@ -870,6 +920,14 @@ void TSStatic::unpackUpdate(NetConnection *con, BitStream *stream)
       stream->read(&mInvertAlphaFade);  
    }
 
+   mIgnoreZodiacs = stream->readFlag();
+   mHasGradients = stream->readFlag();
+   if (mHasGradients)
+   {
+      mInvertGradientRange = stream->readFlag();
+      stream->read(&mGradientRange.x);
+      stream->read(&mGradientRange.y);
+   }
    if ( mLightPlugin )
    {
       mLightPlugin->unpackUpdate(this, con, stream);
@@ -882,6 +940,7 @@ void TSStatic::unpackUpdate(NetConnection *con, BitStream *stream)
 
    if ( isProperlyAdded() )
       _updateShouldTick();
+   set_special_typing();
 }
 
 //----------------------------------------------------------------------------
@@ -992,6 +1051,11 @@ bool TSStatic::buildPolyList(PolyListContext context, AbstractPolyList* polyList
          polyList->addBox( mObjBox );
       else if ( meshType == VisibleMesh )
           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
       {
          // Everything else is done from the collision meshes
@@ -1324,3 +1388,41 @@ DefineEngineMethod( TSStatic, getModelFile, const char *, (),,
 {
    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.
 //-----------------------------------------------------------------------------
 
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
+// Copyright (C) 2015 Faust Logic, Inc.
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
 #ifndef _TSSTATIC_H_
 #define _TSSTATIC_H_
 
@@ -236,6 +241,20 @@ public:
 
    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;

+ 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
 {
    typedef TurretShape Parent;
 
 protected:
 
+#ifdef AFX_REUSE_TURRETSHAPE_MASKBITS
+   enum MaskBits {
+      TurretStateMask   = Parent::TurretUpdateMask,
+      NextFreeMask      = Parent::NextFreeMask
+   };
+#else // ORIGINAL CODE
    enum MaskBits {
       TurretStateMask   = Parent::NextFreeMask,
       NextFreeMask      = Parent::NextFreeMask << 1
    };
+#endif
 
    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_

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