Torque3D Documentation / _generateds / afxMagicMissile.cpp

afxMagicMissile.cpp

Engine/source/afx/afxMagicMissile.cpp

More...

Classes:

Public Defines

define
myOffset(field) (field, )

Public Functions

ConsoleDocClass(afxMagicMissile , "@brief Magic-missile class used internally by afxMagicSpell. Properties of individual missile types are defined using <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">afxMagicMissileData.\n</a>" "@ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">AFX\n</a>" )
ConsoleDocClass(afxMagicMissileData , "@brief Defines <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> particular magic-missile type. (Use with afxMagicSpellData.)\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "@<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">tsexample\n</a>" "datablock <a href="/coding/class/classafxmagicmissiledata/">afxMagicMissileData</a>(Fireball_MM)\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "{\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "  muzzleVelocity = 50;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "  velInheritFactor = 0;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "  lifetime = 20000;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "  isBallistic = true;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "  ballisticCoefficient = 0.85;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "  gravityMod = 0.05;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "  isGuided = true;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "  precision = 30;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "  trackDelay = 7;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "  launchOffset = \"0 0 43.7965\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "  launchOnServerSignal = true;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "};\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "@<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">endtsexample\n</a>" "@ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">AFX\n</a>" )
DefineEngineMethod(afxMagicMissile , setStartingVelocity , void , (float velocity) , "Set the starting velocity <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> magic-<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">missile.\n\n</a>" "@ingroup AFX" )
DefineEngineMethod(afxMagicMissile , setStartingVelocityVector , void , (Point3F velocityVec) , "Set the starting velocity-vector <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> magic-<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">missile.\n\n</a>" "@ingroup AFX" )
bool
readVector(Vector< T > & vec, Stream & stream, const char * msg)
bool
writeVector(const Vector< T > & vec, Stream & stream, const char * msg)

Detailed Description

Public Defines

myOffset(field) (field, )

Public Variables

FRangeValidator missileBallisticCoefficientValidator (0, 1)
FRangeValidator missileTrackDelayValidator (0, 100000)
FRangeValidator muzzleVelocityValidator (0, 10000)

Public Functions

ConsoleDocClass(afxMagicMissile , "@brief Magic-missile class used internally by afxMagicSpell. Properties of individual missile types are defined using <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">afxMagicMissileData.\n</a>" "@ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">AFX\n</a>" )

ConsoleDocClass(afxMagicMissileData , "@brief Defines <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> particular magic-missile type. (Use with afxMagicSpellData.)\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "@<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">tsexample\n</a>" "datablock <a href="/coding/class/classafxmagicmissiledata/">afxMagicMissileData</a>(Fireball_MM)\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "{\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "  muzzleVelocity = 50;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "  velInheritFactor = 0;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "  lifetime = 20000;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "  isBallistic = true;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "  ballisticCoefficient = 0.85;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "  gravityMod = 0.05;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "  isGuided = true;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "  precision = 30;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "  trackDelay = 7;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "  launchOffset = \"0 0 43.7965\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "  launchOnServerSignal = true;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "};\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "@<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">endtsexample\n</a>" "@ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">AFX\n</a>" )

DefineEngineMethod(afxMagicMissile , setStartingVelocity , void , (float velocity) , "Set the starting velocity <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> magic-<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">missile.\n\n</a>" "@ingroup AFX" )

DefineEngineMethod(afxMagicMissile , setStartingVelocityVector , void , (Point3F velocityVec) , "Set the starting velocity-vector <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> magic-<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">missile.\n\n</a>" "@ingroup AFX" )

IMPLEMENT_CO_DATABLOCK_V1(afxMagicMissileData )

IMPLEMENT_CO_NETOBJECT_V1(afxMagicMissile )

missilePrecisionValidator(0. f, 100. f)

readVector(Vector< T > & vec, Stream & stream, const char * msg)

writeVector(const Vector< T > & vec, Stream & stream, const char * msg)

   1
   2//-----------------------------------------------------------------------------
   3// Copyright (c) 2012 GarageGames, LLC
   4//
   5// Permission is hereby granted, free of charge, to any person obtaining a copy
   6// of this software and associated documentation files (the "Software"), to
   7// deal in the Software without restriction, including without limitation the
   8// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
   9// sell copies of the Software, and to permit persons to whom the Software is
  10// furnished to do so, subject to the following conditions:
  11//
  12// The above copyright notice and this permission notice shall be included in
  13// all copies or substantial portions of the Software.
  14//
  15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  20// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
  21// IN THE SOFTWARE.
  22//-----------------------------------------------------------------------------
  23
  24//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
  25// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
  26// Copyright (C) 2015 Faust Logic, Inc.
  27//
  28// afxMagicMissile is a heavily modified variation of the stock Projectile class. In 
  29// addition to numerous AFX customizations, it also incorporates functionality based on
  30// the following TGE resources:
  31//
  32// Guided or Seeker Projectiles by Derk Adams
  33//   http://www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=6778
  34//
  35// Projectile Ballistic Coefficients (drag factors) by Mark Owen
  36//   http://www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=5128
  37//
  38//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
  39
  40#include "afx/arcaneFX.h"
  41
  42#include "scene/sceneRenderState.h"
  43#include "scene/sceneManager.h"
  44#include "core/resourceManager.h"
  45#include "ts/tsShapeInstance.h"
  46#include "sfx/sfxTrack.h"
  47#include "sfx/sfxSource.h"
  48#include "sfx/sfxSystem.h"
  49#include "sfx/sfxTypes.h"
  50#include "math/mathUtils.h"
  51#include "math/mathIO.h"
  52#include "sim/netConnection.h"
  53#include "T3D/fx/particleEmitter.h"
  54#include "T3D/fx/splash.h"
  55#include "T3D/physics/physicsPlugin.h"
  56#include "T3D/physics/physicsWorld.h"
  57#include "gfx/gfxTransformSaver.h"
  58#include "T3D/containerQuery.h"
  59#include "T3D/lightDescription.h"
  60#include "console/engineAPI.h"
  61#include "lighting/lightManager.h"
  62
  63#include "afx/util/afxEase.h"
  64#include "afx/afxMagicMissile.h"
  65#include "afx/afxMagicSpell.h"
  66#include "afx/afxChoreographer.h"
  67
  68class ObjectDeleteEvent : public SimEvent
  69{
  70public:
  71  void process(SimObject *object)
  72  {
  73    object->deleteObject();
  74  }
  75};
  76
  77IMPLEMENT_CO_DATABLOCK_V1(afxMagicMissileData);
  78
  79ConsoleDocClass( afxMagicMissileData,
  80   "@brief Defines a particular magic-missile type. (Use with afxMagicSpellData.)\n"
  81   "@tsexample\n"
  82   "datablock afxMagicMissileData(Fireball_MM)\n"
  83   "{\n"
  84   "  muzzleVelocity = 50;\n"
  85   "  velInheritFactor = 0;\n"
  86   "  lifetime = 20000;\n"
  87   "  isBallistic = true;\n"
  88   "  ballisticCoefficient = 0.85;\n"
  89   "  gravityMod = 0.05;\n"
  90   "  isGuided = true;\n"
  91   "  precision = 30;\n"
  92   "  trackDelay = 7;\n"
  93   "  launchOffset = \"0 0 43.7965\";\n"
  94   "  launchOnServerSignal = true;\n"
  95   "};\n"
  96   "@endtsexample\n"
  97   "@ingroup AFX\n"
  98);
  99
 100IMPLEMENT_CO_NETOBJECT_V1(afxMagicMissile);
 101
 102ConsoleDocClass( afxMagicMissile,
 103   "@brief Magic-missile class used internally by afxMagicSpell. Properties of individual missile types are defined using afxMagicMissileData.\n"
 104   "@ingroup AFX\n"
 105);
 106
 107/* From stock Projectile code...
 108IMPLEMENT_CALLBACK( ProjectileData, onExplode, void, ( Projectile* proj, Point3F pos, F32 fade ), 
 109                   ( proj, pos, fade ),
 110               "Called when a projectile explodes.\n"
 111               "@param proj The projectile exploding.\n"
 112               "@param pos The position of the explosion.\n"
 113               "@param fade The currently fadeValue of the projectile, affects its visibility.\n"
 114               "@see Projectile, ProjectileData\n"
 115              );
 116
 117IMPLEMENT_CALLBACK( ProjectileData, onCollision, void, ( Projectile* proj, SceneObject* col, F32 fade, Point3F pos, Point3F normal ),
 118                   ( proj, col, fade, pos, normal ),
 119               "Called when a projectile collides with another object.\n"
 120               "@param proj The projectile colliding.\n"
 121               "@param col The object hit by the projectile.\n"
 122               "@param fade The current fadeValue of the projectile, affects its visibility.\n"
 123               "@param pos The collision position.\n"
 124               "@param normal The collision normal.\n"
 125               "@see Projectile, ProjectileData\n"
 126              );
 127
 128const U32 Projectile::csmStaticCollisionMask =  TerrainObjectType    |
 129                                                InteriorObjectType   |
 130                                                StaticObjectType;
 131
 132const U32 Projectile::csmDynamicCollisionMask = PlayerObjectType        |
 133                                                VehicleObjectType       |
 134                                                DamagableItemObjectType;
 135
 136const U32 Projectile::csmDamageableMask = Projectile::csmDynamicCollisionMask;
 137
 138U32 Projectile::smProjectileWarpTicks = 5;
 139*/
 140
 141//--------------------------------------------------------------------------
 142//
 143afxMagicMissileData::afxMagicMissileData()
 144{
 145   projectileShapeName = ST_NULLSTRING;
 146
 147   sound = NULL;
 148
 149   /* From stock Projectile code...
 150   explosion = NULL;
 151   explosionId = 0;
 152
 153   waterExplosion = NULL;
 154   waterExplosionId = 0;
 155   */
 156
 157   /* From stock Projectile code...
 158   faceViewer = false;
 159   */
 160   scale.set( 1.0f, 1.0f, 1.0f );
 161
 162   isBallistic = false;
 163
 164   /* From stock Projectile code...
 165   velInheritFactor = 1.0f;
 166   */
 167   muzzleVelocity = 50;
 168   /* From stock Projectile code...
 169   impactForce = 0.0f;
 170
 171   armingDelay = 0;
 172
 173   activateSeq = -1;
 174   maintainSeq = -1;
 175   */
 176
 177   lifetime = 20000 / 32;
 178   fadeDelay = 20000 / 32;
 179
 180   gravityMod = 1.0;
 181   /* From stock Projectile code...
 182   bounceElasticity = 0.999f;
 183   bounceFriction = 0.3f;
 184   */
 185
 186   particleEmitter = NULL;
 187   particleEmitterId = 0;
 188
 189   particleWaterEmitter = NULL;
 190   particleWaterEmitterId = 0;
 191
 192   splash = NULL;
 193   splashId = 0;
 194
 195   /* From stock Projectile code...
 196   decal = NULL;
 197   decalId = 0;
 198   */
 199
 200   lightDesc = NULL;
 201   lightDescId = 0;
 202   
 203  starting_vel_vec.zero();
 204
 205  isGuided = false;
 206  precision = 0; 
 207  trackDelay = 0;
 208  ballisticCoefficient = 1.0f; 
 209
 210  followTerrain = false;
 211  followTerrainHeight = 0.1f;
 212  followTerrainAdjustRate = 20.0f;
 213  followTerrainAdjustDelay = 0;
 214
 215  lifetime = MaxLifetimeTicks;
 216  collision_mask = arcaneFX::sMissileCollisionMask;
 217
 218  acceleration = 0;
 219  accelDelay = 0;
 220  accelLifetime = 0;
 221
 222  launch_node = ST_NULLSTRING;
 223  launch_offset.zero();
 224  launch_offset_server.zero();
 225  launch_offset_client.zero();
 226  launch_node_offset.zero();
 227  launch_pitch = 0;
 228  launch_pan = 0;
 229  launch_cons_s_spec = ST_NULLSTRING;
 230  launch_cons_c_spec = ST_NULLSTRING;
 231
 232  echo_launch_offset = false;
 233
 234  wiggle_axis_string = ST_NULLSTRING;
 235  wiggle_num_axis = 0;
 236  wiggle_axis = 0;
 237
 238  hover_altitude = 0;
 239  hover_attack_distance = 0;
 240  hover_attack_gradient = 0;
 241  hover_time = 0;
 242
 243  reverse_targeting = false;
 244
 245  caster_safety_time = U32_MAX;
 246}
 247
 248afxMagicMissileData::afxMagicMissileData(const afxMagicMissileData& other, bool temp_clone) : GameBaseData(other, temp_clone)
 249{
 250  projectileShapeName = other.projectileShapeName;
 251  projectileShape = other.projectileShape; // -- TSShape loads using projectileShapeName
 252  sound = other.sound;
 253  splash = other.splash;
 254  splashId = other.splashId; // -- for pack/unpack of splash ptr
 255  lightDesc = other.lightDesc;
 256  lightDescId = other.lightDescId; // -- for pack/unpack of lightDesc ptr
 257  scale = other.scale;
 258  isBallistic = other.isBallistic;
 259  muzzleVelocity = other.muzzleVelocity;
 260  gravityMod = other.gravityMod;
 261  particleEmitter = other.particleEmitter;
 262  particleEmitterId = other.particleEmitterId; // -- for pack/unpack of particleEmitter ptr
 263  particleWaterEmitter = other.particleWaterEmitter;
 264  particleWaterEmitterId = other.particleWaterEmitterId; // -- for pack/unpack of particleWaterEmitter ptr
 265
 266  collision_mask = other.collision_mask;
 267  starting_vel_vec = other.starting_vel_vec;
 268  isGuided = other.isGuided;
 269  precision = other.precision;
 270  trackDelay = other.trackDelay;
 271  ballisticCoefficient = other.ballisticCoefficient;
 272  followTerrain  = other.followTerrain;
 273  followTerrainHeight = other.followTerrainHeight;
 274  followTerrainAdjustRate = other.followTerrainAdjustRate;
 275  followTerrainAdjustDelay = other.followTerrainAdjustDelay;
 276  lifetime = other.lifetime;
 277  fadeDelay = other.fadeDelay;
 278  acceleration = other.acceleration;
 279  accelDelay = other.accelDelay;
 280  accelLifetime = other.accelLifetime;
 281  launch_node  = other.launch_node;
 282  launch_offset = other.launch_offset;
 283  launch_offset_server = other.launch_offset_server;
 284  launch_offset_client = other.launch_offset_client;
 285  launch_node_offset = other.launch_node_offset;
 286  launch_pitch = other.launch_pitch;
 287  launch_pan = other.launch_pan;
 288  launch_cons_s_spec = other.launch_cons_s_spec;
 289  launch_cons_c_spec = other.launch_cons_c_spec;
 290  launch_cons_s_def = other.launch_cons_s_def;
 291  launch_cons_c_def = other.launch_cons_c_def;
 292  echo_launch_offset = other.echo_launch_offset;
 293  wiggle_magnitudes = other.wiggle_magnitudes;
 294  wiggle_speeds = other.wiggle_speeds;
 295  wiggle_axis_string = other.wiggle_axis_string;
 296  wiggle_num_axis = other.wiggle_num_axis;
 297  wiggle_axis = other.wiggle_axis;
 298  hover_altitude = other.hover_altitude;
 299  hover_attack_distance = other.hover_attack_distance;
 300  hover_attack_gradient = other.hover_attack_gradient;
 301  hover_time = other.hover_time;
 302  reverse_targeting = other.reverse_targeting;
 303  caster_safety_time = other.caster_safety_time;
 304}
 305
 306afxMagicMissileData::~afxMagicMissileData()
 307{
 308  if (wiggle_axis)
 309    delete [] wiggle_axis;
 310}
 311
 312afxMagicMissileData* afxMagicMissileData::cloneAndPerformSubstitutions(const SimObject* owner, S32 index)
 313{
 314  if (!owner || getSubstitutionCount() == 0)
 315    return this;
 316
 317  afxMagicMissileData* sub_missile_db = new afxMagicMissileData(*this, true);
 318  performSubstitutions(sub_missile_db, owner, index);
 319
 320  return sub_missile_db;
 321}
 322
 323//--------------------------------------------------------------------------
 324
 325#define myOffset(field) Offset(field, afxMagicMissileData)
 326
 327FRangeValidator muzzleVelocityValidator(0, 10000);
 328FRangeValidator missilePrecisionValidator(0.f, 100.f);
 329FRangeValidator missileTrackDelayValidator(0, 100000);
 330FRangeValidator missileBallisticCoefficientValidator(0, 1);
 331
 332void afxMagicMissileData::initPersistFields()
 333{
 334   static IRangeValidatorScaled ticksFromMS(TickMs, 0, MaxLifetimeTicks);
 335
 336   addField("particleEmitter", TYPEID<ParticleEmitterData>(), Offset(particleEmitter, afxMagicMissileData));
 337   addField("particleWaterEmitter", TYPEID<ParticleEmitterData>(), Offset(particleWaterEmitter, afxMagicMissileData));
 338
 339   addField("projectileShapeName", TypeFilename, Offset(projectileShapeName, afxMagicMissileData));
 340   addField("scale", TypePoint3F, Offset(scale, afxMagicMissileData));
 341
 342   addField("sound", TypeSFXTrackName, Offset(sound, afxMagicMissileData));
 343
 344   /* From stock Projectile code...
 345   addField("explosion", TYPEID< ExplosionData >(), Offset(explosion, ProjectileData));
 346   addField("waterExplosion", TYPEID< ExplosionData >(), Offset(waterExplosion, ProjectileData));
 347   */
 348
 349   addField("splash", TYPEID<SplashData>(), Offset(splash, afxMagicMissileData));
 350   /* From stock Projectile code...
 351   addField("decal", TYPEID< DecalData >(), Offset(decal, ProjectileData));
 352   */
 353
 354   addField("lightDesc", TYPEID< LightDescription >(), Offset(lightDesc, afxMagicMissileData));
 355
 356   addField("isBallistic", TypeBool,   Offset(isBallistic, afxMagicMissileData));
 357   /* From stock Projectile code...
 358   addField("velInheritFactor", TypeF32, Offset(velInheritFactor, ProjectileData));
 359   */
 360   addNamedFieldV(muzzleVelocity,    TypeF32,      afxMagicMissileData,  &muzzleVelocityValidator);
 361   /* From stock Projectile code...
 362   addField("impactForce", TypeF32, Offset(impactForce, ProjectileData));
 363   */
 364   addNamedFieldV(lifetime,    TypeS32,              afxMagicMissileData,  &ticksFromMS);
 365   /* From stock Projectile code...
 366   addProtectedField("armingDelay", TypeS32, Offset(armingDelay, ProjectileData), &setArmingDelay, &getScaledValue, 
 367      "The time in milliseconds before the projectile is armed and will cause damage or explode on impact." );
 368
 369   addProtectedField("fadeDelay", TypeS32, Offset(fadeDelay, ProjectileData), &setFadeDelay, &getScaledValue,
 370      "The time in milliseconds when the projectile begins to fade out.  Must be less than the lifetime to have an effect." );
 371
 372   addField("bounceElasticity", TypeF32, Offset(bounceElasticity, ProjectileData));
 373   addField("bounceFriction", TypeF32, Offset(bounceFriction, ProjectileData));
 374   */
 375   addField("gravityMod", TypeF32, Offset(gravityMod, afxMagicMissileData));
 376
 377   // FIELDS ADDED BY MAGIC-MISSILE
 378
 379   addField("missileShapeName",    TypeFilename, myOffset(projectileShapeName));
 380   addField("missileShapeScale",   TypePoint3F,  myOffset(scale));
 381
 382   addField("startingVelocityVector",TypePoint3F,  myOffset(starting_vel_vec));
 383
 384   addNamedField(isGuided,               TypeBool,   afxMagicMissileData);
 385   addNamedFieldV(precision,             TypeF32,    afxMagicMissileData,  &missilePrecisionValidator); 
 386   addNamedFieldV(trackDelay,            TypeS32,    afxMagicMissileData,  &missileTrackDelayValidator); 
 387   addNamedFieldV(ballisticCoefficient,  TypeF32,    afxMagicMissileData,  &missileBallisticCoefficientValidator);
 388
 389   addField("collisionMask",         TypeS32,      myOffset(collision_mask));
 390
 391   addField("followTerrain",             TypeBool, myOffset(followTerrain));
 392   addField("followTerrainHeight",       TypeF32,  myOffset(followTerrainHeight));
 393   addField("followTerrainAdjustRate",   TypeF32,  myOffset(followTerrainAdjustRate));
 394   addFieldV("followTerrainAdjustDelay", TypeS32,  myOffset(followTerrainAdjustDelay), &ticksFromMS); 
 395
 396   addNamedField(acceleration,     TypeF32,  afxMagicMissileData);
 397   addNamedFieldV(accelDelay,      TypeS32,  afxMagicMissileData,  &ticksFromMS);
 398   addNamedFieldV(accelLifetime,   TypeS32,  afxMagicMissileData,  &ticksFromMS);
 399
 400   addField("launchNode",        TypeString,   myOffset(launch_node));
 401   addField("launchOffset",      TypePoint3F,  myOffset(launch_offset));
 402   addField("launchOffsetServer",TypePoint3F,  myOffset(launch_offset_server));
 403   addField("launchOffsetClient",TypePoint3F,  myOffset(launch_offset_client));
 404   addField("launchNodeOffset",  TypePoint3F,  myOffset(launch_node_offset));
 405   addField("launchAimPitch",    TypeF32,      myOffset(launch_pitch));
 406   addField("launchAimPan",      TypeF32,      myOffset(launch_pan));
 407   addField("launchConstraintServer",  TypeString,   myOffset(launch_cons_s_spec));
 408   addField("launchConstraintClient",  TypeString,   myOffset(launch_cons_c_spec));
 409   //
 410   addField("echoLaunchOffset",  TypeBool,     myOffset(echo_launch_offset));
 411
 412   addField("wiggleMagnitudes", TypeF32Vector, myOffset(wiggle_magnitudes));
 413   addField("wiggleSpeeds",     TypeF32Vector, myOffset(wiggle_speeds));
 414   addField("wiggleAxis",       TypeString,    myOffset(wiggle_axis_string));
 415
 416   addField("hoverAltitude",       TypeF32,    myOffset(hover_altitude));
 417   addField("hoverAttackDistance", TypeF32,    myOffset(hover_attack_distance));
 418   addField("hoverAttackGradient", TypeF32,    myOffset(hover_attack_gradient));
 419   addFieldV("hoverTime",          TypeS32,    myOffset(hover_time), &ticksFromMS); 
 420
 421   addField("reverseTargeting",  TypeBool,     myOffset(reverse_targeting));
 422
 423   addFieldV("casterSafetyTime", TypeS32,      myOffset(caster_safety_time), &ticksFromMS); 
 424
 425   Parent::initPersistFields();
 426
 427   // disallow some field substitutions
 428   onlyKeepClearSubstitutions("particleEmitter"); // subs resolving to "~~", or "~0" are OK
 429   onlyKeepClearSubstitutions("particleWaterEmitter");
 430   onlyKeepClearSubstitutions("sound");
 431   onlyKeepClearSubstitutions("splash");
 432}
 433
 434
 435//--------------------------------------------------------------------------
 436bool afxMagicMissileData::onAdd()
 437{
 438   if(!Parent::onAdd())
 439      return false;
 440
 441   // ADDED BY MAGIC-MISSILE
 442
 443   // Wiggle axes ////////////////////////////////////////////////////////////  
 444   if (wiggle_axis_string != ST_NULLSTRING && wiggle_num_axis == 0)   
 445   {
 446      // Tokenize input string and convert to Point3F array
 447      //
 448      Vector<char*> dataBlocks(__FILE__, __LINE__);
 449
 450      // make a copy of points_string
 451      dsize_t tokCopyLen = dStrlen(wiggle_axis_string) + 1;
 452      char* tokCopy = new char[tokCopyLen];
 453      dStrcpy(tokCopy, wiggle_axis_string, tokCopyLen);
 454
 455      // extract tokens one by one, adding them to dataBlocks
 456      char* currTok = dStrtok(tokCopy, " \t");
 457      while (currTok != NULL) 
 458      {
 459         dataBlocks.push_back(currTok);
 460         currTok = dStrtok(NULL, " \t");
 461      }
 462
 463      // bail if there were no tokens in the string
 464      if (dataBlocks.size() == 0) 
 465      {
 466         Con::warnf(ConsoleLogEntry::General, "afxMagicMissileData(%s) invalid wiggle axis string. No tokens found", getName());
 467         delete [] tokCopy;
 468         return false;
 469      }
 470
 471      // Find wiggle_num_axis (round up to multiple of 3) // WARNING here if not multiple of 3?
 472      for (U32 i = 0; i < dataBlocks.size()%3; i++) 
 473      {
 474         dataBlocks.push_back("0.0");
 475      }
 476
 477      wiggle_num_axis = dataBlocks.size()/3;
 478      wiggle_axis = new Point3F[wiggle_num_axis];
 479
 480      U32 p_i = 0;
 481      for (U32 i = 0; i < dataBlocks.size(); i+=3, p_i++)
 482      {
 483         F32 x,y,z;
 484         x = dAtof(dataBlocks[i]);  // What about overflow?
 485         y = dAtof(dataBlocks[i+1]);
 486         z = dAtof(dataBlocks[i+2]);
 487         wiggle_axis[p_i].set(x,y,z);
 488
 489         wiggle_axis[p_i].normalizeSafe(); // sufficient????
 490      }
 491
 492      delete [] tokCopy; 
 493   }
 494
 495   launch_cons_s_def.parseSpec(launch_cons_s_spec, true, false);
 496   launch_cons_c_def.parseSpec(launch_cons_c_spec, false, true);
 497
 498   return true;
 499}
 500
 501
 502bool afxMagicMissileData::preload(bool server, String &errorStr)
 503{
 504   if (Parent::preload(server, errorStr) == false)
 505      return false;
 506      
 507   if( !server )
 508   {
 509      if (!particleEmitter && particleEmitterId != 0)
 510         if (Sim::findObject(particleEmitterId, particleEmitter) == false)
 511            Con::errorf(ConsoleLogEntry::General, "afxMagicMissileData::preload: Invalid packet, bad datablockId(particleEmitter): %d", particleEmitterId);
 512
 513      if (!particleWaterEmitter && particleWaterEmitterId != 0)
 514         if (Sim::findObject(particleWaterEmitterId, particleWaterEmitter) == false)
 515            Con::errorf(ConsoleLogEntry::General, "afxMagicMissileData::preload: Invalid packet, bad datablockId(particleWaterEmitter): %d", particleWaterEmitterId);
 516
 517      /* From stock Projectile code...
 518      if (!explosion && explosionId != 0)
 519         if (Sim::findObject(explosionId, explosion) == false)
 520            Con::errorf(ConsoleLogEntry::General, "ProjectileData::preload: Invalid packet, bad datablockId(explosion): %d", explosionId);
 521
 522      if (!waterExplosion && waterExplosionId != 0)
 523         if (Sim::findObject(waterExplosionId, waterExplosion) == false)
 524            Con::errorf(ConsoleLogEntry::General, "ProjectileData::preload: Invalid packet, bad datablockId(waterExplosion): %d", waterExplosionId);
 525      */
 526
 527      if (!splash && splashId != 0)
 528         if (Sim::findObject(splashId, splash) == false)
 529            Con::errorf(ConsoleLogEntry::General, "afxMagicMissileData::preload: Invalid packet, bad datablockId(splash): %d", splashId);
 530
 531      /* From stock Projectile code...
 532      if (!decal && decalId != 0)
 533         if (Sim::findObject(decalId, decal) == false)
 534            Con::errorf(ConsoleLogEntry::General, "ProjectileData::preload: Invalid packet, bad datablockId(decal): %d", decalId);
 535      */
 536
 537      String sfxErrorStr;
 538      if( !sfxResolve( &sound, sfxErrorStr) )
 539         Con::errorf(ConsoleLogEntry::General, "afxMagicMissileData::preload: Invalid packet: %s", sfxErrorStr.c_str());
 540
 541      if (!lightDesc && lightDescId != 0)
 542         if (Sim::findObject(lightDescId, lightDesc) == false)
 543            Con::errorf(ConsoleLogEntry::General, "afxMagicMissileData::preload: Invalid packet, bad datablockid(lightDesc): %d", lightDescId);   
 544   }
 545
 546   if (projectileShapeName != ST_NULLSTRING) 
 547   {
 548      projectileShape = ResourceManager::get().load(projectileShapeName);
 549      if (bool(projectileShape) == false)
 550      {
 551         errorStr = String::ToString("afxMagicMissileData::load: Couldn't load shape \"%s\"", projectileShapeName);
 552         return false;
 553      }
 554      /* From stock Projectile code...
 555      activateSeq = projectileShape->findSequence("activate");
 556      maintainSeq = projectileShape->findSequence("maintain");
 557      */
 558   }
 559
 560   if (bool(projectileShape)) // create an instance to preload shape data
 561   {
 562      TSShapeInstance* pDummy = new TSShapeInstance(projectileShape, !server);
 563      delete pDummy;
 564   }
 565
 566   return true;
 567}
 568
 569//--------------------------------------------------------------------------
 570// Modified from floorPlanRes.cc
 571// Read a vector of items
 572template <class T>
 573bool readVector(Vector<T> & vec, Stream & stream, const char * msg)
 574{
 575   U32   num, i;
 576   bool  Ok = true;
 577   stream.read( & num );
 578   vec.setSize( num );
 579   for( i = 0; i < num && Ok; i++ ){
 580      Ok = stream.read(& vec[i]);
 581      AssertISV( Ok, avar("math vec read error (%s) on elem %d", msg, i) );
 582   }
 583   return Ok;
 584}
 585// Write a vector of items
 586template <class T>
 587bool writeVector(const Vector<T> & vec, Stream & stream, const char * msg)
 588{
 589   bool  Ok = true;
 590   stream.write( vec.size() );
 591   for( U32 i = 0; i < vec.size() && Ok; i++ ) {
 592      Ok = stream.write(vec[i]);
 593      AssertISV( Ok, avar("vec write error (%s) on elem %d", msg, i) );
 594   }
 595   return Ok;
 596}
 597//--------------------------------------------------------------------------
 598
 599void afxMagicMissileData::packData(BitStream* stream)
 600{
 601   Parent::packData(stream);
 602
 603   stream->writeString(projectileShapeName);
 604   /* From stock Projectile code...
 605   stream->writeFlag(faceViewer);
 606   */
 607   if(stream->writeFlag(scale.x != 1 || scale.y != 1 || scale.z != 1))
 608   {
 609      stream->write(scale.x);
 610      stream->write(scale.y);
 611      stream->write(scale.z);
 612   }
 613
 614   stream->write(collision_mask);
 615
 616   if (stream->writeFlag(particleEmitter != NULL))
 617      stream->writeRangedU32(particleEmitter->getId(), DataBlockObjectIdFirst,
 618                                                   DataBlockObjectIdLast);
 619
 620   if (stream->writeFlag(particleWaterEmitter != NULL))
 621      stream->writeRangedU32(particleWaterEmitter->getId(), DataBlockObjectIdFirst,
 622                                                   DataBlockObjectIdLast);
 623
 624   /* From stock Projectile code...
 625   if (stream->writeFlag(explosion != NULL))
 626      stream->writeRangedU32(explosion->getId(), DataBlockObjectIdFirst,
 627                                                 DataBlockObjectIdLast);
 628
 629   if (stream->writeFlag(waterExplosion != NULL))
 630      stream->writeRangedU32(waterExplosion->getId(), DataBlockObjectIdFirst,
 631                                                      DataBlockObjectIdLast);
 632   */
 633
 634   if (stream->writeFlag(splash != NULL))
 635      stream->writeRangedU32(splash->getId(), DataBlockObjectIdFirst,
 636                                              DataBlockObjectIdLast);
 637
 638   /* From stock Projectile code...
 639   if (stream->writeFlag(decal != NULL))
 640      stream->writeRangedU32(decal->getId(), DataBlockObjectIdFirst,
 641                                              DataBlockObjectIdLast);
 642   */
 643
 644   sfxWrite( stream, sound );
 645
 646   if ( stream->writeFlag(lightDesc != NULL))
 647      stream->writeRangedU32(lightDesc->getId(), DataBlockObjectIdFirst,
 648                                                 DataBlockObjectIdLast);
 649
 650   /* From stock Projectile code...
 651   stream->write(impactForce);
 652   */
 653
 654   stream->write(lifetime);
 655   /* From stock Projectile code...
 656   stream->write(armingDelay);
 657   stream->write(fadeDelay);
 658   */
 659
 660   if(stream->writeFlag(isBallistic))
 661   {
 662      stream->write(gravityMod);
 663      /* From stock Projectile code...
 664      stream->write(bounceElasticity);
 665      stream->write(bounceFriction);
 666      */
 667      stream->write(ballisticCoefficient);
 668   }
 669
 670   if(stream->writeFlag(isGuided))
 671   {
 672      stream->write(precision);
 673      stream->write(trackDelay);
 674   }
 675
 676   stream->write(muzzleVelocity);
 677   mathWrite(*stream, starting_vel_vec);
 678   stream->write(acceleration);
 679   stream->write(accelDelay);
 680   stream->write(accelLifetime);
 681
 682   stream->writeString(launch_node);
 683   mathWrite(*stream, launch_offset);
 684   mathWrite(*stream, launch_offset_server);
 685   mathWrite(*stream, launch_offset_client);
 686   mathWrite(*stream, launch_node_offset);
 687   stream->write(launch_pitch);
 688   stream->write(launch_pan);
 689   stream->writeString(launch_cons_c_spec);
 690   stream->writeFlag(echo_launch_offset);
 691
 692   writeVector(wiggle_magnitudes, *stream, "afxMagicMissile: wiggle_magnitudes");
 693   writeVector(wiggle_speeds, *stream, "afxMagicMissile: wiggle_speeds");
 694
 695   stream->write(wiggle_num_axis);
 696   for (U32 i = 0; i < wiggle_num_axis; i++)
 697      mathWrite(*stream, wiggle_axis[i]);
 698
 699   stream->write(hover_altitude);
 700   stream->write(hover_attack_distance);
 701   stream->write(hover_attack_gradient);
 702   stream->writeRangedU32(hover_time, 0, MaxLifetimeTicks);
 703
 704   stream->writeFlag(reverse_targeting);
 705
 706   stream->write(caster_safety_time);
 707}
 708
 709void afxMagicMissileData::unpackData(BitStream* stream)
 710{
 711   Parent::unpackData(stream);
 712
 713   projectileShapeName = stream->readSTString();
 714   /* From stock Projectile code...
 715   faceViewer = stream->readFlag();
 716   */
 717   if(stream->readFlag())
 718   {
 719      stream->read(&scale.x);
 720      stream->read(&scale.y);
 721      stream->read(&scale.z);
 722   }
 723   else
 724      scale.set(1,1,1);
 725
 726   stream->read(&collision_mask);
 727
 728   if (stream->readFlag())
 729      particleEmitterId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast);
 730
 731   if (stream->readFlag())
 732      particleWaterEmitterId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast);
 733
 734   /* From stock Projectile code...
 735   if (stream->readFlag())
 736      explosionId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast);
 737
 738   if (stream->readFlag())
 739      waterExplosionId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast);
 740   */
 741   
 742   if (stream->readFlag())
 743      splashId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast);
 744
 745   /* From stock Projectile code...
 746   if (stream->readFlag())
 747      decalId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast);
 748   */
 749   
 750   sfxRead( stream, &sound );
 751
 752   if (stream->readFlag())
 753      lightDescId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast);
 754
 755   /* From stock Projectile code...
 756   stream->read(&impactForce);
 757   */
 758
 759   stream->read(&lifetime);
 760   /* From stock Projectile code...
 761   stream->read(&armingDelay);
 762   stream->read(&fadeDelay);
 763   */
 764
 765   isBallistic = stream->readFlag();
 766   if(isBallistic)
 767   {
 768      stream->read(&gravityMod);
 769      /* From stock Projectile code...
 770      stream->read(&bounceElasticity);
 771      stream->read(&bounceFriction);
 772      */
 773      stream->read(&ballisticCoefficient);
 774   }
 775
 776   isGuided = stream->readFlag();
 777   if(isGuided)
 778   {
 779      stream->read(&precision);
 780      stream->read(&trackDelay);
 781   }
 782
 783   stream->read(&muzzleVelocity);
 784   mathRead(*stream, &starting_vel_vec);
 785   stream->read(&acceleration);
 786   stream->read(&accelDelay);
 787   stream->read(&accelLifetime);
 788
 789   launch_node = stream->readSTString();
 790   mathRead(*stream, &launch_offset);
 791   mathRead(*stream, &launch_offset_server);
 792   mathRead(*stream, &launch_offset_client);
 793   mathRead(*stream, &launch_node_offset);
 794   stream->read(&launch_pitch);
 795   stream->read(&launch_pan);
 796   launch_cons_c_spec = stream->readSTString();
 797   echo_launch_offset = stream->readFlag();
 798
 799   readVector(wiggle_magnitudes, *stream, "afxMagicMissile: wiggle_magnitudes");
 800   readVector(wiggle_speeds, *stream, "afxMagicMissile: wiggle_speeds");
 801
 802   if (wiggle_axis)
 803      delete [] wiggle_axis;
 804   wiggle_axis = 0;
 805   wiggle_num_axis = 0;
 806
 807   stream->read(&wiggle_num_axis);
 808   if (wiggle_num_axis > 0)
 809   {
 810      wiggle_axis = new Point3F[wiggle_num_axis];
 811      for (U32 i = 0; i < wiggle_num_axis; i++)
 812         mathRead(*stream, &wiggle_axis[i]);
 813   }
 814
 815   stream->read(&hover_altitude);
 816   stream->read(&hover_attack_distance);
 817   stream->read(&hover_attack_gradient);
 818   hover_time = stream->readRangedU32(0, MaxLifetimeTicks);
 819
 820   reverse_targeting = stream->readFlag();
 821
 822   stream->read(&caster_safety_time);
 823}
 824
 825/* From stock Projectile code...
 826bool ProjectileData::setLifetime( void *obj, const char *index, const char *data )
 827{
 828   S32 value = dAtoi(data);
 829   value = scaleValue(value);
 830   
 831   ProjectileData *object = static_cast<ProjectileData*>(obj);
 832   object->lifetime = value;
 833
 834   return false;
 835}
 836
 837bool ProjectileData::setArmingDelay( void *obj, const char *index, const char *data )
 838{
 839   S32 value = dAtoi(data);
 840   value = scaleValue(value);
 841
 842   ProjectileData *object = static_cast<ProjectileData*>(obj);
 843   object->armingDelay = value;
 844
 845   return false;
 846}
 847
 848bool ProjectileData::setFadeDelay( void *obj, const char *index, const char *data )
 849{
 850   S32 value = dAtoi(data);
 851   value = scaleValue(value);
 852
 853   ProjectileData *object = static_cast<ProjectileData*>(obj);
 854   object->fadeDelay = value;
 855
 856   return false;
 857}
 858
 859const char *ProjectileData::getScaledValue( void *obj, const char *data)
 860{
 861
 862   S32 value = dAtoi(data);
 863   value = scaleValue(value, false);
 864
 865   String stringData = String::ToString(value);
 866   char *strBuffer = Con::getReturnBuffer(stringData.size());
 867   dMemcpy( strBuffer, stringData, stringData.size() );
 868
 869   return strBuffer;
 870}
 871
 872S32 ProjectileData::scaleValue( S32 value, bool down )
 873{
 874   S32 minV = 0;
 875   S32 maxV = Projectile::MaxLivingTicks;
 876   
 877   // scale down to ticks before we validate
 878   if( down )
 879      value /= TickMs;
 880   
 881   if(value < minV || value > maxV)
 882   {
 883      Con::errorf("ProjectileData::scaleValue(S32 value = %d, bool down = %b) - Scaled value must be between %d and %d", value, down, minV, maxV);
 884      if(value < minV)
 885         value = minV;
 886      else if(value > maxV)
 887         value = maxV;
 888   }
 889
 890   // scale up from ticks after we validate
 891   if( !down )
 892      value *= TickMs;
 893
 894   return value;
 895}
 896*/
 897
 898void afxMagicMissileData::gather_cons_defs(Vector<afxConstraintDef>& defs)
 899{ 
 900  if (launch_cons_s_def.isDefined())
 901    defs.push_back(launch_cons_s_def);
 902  if (launch_cons_c_def.isDefined())
 903    defs.push_back(launch_cons_c_def);
 904};
 905
 906//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
 907//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
 908// afxMagicMissile
 909
 910afxMagicMissile::afxMagicMissile()
 911{
 912  init(true, true);
 913}
 914
 915afxMagicMissile::afxMagicMissile(bool on_server, bool on_client)
 916{
 917  init(on_server, on_client);
 918}
 919
 920//--------------------------------------------------------------------------
 921//--------------------------------------
 922//
 923void afxMagicMissile::init(bool on_server, bool on_client)
 924{
 925  mPhysicsWorld = NULL;
 926
 927  mTypeMask |= ProjectileObjectType | LightObjectType;
 928
 929  mLight = LightManager::createLightInfo();
 930  mLight->setType( LightInfo::Point );   
 931
 932  mLightState.clear();
 933  mLightState.setLightInfo( mLight );
 934
 935  mCurrPosition.zero();
 936  mCurrVelocity.set(0, 0, 1);
 937
 938  mCurrTick = 0;
 939
 940  mParticleEmitter   = NULL;
 941  mParticleWaterEmitter = NULL;
 942  mSound = 0;
 943
 944  mProjectileShape   = NULL;
 945
 946  mDataBlock = NULL;
 947
 948  choreographer = NULL;
 949
 950  if (on_server != on_client)
 951  {
 952    client_only = on_client;
 953    server_only = on_server;
 954    mNetFlags.clear(Ghostable | ScopeAlways);
 955    if (client_only)
 956      mNetFlags.set(IsGhost);
 957  }
 958  else
 959  {
 960    // note -- setting neither server or client makes no sense so we
 961    // treat as if both are set.
 962    mNetFlags.set(Ghostable | ScopeAlways);
 963    client_only = server_only = false;
 964  }
 965
 966  mCurrDeltaBase.zero();
 967  mCurrBackDelta.zero();
 968  collision_mask = 0;
 969  prec_inc = 0.0f;
 970
 971  did_launch = false;
 972  did_impact = false;
 973
 974  missile_target = NULL;
 975  collide_exempt = NULL;
 976
 977  use_accel = false;
 978
 979  hover_attack_go = false;
 980  hover_attack_tick = 0;
 981
 982  starting_velocity = 0.0;
 983  starting_vel_vec.zero();
 984
 985  ss_object = 0;
 986  ss_index = 0;
 987}
 988
 989afxMagicMissile::~afxMagicMissile()
 990{
 991   SAFE_DELETE(mLight);
 992
 993   delete mProjectileShape;
 994   mProjectileShape = NULL;
 995}
 996
 997//--------------------------------------------------------------------------
 998void afxMagicMissile::initPersistFields()
 999{
1000   addGroup("Physics");
1001   addField("initialPosition", TypePoint3F, Offset(mCurrPosition, afxMagicMissile) ,
1002     "Initial starting position for this missile.");
1003   addField("initialVelocity", TypePoint3F, Offset(mCurrVelocity, afxMagicMissile) ,
1004     "Initial starting velocity for this missile.");
1005   endGroup("Physics");
1006
1007   /* From stock Projectile code...
1008   addGroup("Source");
1009   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.");
1010   addField("sourceSlot",       TypeS32,     Offset(mSourceObjectSlot, Projectile) ,"Which weapon slot on the sourceObject that this projectile originates from.");
1011   endGroup("Source");
1012  */
1013
1014   Parent::initPersistFields();
1015}
1016
1017/* From stock Projectile code...
1018bool Projectile::calculateImpact(float,
1019                                 Point3F& pointOfImpact,
1020                                 float&   impactTime)
1021{
1022   Con::warnf(ConsoleLogEntry::General, "Projectile::calculateImpact: Should never be called");
1023
1024   impactTime = 0;
1025   pointOfImpact.set(0, 0, 0);
1026   return false;
1027}
1028
1029
1030//--------------------------------------------------------------------------
1031F32 Projectile::getUpdatePriority(CameraScopeQuery *camInfo, U32 updateMask, S32 updateSkips)
1032{
1033   F32 ret = Parent::getUpdatePriority(camInfo, updateMask, updateSkips);
1034   // if the camera "owns" this object, it should have a slightly higher priority
1035   if(mSourceObject == camInfo->camera)
1036      return ret + 0.2;
1037   return ret;
1038}
1039*/
1040
1041bool afxMagicMissile::onAdd()
1042{
1043   if(!Parent::onAdd())
1044      return false;
1045
1046   if (isServerObject())
1047   {
1048      /* From stock Projectile code...
1049      ShapeBase* ptr;
1050      if (Sim::findObject(mSourceObjectId, ptr))
1051      {
1052         mSourceObject = ptr;
1053
1054         // Since we later do processAfter( mSourceObject ) we must clearProcessAfter
1055         // if it is deleted. SceneObject already handles this in onDeleteNotify so
1056         // all we need to do is register for the notification.
1057         deleteNotify( ptr );
1058      }
1059      else
1060      {
1061         if (mSourceObjectId != -1)
1062            Con::errorf(ConsoleLogEntry::General, "Projectile::onAdd: mSourceObjectId is invalid");
1063         mSourceObject = NULL;
1064      }
1065
1066      // If we're on the server, we need to inherit some of our parent's velocity
1067      //
1068      mCurrTick = 0;
1069     */
1070   }
1071   else
1072   {
1073      if (bool(mDataBlock->projectileShape))
1074      {
1075         mProjectileShape = new TSShapeInstance(mDataBlock->projectileShape, true);
1076         /* From stock Projectile code...
1077         if (mDataBlock->activateSeq != -1)
1078         {
1079            mActivateThread = mProjectileShape->addThread();
1080            mProjectileShape->setTimeScale(mActivateThread, 1);
1081            mProjectileShape->setSequence(mActivateThread, mDataBlock->activateSeq, 0);
1082         }
1083         */
1084      }
1085      if (mDataBlock->particleEmitter != NULL)
1086      {
1087         ParticleEmitter* pEmitter = new ParticleEmitter;
1088         //pEmitter->setDataBlock(mDataBlock->particleEmitter->cloneAndPerformSubstitutions(ss_object, ss_index));
1089         pEmitter->onNewDataBlock(mDataBlock->particleEmitter->cloneAndPerformSubstitutions(ss_object, ss_index), false);
1090         if (pEmitter->registerObject() == false)
1091         {
1092            Con::warnf(ConsoleLogEntry::General, "Could not register particle emitter for particle of class: %s", mDataBlock->getName());
1093            delete pEmitter;
1094            pEmitter = NULL;
1095         }
1096         mParticleEmitter = pEmitter;
1097      }
1098
1099      if (mDataBlock->particleWaterEmitter != NULL)
1100      {
1101         ParticleEmitter* pEmitter = new ParticleEmitter;
1102         pEmitter->onNewDataBlock(mDataBlock->particleWaterEmitter->cloneAndPerformSubstitutions(ss_object, ss_index), false);
1103         if (pEmitter->registerObject() == false)
1104         {
1105            Con::warnf(ConsoleLogEntry::General, "Could not register particle emitter for particle of class: %s", mDataBlock->getName());
1106            delete pEmitter;
1107            pEmitter = NULL;
1108         }
1109         mParticleWaterEmitter = pEmitter;
1110      }
1111   }
1112   /* From stock Projectile code...
1113   if (mSourceObject.isValid())
1114      processAfter(mSourceObject);
1115  */
1116
1117   // detect for acceleration
1118   use_accel = (mDataBlock->acceleration != 0 && mDataBlock->accelLifetime > 0);
1119
1120   // Setup our bounding box
1121   if (bool(mDataBlock->projectileShape) == true)
1122      mObjBox = mDataBlock->projectileShape->mBounds;
1123   else
1124      mObjBox = Box3F(Point3F(0, 0, 0), Point3F(0, 0, 0));
1125   resetWorldBox();
1126   addToScene();
1127
1128   if ( PHYSICSMGR )
1129      mPhysicsWorld = PHYSICSMGR->getWorld( isServerObject() ? "server" : "client" );
1130
1131   return true;
1132}
1133
1134
1135void afxMagicMissile::onRemove()
1136{
1137   if(mParticleEmitter)
1138   {
1139      mParticleEmitter->deleteWhenEmpty();
1140      mParticleEmitter = NULL;
1141   }
1142
1143   if(mParticleWaterEmitter)
1144   {
1145      mParticleWaterEmitter->deleteWhenEmpty();
1146      mParticleWaterEmitter = NULL;
1147   }
1148
1149   SFX_DELETE( mSound );
1150
1151   removeFromScene();
1152   Parent::onRemove();
1153}
1154
1155
1156bool afxMagicMissile::onNewDataBlock(GameBaseData* dptr, bool reload)
1157{
1158   mDataBlock = dynamic_cast<afxMagicMissileData*>(dptr);
1159   if (!mDataBlock || !Parent::onNewDataBlock(dptr, reload))
1160      return false;
1161
1162   starting_velocity = mDataBlock->muzzleVelocity;
1163   starting_vel_vec = mDataBlock->starting_vel_vec;
1164   collision_mask = mDataBlock->collision_mask;
1165
1166   if ( isGhost() )
1167   {
1168      // Create the sound ahead of time.  This reduces runtime
1169      // costs and makes the system easier to understand.
1170
1171      SFX_DELETE( mSound );
1172
1173      if ( mDataBlock->sound )
1174         mSound = SFX->createSource( mDataBlock->sound );
1175   }
1176
1177   return true;
1178}
1179
1180
1181//--------------------------------------------------------------------------
1182
1183void afxMagicMissile::submitLights( LightManager *lm, bool staticLighting )
1184{
1185   if ( staticLighting || isHidden() || !mDataBlock->lightDesc )
1186      return;
1187   
1188   mDataBlock->lightDesc->submitLight( &mLightState, getRenderTransform(), lm, this );   
1189}
1190
1191bool afxMagicMissile::pointInWater(const Point3F &point)
1192{
1193   // This is pretty much a hack so we can use the existing ContainerQueryInfo
1194   // and findObject router.
1195   
1196   // We only care if we intersect with water at all 
1197   // so build a box at the point that has only 1 z extent.
1198   // And test if water coverage is anything other than zero.
1199
1200   Box3F boundsBox( point, point );
1201   boundsBox.maxExtents.z += 1.0f;
1202
1203   ContainerQueryInfo info;
1204   info.box = boundsBox;
1205   info.mass = 0.0f;
1206   
1207   // Find and retreive physics info from intersecting WaterObject(s)
1208   if(mContainer != NULL)
1209   {
1210      mContainer->findObjects( boundsBox, WaterObjectType, findRouter, &info );
1211   }
1212   else
1213   {
1214      // Handle special case where the projectile has exploded prior to having
1215      // called onAdd() on the client.  This occurs when the projectile on the
1216      // server is created and then explodes in the same network update tick.
1217      // On the client end in NetConnection::ghostReadPacket() the ghost is
1218      // created and then Projectile::unpackUpdate() is called prior to the
1219      // projectile being registered.  Within unpackUpdate() the explosion
1220      // is triggered, but without being registered onAdd() isn't called and
1221      // the container is not set.  As all we're doing is checking if the
1222      // given explosion point is within water, we should be able to use the
1223      // global container here.  We could likely always get away with this,
1224      // but using the actual defined container when possible is the right
1225      // thing to do.  DAW
1226      AssertFatal(isClientObject(), "Server projectile has not been properly added");
1227      gClientContainer.findObjects( boundsBox, WaterObjectType, findRouter, &info );
1228   }
1229
1230   return ( info.waterCoverage > 0.0f );
1231}
1232
1233//----------------------------------------------------------------------------
1234
1235void afxMagicMissile::emitParticles(const Point3F& from, const Point3F& to, const Point3F& vel, const U32 ms)
1236{
1237  /* From stock Projectile code...
1238   if ( isHidden() )
1239      return;
1240  */
1241
1242   Point3F axis = -vel;
1243
1244   if( axis.isZero() )
1245      axis.set( 0.0, 0.0, 1.0 );
1246   else
1247      axis.normalize();
1248
1249   bool fromWater = pointInWater(from);
1250   bool toWater   = pointInWater(to);
1251
1252   if (!fromWater && !toWater && bool(mParticleEmitter))                                        // not in water
1253      mParticleEmitter->emitParticles(from, to, axis, vel, ms);
1254   else if (fromWater && toWater && bool(mParticleWaterEmitter))                                // in water
1255      mParticleWaterEmitter->emitParticles(from, to, axis, vel, ms);
1256   else if (!fromWater && toWater)     // entering water
1257   {
1258      // cast the ray to get the surface point of the water
1259      RayInfo rInfo;
1260      if (gClientContainer.castRay(from, to, WaterObjectType, &rInfo))
1261      {
1262         create_splash(rInfo.point);
1263
1264         // create an emitter for the particles out of water and the particles in water
1265         if (mParticleEmitter)
1266            mParticleEmitter->emitParticles(from, rInfo.point, axis, vel, ms);
1267
1268         if (mParticleWaterEmitter)
1269            mParticleWaterEmitter->emitParticles(rInfo.point, to, axis, vel, ms);
1270      }
1271   }
1272   else if (fromWater && !toWater)     // leaving water
1273   {
1274      // cast the ray in the opposite direction since that point is out of the water, otherwise
1275      //  we hit water immediately and wont get the appropriate surface point
1276      RayInfo rInfo;
1277      if (gClientContainer.castRay(to, from, WaterObjectType, &rInfo))
1278      {
1279      create_splash(rInfo.point);
1280
1281         // create an emitter for the particles out of water and the particles in water
1282         if (mParticleEmitter)
1283            mParticleEmitter->emitParticles(rInfo.point, to, axis, vel, ms);
1284
1285         if (mParticleWaterEmitter)
1286            mParticleWaterEmitter->emitParticles(from, rInfo.point, axis, vel, ms);
1287      }
1288   }
1289}
1290
1291void afxMagicMissile::processTick(const Move* move)
1292{
1293   Parent::processTick( move );
1294   
1295   // only active from launch to impact
1296   if (!is_active())
1297      return;
1298
1299   mCurrTick++;
1300
1301   // missile fizzles out by exceeding lifetime
1302   if ((isServerObject() || client_only) && mCurrTick >= mDataBlock->lifetime)
1303   {
1304      did_impact = true;
1305      setMaskBits(ImpactMask);
1306      if (choreographer)
1307      {
1308         Point3F n = mCurrVelocity; n.normalizeSafe();
1309         choreographer->impactNotify(mCurrPosition, n, 0);
1310      }
1311      Sim::postEvent(this, new ObjectDeleteEvent, Sim::getCurrentTime() + 500);
1312      return;
1313   }
1314
1315   static F32 dT = F32(TickMs)*0.001f;
1316
1317   Point3F old_pos = mCurrPosition;
1318
1319   // adjust missile velocity from gravity and drag influences
1320   if (mDataBlock->isBallistic)
1321   {
1322      F32 dV = (1 - mDataBlock->ballisticCoefficient)*dT;
1323      Point3F d(mCurrVelocity.x*dV, mCurrVelocity.y*dV, 9.81f*mDataBlock->gravityMod*dT);
1324      mCurrVelocity -= d;
1325   }
1326
1327   // adjust missile velocity from acceleration
1328   if (use_accel)
1329   {
1330      if (mCurrTick > mDataBlock->accelDelay && 
1331         mCurrTick <= mDataBlock->accelDelay + mDataBlock->accelLifetime)
1332      {
1333         Point3F d = mCurrVelocity; d.normalizeSafe();
1334         mCurrVelocity += d*mDataBlock->acceleration*dT;
1335      }
1336   }
1337
1338   // adjust mCurrVelocity from guidance system influences
1339   if (mDataBlock->isGuided && missile_target && mCurrTick > mDataBlock->trackDelay) 
1340   {
1341      // get the position tracked by the guidance system
1342      Point3F target_pos = missile_target->getBoxCenter(); 
1343
1344      Point3F target_vec = target_pos - mCurrPosition;
1345
1346      F32 target_dist_sq = target_vec.lenSquared();
1347      if (target_dist_sq < 4.0f)
1348         prec_inc += 1.0f;
1349
1350      // hover
1351      if (mDataBlock->hover_altitude > 0.0f)
1352      {
1353         Point3F target_vec_xy(target_vec.x, target_vec.y, 0);
1354         F32 xy_dist = target_vec_xy.len();
1355
1356         if (xy_dist > mDataBlock->hover_attack_distance)
1357         {          
1358            hover_attack_go = false;
1359
1360            if (xy_dist > mDataBlock->hover_attack_distance + mDataBlock->hover_attack_gradient)
1361            {
1362               target_pos.z += mDataBlock->hover_altitude;          
1363            }
1364            else
1365            {
1366               target_pos.z += afxEase::eq( (xy_dist-mDataBlock->hover_attack_distance)/<a href="/coding/class/classafxmagicmissile/#classafxmagicmissile_1ae9f94d9860633b87b372e3ff11eebcca">mDataBlock</a>->hover_attack_gradient, 
1367                  0.0f, mDataBlock->hover_altitude, 
1368                  0.25f, 0.75f);
1369            }                 
1370            target_vec = target_pos - mCurrPosition;
1371         }
1372
1373         else
1374         {
1375            if (!hover_attack_go) 
1376            {
1377               hover_attack_go = true;
1378               hover_attack_tick = 0;
1379            }
1380            hover_attack_tick++;
1381
1382            if (hover_attack_tick < mDataBlock->hover_time)
1383            {
1384               target_pos.z += mDataBlock->hover_altitude;
1385               target_vec = target_pos - mCurrPosition;
1386            }
1387         }
1388      }
1389
1390      // apply precision 
1391
1392      // extract speed
1393      F32 speed = mCurrVelocity.len(); 
1394
1395      // normalize vectors
1396      target_vec.normalizeSafe();
1397      mCurrVelocity.normalize();
1398
1399      F32 prec = mDataBlock->precision;
1400
1401      // fade in precision gradually to avoid sudden turn
1402      if (mCurrTick < mDataBlock->trackDelay + 16)
1403         prec *= (mCurrTick - mDataBlock->trackDelay)/16.0f;
1404
1405      prec += prec_inc;
1406      if (prec > 100)
1407         prec = 100;
1408
1409      // apply precision weighting
1410      target_vec *= prec;
1411      mCurrVelocity *= (100 - prec);
1412
1413      mCurrVelocity += target_vec;
1414      mCurrVelocity.normalize();
1415      mCurrVelocity *= speed;
1416   } 
1417
1418   // wiggle
1419   for (U32 i = 0; i < mDataBlock->wiggle_num_axis; i++)
1420   {
1421      if (i >= mDataBlock->wiggle_magnitudes.size() || i >= mDataBlock->wiggle_speeds.size()) 
1422         break;
1423
1424      F32 wiggle_mag   = mDataBlock->wiggle_magnitudes[i];
1425      F32 wiggle_speed = mDataBlock->wiggle_speeds[i];
1426      Point3F wiggle_axis = mDataBlock->wiggle_axis[i];
1427      //wiggle_axis.normalizeSafe(); // sufficient????
1428
1429      F32 theta = wiggle_mag * mSin(wiggle_speed*(mCurrTick*TickSec));
1430      //Con::printf( "theta: %f", theta );    
1431      AngAxisF thetaRot(wiggle_axis, theta);
1432      MatrixF temp(true);
1433      thetaRot.setMatrix(&temp);
1434      temp.mulP(mCurrVelocity);
1435   }
1436
1437   Point3F new_pos = old_pos + mCurrVelocity*dT;
1438
1439   // conform to terrain
1440   if (mDataBlock->followTerrain && mCurrTick >= mDataBlock->followTerrainAdjustDelay) 
1441   {
1442      U32 mask = TerrainObjectType | TerrainLikeObjectType; //  | InteriorObjectType;
1443
1444      F32 ht = mDataBlock->followTerrainHeight;
1445      F32 ht_rate = mDataBlock->followTerrainAdjustRate;
1446      F32 ht_min = 0.05f;
1447      if (ht < ht_min)
1448         ht = ht_min;
1449
1450      SceneContainer* container = (isServerObject()) ? &gServerContainer : &gClientContainer;
1451      Point3F above_pos = new_pos; above_pos.z += 10000;
1452      Point3F below_pos = new_pos; below_pos.z -= 10000;
1453      RayInfo rInfo;
1454      if (container && container->castRay(above_pos, below_pos, mask, &rInfo)) 
1455      {
1456         F32 terrain_z = rInfo.point.z;
1457         F32 seek_z = terrain_z + ht;
1458         if (new_pos.z < seek_z)
1459         {
1460            new_pos.z += ht_rate*dT;
1461            if (new_pos.z > seek_z)
1462               new_pos.z = seek_z;
1463         }
1464         else if (new_pos.z > seek_z)
1465         {
1466            new_pos.z -= ht_rate*dT;
1467            if (new_pos.z < seek_z)
1468               new_pos.z = seek_z;
1469         }
1470
1471         if (new_pos.z < terrain_z + ht_min)
1472            new_pos.z = terrain_z + ht_min;
1473      }
1474   }
1475
1476   // only check for impacts on server
1477   if (isServerObject())
1478   {
1479      // avoid collision with the spellcaster
1480      if (collide_exempt && mCurrTick <= mDataBlock->caster_safety_time)
1481         collide_exempt->disableCollision();
1482
1483      // check for collision along ray from old to new position
1484      RayInfo rInfo;
1485      bool did_hit = false;
1486
1487      if (mPhysicsWorld)
1488      {
1489         // did_hit = mPhysicsWorld->castRay(old_pos, new_pos, &rInfo, Point3F(new_pos - old_pos) * mDataBlock->impactForce );
1490         // Impulse currently hardcoded for testing purposes
1491         did_hit = mPhysicsWorld->castRay(old_pos, new_pos, &rInfo, Point3F(new_pos - old_pos) * 1000.0f );
1492      }
1493      else
1494      {
1495         did_hit = getContainer()->castRay(old_pos, new_pos, collision_mask, &rInfo);
1496      }
1497
1498      // restore collisions on spellcaster 
1499      if (collide_exempt && mCurrTick <= mDataBlock->caster_safety_time)
1500         collide_exempt->enableCollision();
1501
1502      // process impact
1503      if (did_hit)
1504      {
1505         MatrixF xform(true);
1506         xform.setColumn(3, rInfo.point);
1507         setTransform(xform);
1508         mCurrPosition = rInfo.point;
1509         mCurrVelocity = Point3F(0, 0, 0);
1510         did_impact = true;
1511         setMaskBits(ImpactMask);
1512         if (choreographer)
1513         {
1514            choreographer->impactNotify(rInfo.point, rInfo.normal, rInfo.object);
1515            Sim::postEvent(this, new ObjectDeleteEvent, Sim::getCurrentTime() + 500);
1516         }
1517      }
1518   }
1519   else // if (isClientObject())
1520   {
1521      emitParticles(mCurrPosition, new_pos, mCurrVelocity, TickMs);
1522      updateSound();
1523   }
1524
1525   // interp values used in interpolateTick()
1526   mCurrDeltaBase = new_pos;
1527   mCurrBackDelta = mCurrPosition - new_pos;
1528
1529   mCurrPosition = new_pos;
1530
1531   MatrixF xform(true);
1532   xform.setColumn(3, mCurrPosition);
1533   setTransform(xform);
1534}
1535
1536void afxMagicMissile::interpolateTick(F32 delta)
1537{
1538   Parent::interpolateTick(delta);
1539
1540   // only active from launch to impact
1541   if (!is_active())
1542      return;
1543
1544   Point3F interpPos = mCurrDeltaBase + mCurrBackDelta * delta;
1545   Point3F dir = mCurrVelocity;
1546   if(dir.isZero())
1547      dir.set(0,0,1);
1548   else
1549      dir.normalize();
1550
1551   MatrixF xform(true);
1552   xform = MathUtils::createOrientFromDir(dir);
1553   xform.setPosition(interpPos);
1554   setRenderTransform(xform);
1555
1556   updateSound();
1557}
1558
1559//--------------------------------------------------------------------------
1560U32 afxMagicMissile::packUpdate(NetConnection* con, U32 mask, BitStream* stream)
1561{
1562   U32 retMask = Parent::packUpdate( con, mask, stream );
1563
1564   const bool isInitalUpdate = mask & GameBase::InitialUpdateMask;
1565
1566   // InitialUpdateMask
1567   if ( stream->writeFlag( isInitalUpdate ) )
1568   {
1569      Point3F pos;
1570      getTransform().getColumn( 3, &pos );
1571      stream->writeCompressedPoint( pos );
1572      F32 len = mCurrVelocity.len();
1573
1574      if ( stream->writeFlag( len > 0.02 ) )
1575      {
1576         Point3F outVel = mCurrVelocity;
1577         outVel *= 1 / len;
1578         stream->writeNormalVector( outVel, 10 );
1579         len *= 32.0; // 5 bits for fraction
1580
1581         if ( len > 8191 )
1582             len = 8191;
1583
1584         stream->writeInt( (S32)len, 13 );
1585      }
1586
1587      stream->writeRangedU32( mCurrTick, 0, afxMagicMissileData::MaxLifetimeTicks );
1588
1589      if (choreographer)
1590      {
1591         S32 ghostIndex = con->getGhostIndex( choreographer );
1592         if ( stream->writeFlag( ghostIndex != -1 ) )
1593         {
1594            stream->writeRangedU32( U32(ghostIndex), 
1595                                    0, 
1596                                    NetConnection::MaxGhostCount );
1597         }
1598         else 
1599            // have not recieved the ghost for the source object yet, try again later
1600            retMask |= GameBase::InitialUpdateMask;
1601      }
1602      else
1603         stream->writeFlag( false );
1604   }
1605
1606   // impact update
1607   if (stream->writeFlag(mask & ImpactMask))
1608   {
1609      mathWrite(*stream, mCurrPosition);
1610      mathWrite(*stream, mCurrVelocity);
1611      stream->writeFlag(did_impact);
1612   }
1613
1614   // guided update
1615   if (stream->writeFlag(mask & GuideMask))
1616   {
1617      mathWrite(*stream, mCurrPosition);
1618      mathWrite(*stream, mCurrVelocity);
1619   }
1620
1621   return retMask;
1622}
1623
1624void afxMagicMissile::unpackUpdate(NetConnection* con, BitStream* stream)
1625{
1626   Parent::unpackUpdate(con, stream);
1627   
1628   if ( stream->readFlag() ) // InitialUpdateMask
1629   {
1630      Point3F pos;
1631      stream->readCompressedPoint( &pos );
1632      if ( stream->readFlag() )
1633      {
1634         stream->readNormalVector( &mCurrVelocity, 10 );
1635         mCurrVelocity *= stream->readInt( 13 ) / 32.0f;
1636      }
1637      else
1638         mCurrVelocity.zero();
1639
1640      mCurrDeltaBase = pos;
1641      mCurrBackDelta = mCurrPosition - pos;
1642      mCurrPosition  = pos;
1643      setPosition( mCurrPosition );
1644
1645      mCurrTick = stream->readRangedU32(0, afxMagicMissileData::MaxLifetimeTicks);
1646      if ( stream->readFlag() )
1647      {
1648         U32 id   = stream->readRangedU32(0, NetConnection::MaxGhostCount);
1649         choreographer = dynamic_cast<afxChoreographer*>(con->resolveGhost(id));
1650         if (choreographer)
1651         {
1652            deleteNotify(choreographer);
1653         }
1654      }
1655      else
1656      {
1657         if (choreographer)
1658            clearNotify(choreographer);
1659         choreographer = 0;
1660      }
1661   }
1662   
1663   // impact update
1664   if (stream->readFlag())
1665   {
1666      mathRead(*stream, &mCurrPosition);
1667      mathRead(*stream, &mCurrVelocity);
1668      did_impact = stream->readFlag();
1669
1670   }
1671
1672   if (stream->readFlag()) // guided update
1673   {
1674      mathRead( *stream, &mCurrPosition );
1675      mathRead( *stream, &mCurrVelocity );
1676   }
1677}
1678
1679//--------------------------------------------------------------------------
1680void afxMagicMissile::prepRenderImage(SceneRenderState* state)
1681{
1682   if (!is_active())
1683      return;
1684
1685   /*
1686   if (isHidden() || mFadeValue <= (1.0/255.0))
1687      return;
1688   */
1689
1690   if ( mDataBlock->lightDesc )
1691   {
1692     mDataBlock->lightDesc->prepRender( state, &mLightState, getRenderTransform() );
1693   }
1694
1695   /*
1696   if ( mFlareData )
1697   {
1698     mFlareState.fullBrightness = mDataBlock->lightDesc->mBrightness;
1699     mFlareState.scale = mFlareScale;
1700     mFlareState.lightInfo = mLight;
1701     mFlareState.lightMat = getTransform();
1702
1703     mFlareData->prepRender( state, &mFlareState );
1704   }
1705   */
1706
1707   prepBatchRender( state );
1708}
1709
1710void afxMagicMissile::prepBatchRender(SceneRenderState* state)
1711{
1712   if ( !mProjectileShape )
1713      return;
1714
1715   GFXTransformSaver saver;
1716
1717   // Set up our TS render state.
1718   TSRenderState rdata;
1719   rdata.setSceneState( state );
1720
1721   // We might have some forward lit materials
1722   // so pass down a query to gather lights.
1723   LightQuery query;
1724   query.init( getWorldSphere() );
1725   rdata.setLightQuery( &query );
1726
1727   MatrixF mat = getRenderTransform();
1728   mat.scale( mObjScale );
1729   mat.scale( mDataBlock->scale );
1730   GFX->setWorldMatrix( mat );
1731
1732   mProjectileShape->setDetailFromPosAndScale( state, mat.getPosition(), mObjScale );
1733   mProjectileShape->animate();
1734
1735   mProjectileShape->render( rdata );
1736}
1737
1738void afxMagicMissile::onDeleteNotify(SimObject* obj)
1739{
1740  ShapeBase* shape_test = dynamic_cast<ShapeBase*>(obj);
1741  if (shape_test == collide_exempt)
1742  {
1743    collide_exempt = NULL;
1744    Parent::onDeleteNotify(obj);
1745    return;
1746  }
1747
1748  SceneObject* target_test = dynamic_cast<SceneObject*>(obj);
1749  if (target_test == missile_target)
1750  {
1751    missile_target = NULL;
1752    Parent::onDeleteNotify(obj);
1753    return;
1754  }
1755
1756  afxChoreographer* ch = dynamic_cast<afxChoreographer*>(obj);
1757  if (ch == choreographer)
1758  {
1759    choreographer = NULL;
1760    Parent::onDeleteNotify(obj);
1761    return;
1762  }
1763
1764  Parent::onDeleteNotify(obj);
1765}
1766
1767//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
1768// private:
1769
1770void afxMagicMissile::create_splash(const Point3F& pos)
1771{
1772  if (!mDataBlock || !mDataBlock->splash)
1773    return;
1774
1775  MatrixF xfm = getTransform();
1776  xfm.setPosition(pos);
1777
1778  Splash* splash = new Splash();
1779  splash->onNewDataBlock(mDataBlock->splash, false);
1780  splash->setTransform(xfm);
1781  splash->setInitialState(xfm.getPosition(), Point3F(0.0, 0.0, 1.0));
1782  if (!splash->registerObject())
1783  {
1784    delete splash;
1785    splash = NULL;
1786  }
1787}
1788
1789void afxMagicMissile::get_launch_constraint_data(Point3F& pos, Point3F& vel)
1790{
1791  // need a choreographer
1792  if (!choreographer)
1793  {
1794    Con::errorf("afxMagicMissile::get_launch_constraint_data(): missing choreographer.");
1795    return;
1796  }
1797
1798  // need a constraint manager
1799  afxConstraintMgr* cons_mgr = choreographer->getConstraintMgr();
1800  if (!cons_mgr)
1801  {
1802    Con::errorf("afxMagicMissile::get_launch_constraint_data(): missing constriant manager.");
1803    return;
1804  }
1805
1806  // need a valid constraint
1807  afxConstraintID launch_cons_id;
1808  if (isServerObject())
1809    launch_cons_id = cons_mgr->getConstraintId(mDataBlock->launch_cons_s_def);
1810  else
1811    launch_cons_id = cons_mgr->getConstraintId(mDataBlock->launch_cons_c_def);
1812
1813  afxConstraint* launch_cons = cons_mgr->getConstraint(launch_cons_id);
1814  if (!launch_cons)
1815  {
1816    Con::errorf("afxMagicMissile::get_launch_constraint_data(): constraint undefined.");
1817    return;
1818  }
1819
1820  MatrixF launch_xfm;
1821  launch_cons->getTransform(launch_xfm);
1822
1823  Point3F launch_pos;
1824  launch_cons->getPosition(launch_pos);
1825
1826  pos = launch_pos;
1827
1828  // echo the actual launch position to the console
1829  if (mDataBlock->echo_launch_offset)
1830  {
1831    SceneObject* default_launcher = get_default_launcher();
1832    if (default_launcher)
1833    {
1834      MatrixF launcher_xfm_inv = default_launcher->getWorldTransform();
1835      VectorF offset = pos - default_launcher->getRenderPosition();
1836      launcher_xfm_inv.mulV(offset);
1837      if (isServerObject())
1838        Con::printf("launchOffsetServer = \"%g %g %g\";", offset.x, offset.y, offset.z);
1839      else
1840        Con::printf("launchOffsetClient = \"%g %g %g\";", offset.x, offset.y, offset.z);
1841    }
1842  }
1843
1844  // setup aiming matrix to straight forward and level
1845  MatrixF aim_mtx;
1846  AngAxisF aim_aa(Point3F(0,1,0),0);
1847  aim_aa.setMatrix(&aim_mtx);
1848
1849  // calculate final aiming vector
1850  MatrixF aim2_mtx;
1851  aim2_mtx.mul(launch_xfm, aim_mtx);
1852
1853  VectorF aim_vec;
1854  aim2_mtx.getColumn(1,&aim_vec);
1855  aim_vec.normalizeSafe();
1856
1857  // give velocity vector a magnitude
1858  vel = aim_vec*mDataBlock->muzzleVelocity;
1859}
1860
1861// resolve the launch constraint object. normally it's the caster, but for
1862// reverse_targeting the target object us used.
1863SceneObject* afxMagicMissile::get_default_launcher() const
1864{
1865  SceneObject* launch_cons_obj = 0;
1866  if (mDataBlock->reverse_targeting)
1867  {
1868    if (dynamic_cast<afxMagicSpell*>(choreographer))
1869      launch_cons_obj = ((afxMagicSpell*)choreographer)->mTarget;
1870    if (!launch_cons_obj)
1871    {
1872      Con::errorf("afxMagicMissile::get_launch_data(): missing target constraint object for reverse targeted missile.");
1873      return 0;
1874    }
1875  }
1876  else
1877  {
1878    if (dynamic_cast<afxMagicSpell*>(choreographer))
1879      launch_cons_obj = ((afxMagicSpell*)choreographer)->mCaster;
1880    if (!launch_cons_obj)
1881    {
1882      Con::errorf("afxMagicMissile::get_launch_data(): missing launch constraint object missile.");
1883      return 0;
1884    }
1885  }
1886
1887  return launch_cons_obj;
1888}
1889
1890void afxMagicMissile::get_launch_data(Point3F& pos, Point3F& vel)
1891{
1892  bool use_constraint = (isServerObject()) ? mDataBlock->launch_cons_s_def.isDefined() : mDataBlock->launch_cons_c_def.isDefined();
1893  if (use_constraint)
1894  {
1895    get_launch_constraint_data(pos, vel);
1896    return;
1897  }  
1898  
1899  // a choreographer pointer is required
1900  if (!choreographer)
1901  {
1902    Con::errorf("afxMagicMissile::get_launch_data(): missing choreographer.");
1903    return;
1904  }
1905
1906  SceneObject* launch_cons_obj = get_default_launcher();
1907  if (!launch_cons_obj)
1908    return;
1909
1910  MatrixF launch_xfm = launch_cons_obj->getRenderTransform();
1911
1912  // calculate launch position
1913  Point3F offset_override = (isClientObject()) ?  mDataBlock->launch_offset_client : 
1914                                                  mDataBlock->launch_offset_server;
1915  // override
1916  if (!offset_override.isZero())
1917  {
1918    launch_xfm.mulV(offset_override);
1919    pos = launch_cons_obj->getRenderPosition() + offset_override;
1920  }
1921  // no override 
1922  else
1923  {
1924    // get transformed launch offset
1925    VectorF launch_offset = mDataBlock->launch_offset;
1926    launch_xfm.mulV(launch_offset);
1927    
1928    StringTableEntry launch_node = mDataBlock->launch_node;
1929    
1930    // calculate position of missile at launch
1931    if (launch_node != ST_NULLSTRING)
1932    {
1933      ShapeBase* launch_cons_shape = dynamic_cast<ShapeBase*>(launch_cons_obj);
1934      TSShapeInstance* shape_inst = (launch_cons_shape) ? launch_cons_shape->getShapeInstance() : 0;
1935      if (!shape_inst || !shape_inst->getShape())
1936        launch_node = ST_NULLSTRING;
1937      else
1938      {
1939        S32 node_ID = shape_inst->getShape()->findNode(launch_node);
1940        MatrixF node_xfm = launch_cons_obj->getRenderTransform();
1941        node_xfm.scale(launch_cons_obj->getScale());
1942        if (node_ID >= 0)
1943          node_xfm.mul(shape_inst->mNodeTransforms[node_ID]);
1944        
1945        VectorF node_offset = mDataBlock->launch_node_offset;
1946        node_xfm.mulV(node_offset);
1947        
1948        pos = node_xfm.getPosition() + launch_offset + node_offset;
1949      }
1950    }   
1951    // calculate launch position without launch node
1952    else
1953      pos = launch_cons_obj->getRenderPosition() + launch_offset;
1954  }
1955
1956  // echo the actual launch position to the console
1957  if (mDataBlock->echo_launch_offset)
1958  {
1959    VectorF offset = pos - launch_cons_obj->getRenderPosition();
1960    MatrixF caster_xfm_inv = launch_xfm;
1961    caster_xfm_inv.affineInverse();
1962    caster_xfm_inv.mulV(offset);
1963    if (isServerObject())
1964      Con::printf("launchOffsetServer = \"%g %g %g\";", offset.x, offset.y, offset.z);
1965    else
1966      Con::printf("launchOffsetClient = \"%g %g %g\";", offset.x, offset.y, offset.z);
1967  }
1968
1969  // calculate launch velocity vector
1970  if (starting_vel_vec.isZero())
1971  {
1972    // setup aiming matrix to straight forward and level
1973    MatrixF aim_mtx;
1974    AngAxisF aim_aa(Point3F(0,1,0),0);
1975    aim_aa.setMatrix(&aim_mtx);
1976    
1977    // setup pitch matrix
1978    MatrixF pitch_mtx;
1979    AngAxisF pitch_aa(Point3F(1,0,0),mDegToRad(mDataBlock->launch_pitch));
1980    pitch_aa.setMatrix(&pitch_mtx);
1981    
1982    // setup pan matrix
1983    MatrixF pan_mtx;
1984    AngAxisF pan_aa(Point3F(0,0,1),mDegToRad(mDataBlock->launch_pan));
1985    pan_aa.setMatrix(&pan_mtx);
1986    
1987    // calculate adjusted aiming matrix
1988    aim_mtx.mul(pitch_mtx);
1989    aim_mtx.mul(pan_mtx);
1990    
1991    // calculate final aiming vector
1992    MatrixF aim2_mtx;
1993    aim2_mtx.mul(launch_xfm, aim_mtx);
1994    VectorF aim_vec;
1995    aim2_mtx.getColumn(1,&aim_vec);
1996    aim_vec.normalizeSafe();
1997    
1998    // give velocity vector a magnitude
1999    vel = aim_vec*mDataBlock->muzzleVelocity;
2000  }
2001  else
2002  {
2003    vel = starting_vel_vec*starting_velocity;
2004  }
2005}
2006
2007void afxMagicMissile::updateSound()
2008{
2009  if (!mDataBlock->sound)
2010    return;
2011
2012  if ( mSound )
2013  {
2014    if ( !mSound->isPlaying() )
2015      mSound->play();
2016
2017    mSound->setVelocity( getVelocity() );
2018    mSound->setTransform( getRenderTransform() );
2019  }
2020}
2021
2022//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
2023// public:
2024
2025void afxMagicMissile::launch()
2026{
2027  get_launch_data(mCurrPosition, mCurrVelocity);
2028
2029  mCurrDeltaBase = mCurrPosition;
2030  mCurrBackDelta.zero();
2031
2032  did_launch = true;
2033
2034  setPosition(mCurrPosition);
2035
2036  afxMagicSpell* spell = dynamic_cast<afxMagicSpell*>(choreographer);
2037  if (spell)
2038  {
2039    if (mDataBlock->reverse_targeting)
2040    {
2041      missile_target = spell->mCaster;
2042      collide_exempt = spell->mTarget;
2043    }
2044    else
2045    {
2046      missile_target = spell->mTarget;
2047      collide_exempt = spell->mCaster;
2048    }
2049
2050    if (spell->mCaster)
2051      processAfter(spell->mCaster);
2052    if (missile_target)
2053      deleteNotify(missile_target);
2054    if (collide_exempt)
2055      deleteNotify(collide_exempt);
2056  }
2057  else
2058  {
2059    missile_target = 0;
2060    collide_exempt = 0;
2061  }
2062}
2063
2064void afxMagicMissile::setChoreographer(afxChoreographer* chor)
2065{
2066  if (choreographer)
2067    clearNotify(choreographer);
2068  choreographer = chor;
2069  if (choreographer)
2070    deleteNotify(choreographer);
2071}
2072
2073void afxMagicMissile::setStartingVelocityVector(const Point3F& vel_vec)
2074{
2075  starting_vel_vec = vel_vec;
2076}
2077
2078void afxMagicMissile::setStartingVelocity(const F32 vel)
2079{
2080  starting_velocity = vel;
2081}
2082
2083void afxMagicMissile::getStartingVelocityValues(F32& vel, Point3F& vel_vec)
2084{
2085  vel = starting_velocity;
2086  vel_vec = starting_vel_vec;
2087}
2088
2089//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
2090
2091/* From stock Projectile code...
2092DefineEngineMethod(Projectile, presimulate, void, (F32 seconds), (1.0f), "Updates velocity and position, and performs collision testing.\n"
2093                                       "@param seconds Amount of time, in seconds, since the simulation began, to start the simulation at.\n"
2094                                       "@tsexample\n"
2095                                          "// Tell the projectile object to process a simulation event, and provide the amount of time\n"
2096                                          "   in seconds that has passed since the simulation began.\n"
2097                                          "%seconds = 2000;\n"
2098                                          "%projectile.presimulate(%seconds);\n"
2099                                       "@endtsexample\n")
2100{
2101   object->simulate( seconds );
2102}
2103*/
2104
2105DefineEngineMethod(afxMagicMissile, setStartingVelocityVector, void, (Point3F velocityVec),,
2106                   "Set the starting velocity-vector for a magic-missile.\n\n"
2107                   "@ingroup AFX")
2108{
2109  object->setStartingVelocityVector(velocityVec);
2110}
2111
2112DefineEngineMethod(afxMagicMissile, setStartingVelocity, void, (float velocity),,
2113                   "Set the starting velocity for a magic-missile.\n\n"
2114                   "@ingroup AFX")
2115{
2116  object->setStartingVelocity(velocity);
2117}
2118
2119//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
2120