//-----------------------------------------------------------------------------
// 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
      dsize_t tokCopyLen = dStrlen(wiggle_axis_string) + 1;
      char* tokCopy = new char[tokCopyLen];
      dStrcpy(tokCopy, wiggle_axis_string, tokCopyLen);

      // 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 sfxErrorStr;
      if( !sfxResolve( &sound, sfxErrorStr) )
         Con::errorf(ConsoleLogEntry::General, "afxMagicMissileData::preload: Invalid packet: %s", sfxErrorStr.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->mBounds;
   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)->mTarget;
    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)->mCaster;
    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->mCaster;
      collide_exempt = spell->mTarget;
    }
    else
    {
      missile_target = spell->mTarget;
      collide_exempt = spell->mCaster;
    }

    if (spell->mCaster)
      processAfter(spell->mCaster);
    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);
}

//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
