projectile.cpp

Engine/source/T3D/projectile.cpp

More...

Public Functions

ConsoleDocClass(Projectile , "@brief Base projectile class. Uses the <a href="/coding/class/classprojectiledata/">ProjectileData</a> class <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> properties of individual <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">projectiles.\n</a>" "@ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">gameObjects\n</a>" )
ConsoleDocClass(ProjectileData , "@brief Stores properties <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> an individual projectile <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">type.\n</a>" "@<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">tsexample\n</a>" "datablock <a href="/coding/class/classprojectiledata/">ProjectileData</a>(GrenadeLauncherProjectile)\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "{\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " projectileShapeName = \"art/shapes/weapons/SwarmGun/rocket.dts\";\n" "directDamage = 30;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "radiusDamage = 30;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "damageRadius = 5;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "areaImpulse = 2000;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "explosion = GrenadeLauncherExplosion;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "waterExplosion = GrenadeLauncherWaterExplosion;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "decal = ScorchRXDecal;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "splash = GrenadeSplash;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "particleEmitter = GrenadeProjSmokeTrailEmitter;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "particleWaterEmitter = GrenadeTrailWaterEmitter;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "muzzleVelocity = 30;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "velInheritFactor = 0.3;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "armingDelay = 2000;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "lifetime = 10000;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "fadeDelay = 4500;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "bounceElasticity = 0.4;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "bounceFriction = 0.3;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "isBallistic = true;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "gravityMod = 0.9;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "lightDesc = GrenadeLauncherLightDesc;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "damageType = \"GrenadeDamage\";\n" "};\<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">gameObjects\n</a>" )
DefineEngineMethod(Projectile , presimulate , void , (F32 seconds) , (1.0f) , "@brief Updates the projectile's positional and collision <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">information.\n\n</a>" "This function will first delete the projectile <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a4e4fa7e3399708e0777b5308db01278c">if</a> it is <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> server object and is outside it's ProjectileData::lifetime. " "Also responsible <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> applying gravity, determining collisions, triggering explosions, " "emitting trail particles, and calculating bounces <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a4e4fa7e3399708e0777b5308db01278c">if</a> necessary." " @param seconds Amount of time, in seconds since the simulation 's start, <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">advance.\n</a>" " @<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">tsexample\n</a>" "//Tell the projectile <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> process <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> simulation event)
IMPLEMENT_CALLBACK(ProjectileData , onCollision , void , (Projectile *proj, SceneObject *col, F32 fade, Point3F pos, Point3F normal) , (proj, col, fade, pos, normal) , "@brief Called when <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> projectile collides with another <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">object.\n\n</a>" "This function is only called on server objects." "@param proj The projectile colliding with <a href="/coding/class/classsceneobject/">SceneObject</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">col.\n</a>" "@param col The <a href="/coding/class/classsceneobject/">SceneObject</a> hit by the <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">projectile.\n</a>" "@param fade The current fadeValue of the projectile, affects its <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">visibility.\n</a>" " @param pos The position of the <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">collision.\n</a>" " @param normal The normal of the <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">collision.\n</a>" " @see <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Projectile\n</a>" )
IMPLEMENT_CALLBACK(ProjectileData , onExplode , void , (Projectile *proj, Point3F pos, F32 fade) , (proj, pos, fade) , "@brief Called when <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> projectile <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">explodes.\n\n</a>" "This function is only called on server <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">objects.\n</a>" "@param proj The exploding <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">projectile.\n</a>" "@param pos The position of the <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">explosion.\n</a>" "@param fade The current fadeValue of the projectile, affects its <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">visibility.\n\n</a>" " @see <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Projectile\n</a>" )

Detailed Description

Public Functions

ConsoleDocClass(Projectile , "@brief Base projectile class. Uses the <a href="/coding/class/classprojectiledata/">ProjectileData</a> class <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> properties of individual <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">projectiles.\n</a>" "@ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">gameObjects\n</a>" )

ConsoleDocClass(ProjectileData , "@brief Stores properties <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> an individual projectile <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">type.\n</a>" "@<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">tsexample\n</a>" "datablock <a href="/coding/class/classprojectiledata/">ProjectileData</a>(GrenadeLauncherProjectile)\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "{\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " projectileShapeName = \"art/shapes/weapons/SwarmGun/rocket.dts\";\n" "directDamage = 30;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "radiusDamage = 30;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "damageRadius = 5;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "areaImpulse = 2000;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "explosion = GrenadeLauncherExplosion;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "waterExplosion = GrenadeLauncherWaterExplosion;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "decal = ScorchRXDecal;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "splash = GrenadeSplash;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "particleEmitter = GrenadeProjSmokeTrailEmitter;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "particleWaterEmitter = GrenadeTrailWaterEmitter;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "muzzleVelocity = 30;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "velInheritFactor = 0.3;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "armingDelay = 2000;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "lifetime = 10000;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "fadeDelay = 4500;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "bounceElasticity = 0.4;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "bounceFriction = 0.3;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "isBallistic = true;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "gravityMod = 0.9;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "lightDesc = GrenadeLauncherLightDesc;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "damageType = \"GrenadeDamage\";\n" "};\<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">gameObjects\n</a>" )

DefineEngineMethod(Projectile , presimulate , void , (F32 seconds) , (1.0f) , "@brief Updates the projectile's positional and collision <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">information.\n\n</a>" "This function will first delete the projectile <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a4e4fa7e3399708e0777b5308db01278c">if</a> it is <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> server object and is outside it's ProjectileData::lifetime. " "Also responsible <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> applying gravity, determining collisions, triggering explosions, " "emitting trail particles, and calculating bounces <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a4e4fa7e3399708e0777b5308db01278c">if</a> necessary." " @param seconds Amount of time, in seconds since the simulation 's start, <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">advance.\n</a>" " @<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">tsexample\n</a>" "//Tell the projectile <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> process <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> simulation event)

IMPLEMENT_CALLBACK(ProjectileData , onCollision , void , (Projectile *proj, SceneObject *col, F32 fade, Point3F pos, Point3F normal) , (proj, col, fade, pos, normal) , "@brief Called when <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> projectile collides with another <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">object.\n\n</a>" "This function is only called on server objects." "@param proj The projectile colliding with <a href="/coding/class/classsceneobject/">SceneObject</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">col.\n</a>" "@param col The <a href="/coding/class/classsceneobject/">SceneObject</a> hit by the <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">projectile.\n</a>" "@param fade The current fadeValue of the projectile, affects its <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">visibility.\n</a>" " @param pos The position of the <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">collision.\n</a>" " @param normal The normal of the <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">collision.\n</a>" " @see <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Projectile\n</a>" )

IMPLEMENT_CALLBACK(ProjectileData , onExplode , void , (Projectile *proj, Point3F pos, F32 fade) , (proj, pos, fade) , "@brief Called when <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> projectile <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">explodes.\n\n</a>" "This function is only called on server <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">objects.\n</a>" "@param proj The exploding <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">projectile.\n</a>" "@param pos The position of the <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">explosion.\n</a>" "@param fade The current fadeValue of the projectile, affects its <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">visibility.\n\n</a>" " @see <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Projectile\n</a>" )

IMPLEMENT_CO_DATABLOCK_V1(ProjectileData )

IMPLEMENT_CO_NETOBJECT_V1(Projectile )

   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
  29#include "platform/platform.h"
  30#include "T3D/projectile.h"
  31
  32#include "scene/sceneRenderState.h"
  33#include "scene/sceneManager.h"
  34#include "lighting/lightInfo.h"
  35#include "lighting/lightManager.h"
  36#include "console/consoleTypes.h"
  37#include "console/typeValidators.h"
  38#include "core/resourceManager.h"
  39#include "core/stream/bitStream.h"
  40#include "T3D/fx/explosion.h"
  41#include "T3D/shapeBase.h"
  42#include "ts/tsShapeInstance.h"
  43#include "sfx/sfxTrack.h"
  44#include "sfx/sfxSource.h"
  45#include "sfx/sfxSystem.h"
  46#include "sfx/sfxTypes.h"
  47#include "math/mathUtils.h"
  48#include "math/mathIO.h"
  49#include "sim/netConnection.h"
  50#include "T3D/fx/particleEmitter.h"
  51#include "T3D/fx/splash.h"
  52#include "T3D/physics/physicsPlugin.h"
  53#include "T3D/physics/physicsWorld.h"
  54#include "gfx/gfxTransformSaver.h"
  55#include "T3D/containerQuery.h"
  56#include "T3D/decal/decalManager.h"
  57#include "T3D/decal/decalData.h"
  58#include "T3D/lightDescription.h"
  59#include "console/engineAPI.h"
  60
  61
  62IMPLEMENT_CO_DATABLOCK_V1(ProjectileData);
  63
  64ConsoleDocClass( ProjectileData,
  65   "@brief Stores properties for an individual projectile type.\n"
  66
  67   "@tsexample\n"
  68      "datablock ProjectileData(GrenadeLauncherProjectile)\n"
  69      "{\n"
  70        " projectileShapeName = \"art/shapes/weapons/SwarmGun/rocket.dts\";\n"
  71         "directDamage = 30;\n"
  72         "radiusDamage = 30;\n"
  73         "damageRadius = 5;\n"
  74         "areaImpulse = 2000;\n"
  75
  76         "explosion = GrenadeLauncherExplosion;\n"
  77         "waterExplosion = GrenadeLauncherWaterExplosion;\n"
  78
  79         "decal = ScorchRXDecal;\n"
  80         "splash = GrenadeSplash;\n"
  81
  82         "particleEmitter = GrenadeProjSmokeTrailEmitter;\n"
  83         "particleWaterEmitter = GrenadeTrailWaterEmitter;\n"
  84
  85         "muzzleVelocity = 30;\n"
  86         "velInheritFactor = 0.3;\n"
  87
  88         "armingDelay = 2000;\n"
  89         "lifetime = 10000;\n"
  90         "fadeDelay = 4500;\n"
  91
  92         "bounceElasticity = 0.4;\n"
  93         "bounceFriction = 0.3;\n"
  94         "isBallistic = true;\n"
  95         "gravityMod = 0.9;\n"
  96
  97         "lightDesc = GrenadeLauncherLightDesc;\n"
  98
  99         "damageType = \"GrenadeDamage\";\n"
 100      "};\n"
 101   "@endtsexample\n"
 102
 103   "@ingroup gameObjects\n"
 104);
 105
 106IMPLEMENT_CO_NETOBJECT_V1(Projectile);
 107
 108ConsoleDocClass( Projectile,
 109   "@brief Base projectile class. Uses the ProjectileData class for properties of individual projectiles.\n"
 110   "@ingroup gameObjects\n"
 111);
 112
 113IMPLEMENT_CALLBACK( ProjectileData, onExplode, void, ( Projectile* proj, Point3F pos, F32 fade ), 
 114                   ( proj, pos, fade ),
 115               "@brief Called when a projectile explodes.\n\n"
 116                   "This function is only called on server objects.\n"
 117                   "@param proj The exploding projectile.\n"
 118               "@param pos The position of the explosion.\n"
 119               "@param fade The current fadeValue of the projectile, affects its visibility.\n\n"
 120               "@see Projectile\n"
 121              );
 122
 123IMPLEMENT_CALLBACK( ProjectileData, onCollision, void, ( Projectile* proj, SceneObject* col, F32 fade, Point3F pos, Point3F normal ),
 124                   ( proj, col, fade, pos, normal ),
 125               "@brief Called when a projectile collides with another object.\n\n"
 126                   "This function is only called on server objects."
 127               "@param proj The projectile colliding with SceneObject col.\n"
 128               "@param col The SceneObject hit by the projectile.\n"
 129               "@param fade The current fadeValue of the projectile, affects its visibility.\n"
 130               "@param pos The position of the collision.\n"
 131                   "@param normal The normal of the collision.\n"
 132               "@see Projectile\n"
 133              );
 134
 135const U32 Projectile::csmStaticCollisionMask =  TerrainObjectType | StaticShapeObjectType;
 136
 137const U32 Projectile::csmDynamicCollisionMask = PlayerObjectType | VehicleObjectType;
 138
 139const U32 Projectile::csmDamageableMask = Projectile::csmDynamicCollisionMask;
 140
 141U32 Projectile::smProjectileWarpTicks = 5;
 142
 143
 144//--------------------------------------------------------------------------
 145//
 146ProjectileData::ProjectileData()
 147{
 148   projectileShapeName = NULL;
 149
 150   sound = NULL;
 151
 152   explosion = NULL;
 153   explosionId = 0;
 154
 155   waterExplosion = NULL;
 156   waterExplosionId = 0;
 157
 158   //hasLight = false;
 159   //lightRadius = 1;
 160   //lightColor.set(1, 1, 1);
 161   lightDesc = NULL;
 162
 163   faceViewer = false;
 164   scale.set( 1.0f, 1.0f, 1.0f );
 165
 166   isBallistic = false;
 167
 168   velInheritFactor = 1.0f;
 169   muzzleVelocity = 50;
 170   impactForce = 0.0f;
 171
 172   armingDelay = 0;
 173   fadeDelay = 20000 / 32;
 174   lifetime = 20000 / 32;
 175
 176   activateSeq = -1;
 177   maintainSeq = -1;
 178
 179   gravityMod = 1.0;
 180   bounceElasticity = 0.999f;
 181   bounceFriction = 0.3f;
 182
 183   particleEmitter = NULL;
 184   particleEmitterId = 0;
 185
 186   particleWaterEmitter = NULL;
 187   particleWaterEmitterId = 0;
 188
 189   splash = NULL;
 190   splashId = 0;
 191
 192   decal = NULL;
 193   decalId = 0;
 194
 195   lightDesc = NULL;
 196   lightDescId = 0;
 197}
 198
 199ProjectileData::ProjectileData(const ProjectileData& other, bool temp_clone) : GameBaseData(other, temp_clone)
 200{
 201   projectileShapeName = other.projectileShapeName;
 202   faceViewer = other.faceViewer; // -- always set to false
 203   scale = other.scale;
 204   velInheritFactor = other.velInheritFactor;
 205   muzzleVelocity = other.muzzleVelocity;
 206   impactForce = other.impactForce;
 207   isBallistic = other.isBallistic;
 208   bounceElasticity = other.bounceElasticity;
 209   bounceFriction = other.bounceFriction;
 210   gravityMod = other.gravityMod;
 211   lifetime = other.lifetime;
 212   armingDelay = other.armingDelay;
 213   fadeDelay = other.fadeDelay;
 214   explosion = other.explosion;
 215   explosionId = other.explosionId; // -- for pack/unpack of explosion ptr
 216   waterExplosion = other.waterExplosion;
 217   waterExplosionId = other.waterExplosionId; // -- for pack/unpack of waterExplosion ptr
 218   splash = other.splash;
 219   splashId = other.splashId; // -- for pack/unpack of splash ptr
 220   decal = other.decal;
 221   decalId = other.decalId; // -- for pack/unpack of decal ptr
 222   sound = other.sound;
 223   lightDesc = other.lightDesc;
 224   lightDescId = other.lightDescId; // -- for pack/unpack of lightDesc ptr
 225   projectileShape = other.projectileShape; // -- TSShape loads using projectileShapeName
 226   activateSeq = other.activateSeq; // -- from projectileShape sequence "activate"
 227   maintainSeq = other.maintainSeq; // -- from projectileShape sequence "maintain"
 228   particleEmitter = other.particleEmitter;
 229   particleEmitterId = other.particleEmitterId; // -- for pack/unpack of particleEmitter ptr
 230   particleWaterEmitter = other.particleWaterEmitter;
 231   particleWaterEmitterId = other.particleWaterEmitterId; // -- for pack/unpack of particleWaterEmitter ptr
 232}
 233//--------------------------------------------------------------------------
 234
 235void ProjectileData::initPersistFields()
 236{
 237   addField("particleEmitter", TYPEID< ParticleEmitterData >(), Offset(particleEmitter, ProjectileData),
 238      "@brief Particle emitter datablock used to generate particles while the projectile is outside of water.\n\n"
 239      "@note If datablocks are defined for both particleEmitter and particleWaterEmitter, both effects will play "
 240      "as the projectile enters or leaves water.\n\n"
 241      "@see particleWaterEmitter\n");
 242   addField("particleWaterEmitter", TYPEID< ParticleEmitterData >(), Offset(particleWaterEmitter, ProjectileData),
 243      "@brief Particle emitter datablock used to generate particles while the projectile is submerged in water.\n\n"
 244      "@note If datablocks are defined for both particleWaterEmitter and particleEmitter , both effects will play "
 245      "as the projectile enters or leaves water.\n\n"
 246      "@see particleEmitter\n");
 247
 248   addField("projectileShapeName", TypeShapeFilename, Offset(projectileShapeName, ProjectileData),
 249      "@brief File path to the model of the projectile.\n\n");
 250   addField("scale", TypePoint3F, Offset(scale, ProjectileData),
 251      "@brief Scale to apply to the projectile's size.\n\n"
 252      "@note This is applied after SceneObject::scale\n");
 253
 254   addField("sound", TypeSFXTrackName, Offset(sound, ProjectileData),
 255      "@brief SFXTrack datablock used to play sounds while in flight.\n\n");
 256
 257   addField("explosion", TYPEID< ExplosionData >(), Offset(explosion, ProjectileData),
 258      "@brief Explosion datablock used when the projectile explodes outside of water.\n\n");
 259   addField("waterExplosion", TYPEID< ExplosionData >(), Offset(waterExplosion, ProjectileData),
 260      "@brief Explosion datablock used when the projectile explodes underwater.\n\n");
 261
 262   addField("splash", TYPEID< SplashData >(), Offset(splash, ProjectileData),
 263      "@brief Splash datablock used to create splash effects as the projectile enters or leaves water\n\n");
 264
 265   addField("decal", TYPEID< DecalData >(), Offset(decal, ProjectileData),
 266      "@brief Decal datablock used for decals placed at projectile explosion points.\n\n");
 267
 268   addField("lightDesc", TYPEID< LightDescription >(), Offset(lightDesc, ProjectileData),
 269      "@brief LightDescription datablock used for lights attached to the projectile.\n\n");
 270
 271   addField("isBallistic", TypeBool, Offset(isBallistic, ProjectileData),
 272      "@brief Detetmines if the projectile should be affected by gravity and whether or not "
 273      "it bounces before exploding.\n\n");
 274
 275   addField("velInheritFactor", TypeF32, Offset(velInheritFactor, ProjectileData),
 276      "@brief Amount of velocity the projectile recieves from the source that created it.\n\n"
 277      "Use an amount between 0 and 1 for the best effect. "
 278      "This value is never modified by the engine.\n"
 279      "@note This value by default is not transmitted between the server and the client.");
 280   addField("muzzleVelocity", TypeF32, Offset(muzzleVelocity, ProjectileData),
 281      "@brief Amount of velocity the projectile recieves from the \"muzzle\" of the gun.\n\n"
 282      "Used with velInheritFactor to determine the initial velocity of the projectile. "
 283      "This value is never modified by the engine.\n\n"
 284      "@note This value by default is not transmitted between the server and the client.\n\n"
 285      "@see velInheritFactor");
 286   
 287   addField("impactForce", TypeF32, Offset(impactForce, ProjectileData));
 288
 289   addProtectedField("lifetime", TypeS32, Offset(lifetime, ProjectileData), &setLifetime, &getScaledValue, 
 290      "@brief Amount of time, in milliseconds, before the projectile is removed from the simulation.\n\n"
 291      "Used with fadeDelay to determine the transparency of the projectile at a given time. "
 292      "A projectile may exist up to a maximum of 131040ms (or 4095 ticks) as defined by Projectile::MaxLivingTicks in the source code."
 293      "@see fadeDelay");
 294
 295   addProtectedField("armingDelay", TypeS32, Offset(armingDelay, ProjectileData), &setArmingDelay, &getScaledValue, 
 296      "@brief Amount of time, in milliseconds, before the projectile will cause damage or explode on impact.\n\n"
 297      "This value must be equal to or less than the projectile's lifetime.\n\n"
 298      "@see lifetime");
 299   addProtectedField("fadeDelay", TypeS32, Offset(fadeDelay, ProjectileData), &setFadeDelay, &getScaledValue,
 300      "@brief Amount of time, in milliseconds, before the projectile begins to fade out.\n\n"
 301      "This value must be smaller than the projectile's lifetime to have an affect.");
 302
 303   addField("bounceElasticity", TypeF32, Offset(bounceElasticity, ProjectileData), 
 304      "@brief Influences post-bounce velocity of a projectile that does not explode on contact.\n\n"
 305      "Scales the velocity from a bounce after friction is taken into account. "
 306      "A value of 1.0 will leave it's velocity unchanged while values greater than 1.0 will increase it.\n");
 307   addField("bounceFriction", TypeF32, Offset(bounceFriction, ProjectileData),
 308      "@brief Factor to reduce post-bounce velocity of a projectile that does not explode on contact.\n\n"
 309      "Reduces bounce velocity by this factor and a multiple of the tangent to impact. "
 310      "Used to simulate surface friction.\n");
 311   addField("gravityMod", TypeF32, Offset(gravityMod, ProjectileData ),
 312      "@brief Scales the influence of gravity on the projectile.\n\n"
 313      "The larger this value is, the more that gravity will affect the projectile. "
 314      "A value of 1.0 will assume \"normal\" influence upon it.\n"
 315      "The magnitude of gravity is assumed to be 9.81 m/s/s\n\n"
 316      "@note ProjectileData::isBallistic must be true for this to have any affect.");
 317   // disallow some field substitutions
 318   onlyKeepClearSubstitutions("explosion");
 319   onlyKeepClearSubstitutions("particleEmitter");
 320   onlyKeepClearSubstitutions("particleWaterEmitter");
 321   onlyKeepClearSubstitutions("sound");
 322   onlyKeepClearSubstitutions("splash");
 323   onlyKeepClearSubstitutions("waterExplosion");
 324
 325   Parent::initPersistFields();
 326}
 327
 328
 329//--------------------------------------------------------------------------
 330bool ProjectileData::onAdd()
 331{
 332   if(!Parent::onAdd())
 333      return false;
 334
 335   return true;
 336}
 337
 338
 339bool ProjectileData::preload(bool server, String &errorStr)
 340{
 341   if (Parent::preload(server, errorStr) == false)
 342      return false;
 343      
 344   if( !server )
 345   {
 346      if (!particleEmitter && particleEmitterId != 0)
 347         if (Sim::findObject(particleEmitterId, particleEmitter) == false)
 348            Con::errorf(ConsoleLogEntry::General, "ProjectileData::preload: Invalid packet, bad datablockId(particleEmitter): %d", particleEmitterId);
 349
 350      if (!particleWaterEmitter && particleWaterEmitterId != 0)
 351         if (Sim::findObject(particleWaterEmitterId, particleWaterEmitter) == false)
 352            Con::errorf(ConsoleLogEntry::General, "ProjectileData::preload: Invalid packet, bad datablockId(particleWaterEmitter): %d", particleWaterEmitterId);
 353
 354      if (!explosion && explosionId != 0)
 355         if (Sim::findObject(explosionId, explosion) == false)
 356            Con::errorf(ConsoleLogEntry::General, "ProjectileData::preload: Invalid packet, bad datablockId(explosion): %d", explosionId);
 357
 358      if (!waterExplosion && waterExplosionId != 0)
 359         if (Sim::findObject(waterExplosionId, waterExplosion) == false)
 360            Con::errorf(ConsoleLogEntry::General, "ProjectileData::preload: Invalid packet, bad datablockId(waterExplosion): %d", waterExplosionId);
 361
 362      if (!splash && splashId != 0)
 363         if (Sim::findObject(splashId, splash) == false)
 364            Con::errorf(ConsoleLogEntry::General, "ProjectileData::preload: Invalid packet, bad datablockId(splash): %d", splashId);
 365
 366      if (!decal && decalId != 0)
 367         if (Sim::findObject(decalId, decal) == false)
 368            Con::errorf(ConsoleLogEntry::General, "ProjectileData::preload: Invalid packet, bad datablockId(decal): %d", decalId);
 369
 370      String sfxErrorStr;
 371      if( !sfxResolve( &sound, sfxErrorStr ) )
 372         Con::errorf(ConsoleLogEntry::General, "ProjectileData::preload: Invalid packet: %s", sfxErrorStr.c_str());
 373
 374      if (!lightDesc && lightDescId != 0)
 375         if (Sim::findObject(lightDescId, lightDesc) == false)
 376            Con::errorf(ConsoleLogEntry::General, "ProjectileData::preload: Invalid packet, bad datablockid(lightDesc): %d", lightDescId);   
 377   }
 378
 379   if (projectileShapeName && projectileShapeName[0] != '\0')
 380   {
 381      projectileShape = ResourceManager::get().load(projectileShapeName);
 382      if (bool(projectileShape) == false)
 383      {
 384         errorStr = String::ToString("ProjectileData::load: Couldn't load shape \"%s\"", projectileShapeName);
 385         return false;
 386      }
 387      activateSeq = projectileShape->findSequence("activate");
 388      maintainSeq = projectileShape->findSequence("maintain");
 389   }
 390
 391   if (bool(projectileShape)) // create an instance to preload shape data
 392   {
 393      TSShapeInstance* pDummy = new TSShapeInstance(projectileShape, !server);
 394      delete pDummy;
 395   }
 396
 397   return true;
 398}
 399
 400//--------------------------------------------------------------------------
 401void ProjectileData::packData(BitStream* stream)
 402{
 403   Parent::packData(stream);
 404
 405   stream->writeString(projectileShapeName);
 406   stream->writeFlag(faceViewer);
 407   if(stream->writeFlag(scale.x != 1 || scale.y != 1 || scale.z != 1))
 408   {
 409      stream->write(scale.x);
 410      stream->write(scale.y);
 411      stream->write(scale.z);
 412   }
 413
 414   if (stream->writeFlag(particleEmitter != NULL))
 415      stream->writeRangedU32(particleEmitter->getId(), DataBlockObjectIdFirst,
 416                                                   DataBlockObjectIdLast);
 417
 418   if (stream->writeFlag(particleWaterEmitter != NULL))
 419      stream->writeRangedU32(particleWaterEmitter->getId(), DataBlockObjectIdFirst,
 420                                                   DataBlockObjectIdLast);
 421
 422   if (stream->writeFlag(explosion != NULL))
 423      stream->writeRangedU32(explosion->getId(), DataBlockObjectIdFirst,
 424                                                 DataBlockObjectIdLast);
 425
 426   if (stream->writeFlag(waterExplosion != NULL))
 427      stream->writeRangedU32(waterExplosion->getId(), DataBlockObjectIdFirst,
 428                                                      DataBlockObjectIdLast);
 429
 430   if (stream->writeFlag(splash != NULL))
 431      stream->writeRangedU32(splash->getId(), DataBlockObjectIdFirst,
 432                                              DataBlockObjectIdLast);
 433
 434   if (stream->writeFlag(decal != NULL))
 435      stream->writeRangedU32(decal->getId(), DataBlockObjectIdFirst,
 436                                              DataBlockObjectIdLast);
 437
 438   sfxWrite( stream, sound );
 439
 440   if ( stream->writeFlag(lightDesc != NULL))
 441      stream->writeRangedU32(lightDesc->getId(), DataBlockObjectIdFirst,
 442                                                 DataBlockObjectIdLast);
 443
 444   stream->write(impactForce);
 445   
 446//    stream->writeRangedU32(lifetime, 0, Projectile::MaxLivingTicks);
 447//    stream->writeRangedU32(armingDelay, 0, Projectile::MaxLivingTicks);
 448//    stream->writeRangedU32(fadeDelay, 0, Projectile::MaxLivingTicks);
 449
 450   // [tom, 3/21/2007] Changing these to write all 32 bits as the previous
 451   // code limited these to a max value of 4095.
 452   stream->write(lifetime);
 453   stream->write(armingDelay);
 454   stream->write(fadeDelay);
 455
 456   if(stream->writeFlag(isBallistic))
 457   {
 458      stream->write(gravityMod);
 459      stream->write(bounceElasticity);
 460      stream->write(bounceFriction);
 461   }
 462
 463}
 464
 465void ProjectileData::unpackData(BitStream* stream)
 466{
 467   Parent::unpackData(stream);
 468
 469   projectileShapeName = stream->readSTString();
 470
 471   faceViewer = stream->readFlag();
 472   if(stream->readFlag())
 473   {
 474      stream->read(&scale.x);
 475      stream->read(&scale.y);
 476      stream->read(&scale.z);
 477   }
 478   else
 479      scale.set(1,1,1);
 480
 481   if (stream->readFlag())
 482      particleEmitterId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast);
 483
 484   if (stream->readFlag())
 485      particleWaterEmitterId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast);
 486
 487   if (stream->readFlag())
 488      explosionId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast);
 489
 490   if (stream->readFlag())
 491      waterExplosionId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast);
 492   
 493   if (stream->readFlag())
 494      splashId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast);
 495
 496   if (stream->readFlag())
 497      decalId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast);
 498   
 499   sfxRead( stream, &sound );
 500
 501   if (stream->readFlag())
 502      lightDescId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast);
 503   
 504   // [tom, 3/21/2007] See comment in packData()
 505//    lifetime = stream->readRangedU32(0, Projectile::MaxLivingTicks);
 506//    armingDelay = stream->readRangedU32(0, Projectile::MaxLivingTicks);
 507//    fadeDelay = stream->readRangedU32(0, Projectile::MaxLivingTicks);
 508
 509   stream->read(&impactForce);
 510
 511   stream->read(&lifetime);
 512   stream->read(&armingDelay);
 513   stream->read(&fadeDelay);
 514
 515   isBallistic = stream->readFlag();
 516   if(isBallistic)
 517   {
 518      stream->read(&gravityMod);
 519      stream->read(&bounceElasticity);
 520      stream->read(&bounceFriction);
 521   }
 522}
 523
 524bool ProjectileData::setLifetime( void *obj, const char *index, const char *data )
 525{
 526   S32 value = dAtoi(data);
 527   value = scaleValue(value);
 528   
 529   ProjectileData *object = static_cast<ProjectileData*>(obj);
 530   object->lifetime = value;
 531
 532   return false;
 533}
 534
 535bool ProjectileData::setArmingDelay( void *obj, const char *index, const char *data )
 536{
 537   S32 value = dAtoi(data);
 538   value = scaleValue(value);
 539
 540   ProjectileData *object = static_cast<ProjectileData*>(obj);
 541   object->armingDelay = value;
 542
 543   return false;
 544}
 545
 546bool ProjectileData::setFadeDelay( void *obj, const char *index, const char *data )
 547{
 548   S32 value = dAtoi(data);
 549   value = scaleValue(value);
 550
 551   ProjectileData *object = static_cast<ProjectileData*>(obj);
 552   object->fadeDelay = value;
 553
 554   return false;
 555}
 556
 557const char *ProjectileData::getScaledValue( void *obj, const char *data)
 558{
 559
 560   S32 value = dAtoi(data);
 561   value = scaleValue(value, false);
 562
 563   String stringData = String::ToString(value);
 564   char *strBuffer = Con::getReturnBuffer(stringData.size());
 565   dMemcpy( strBuffer, stringData, stringData.size() );
 566
 567   return strBuffer;
 568}
 569
 570S32 ProjectileData::scaleValue( S32 value, bool down )
 571{
 572   S32 minV = 0;
 573   S32 maxV = Projectile::MaxLivingTicks;
 574   
 575   // scale down to ticks before we validate
 576   if( down )
 577      value /= TickMs;
 578   
 579   if(value < minV || value > maxV)
 580   {
 581      Con::errorf("ProjectileData::scaleValue(S32 value = %d, bool down = %b) - Scaled value must be between %d and %d", value, down, minV, maxV);
 582      if(value < minV)
 583         value = minV;
 584      else if(value > maxV)
 585         value = maxV;
 586   }
 587
 588   // scale up from ticks after we validate
 589   if( !down )
 590      value *= TickMs;
 591
 592   return value;
 593}
 594
 595//--------------------------------------------------------------------------
 596//--------------------------------------
 597//
 598Projectile::Projectile()
 599 : mPhysicsWorld( NULL ),
 600   mDataBlock( NULL ),
 601   mParticleEmitter( NULL ),
 602   mParticleWaterEmitter( NULL ),
 603   mSound( NULL ),
 604   mCurrPosition( 0, 0, 0 ),
 605   mCurrVelocity( 0, 0, 1 ),
 606   mSourceObjectId( -1 ),
 607   mSourceObjectSlot( -1 ),
 608   mCurrTick( 0 ),
 609   mProjectileShape( NULL ),
 610   mActivateThread( NULL ),
 611   mMaintainThread( NULL ),
 612   mHasExploded( false ),
 613   mFadeValue( 1.0f )
 614{
 615   // Todo: ScopeAlways?
 616   mNetFlags.set(Ghostable);
 617   mTypeMask |= ProjectileObjectType | LightObjectType | DynamicShapeObjectType;
 618
 619   mLight = LightManager::createLightInfo();
 620   mLight->setType( LightInfo::Point );   
 621
 622   mLightState.clear();
 623   mLightState.setLightInfo( mLight );
 624
 625   mDataBlock = 0;
 626   ignoreSourceTimeout = false;
 627   dynamicCollisionMask = csmDynamicCollisionMask;
 628   staticCollisionMask = csmStaticCollisionMask;
 629}
 630
 631Projectile::~Projectile()
 632{
 633   SAFE_DELETE(mLight);
 634
 635   delete mProjectileShape;
 636   mProjectileShape = NULL;
 637   if (mDataBlock && mDataBlock->isTempClone())
 638   {
 639      delete mDataBlock;
 640      mDataBlock = 0;
 641   }
 642}
 643
 644//--------------------------------------------------------------------------
 645void Projectile::initPersistFields()
 646{
 647   addGroup("Physics");
 648
 649   addProtectedField("initialPosition",  TypePoint3F, Offset(mInitialPosition, Projectile), &_setInitialPosition, &defaultProtectedGetFn,
 650      "@brief Starting position for the projectile.\n\n");
 651   //addField("initialPosition",  TypePoint3F, Offset(mCurrPosition, Projectile),
 652   //   "@brief Starting position for the projectile.\n\n");
 653   addProtectedField("initialVelocity", TypePoint3F, Offset(mInitialVelocity, Projectile), &_setInitialVelocity, &defaultProtectedGetFn,
 654      "@brief Starting velocity for the projectile.\n\n");
 655   //addField("initialVelocity", TypePoint3F, Offset(mCurrVelocity, Projectile),
 656   //   "@brief Starting velocity for the projectile.\n\n");
 657
 658   endGroup("Physics");
 659
 660   addGroup("Source");
 661
 662   addField("sourceObject",     TypeS32,     Offset(mSourceObjectId, Projectile),
 663      "@brief ID number of the object that fired the projectile.\n\n"
 664      "@note If the projectile was fired by a WeaponImage, sourceObject will be "
 665      "the object that owns the WeaponImage. This is usually the player.");
 666   addField("sourceSlot",       TypeS32,     Offset(mSourceObjectSlot, Projectile),
 667      "@brief The sourceObject's weapon slot that the projectile originates from.\n\n");
 668
 669   addField("ignoreSourceTimeout",  TypeBool,   Offset(ignoreSourceTimeout, Projectile));
 670   endGroup("Source");
 671
 672
 673   Parent::initPersistFields();
 674}
 675
 676bool Projectile::_setInitialPosition( void *object, const char *index, const char *data )
 677{
 678   Projectile* p = static_cast<Projectile*>( object );
 679   if ( p )
 680   {
 681      Point3F pos;
 682
 683      S32 count = dSscanf( data, "%f %f %f", 
 684         &pos.x, &pos.y, &pos.z);
 685      
 686      if ( (count != 3) )
 687      {
 688         Con::printf("Projectile: Failed to parse initial position \"px py pz\" from '%s'", data);
 689         return false;
 690      }
 691
 692      p->setInitialPosition( pos );
 693   }
 694   return false;
 695}
 696
 697void Projectile::setInitialPosition( const Point3F& pos )
 698{
 699   mInitialPosition = pos;
 700   mCurrPosition = pos;
 701}
 702
 703bool Projectile::_setInitialVelocity( void *object, const char *index, const char *data )
 704{
 705   Projectile* p = static_cast<Projectile*>( object );
 706   if ( p )
 707   {
 708      Point3F vel;
 709
 710      S32 count = dSscanf( data, "%f %f %f", 
 711         &vel.x, &vel.y, &vel.z);
 712      
 713      if ( (count != 3) )
 714      {
 715         Con::printf("Projectile: Failed to parse initial velocity \"vx vy vz\" from '%s'", data);
 716         return false;
 717      }
 718
 719      p->setInitialVelocity( vel );
 720   }
 721   return false;
 722}
 723
 724void Projectile::setInitialVelocity( const Point3F& vel )
 725{
 726   mInitialVelocity = vel;
 727   mCurrVelocity = vel;
 728}
 729
 730//--------------------------------------------------------------------------
 731
 732bool Projectile::calculateImpact(float,
 733                                 Point3F& pointOfImpact,
 734                                 float&   impactTime)
 735{
 736   Con::warnf(ConsoleLogEntry::General, "Projectile::calculateImpact: Should never be called");
 737
 738   impactTime = 0;
 739   pointOfImpact.set(0, 0, 0);
 740   return false;
 741}
 742
 743
 744//--------------------------------------------------------------------------
 745F32 Projectile::getUpdatePriority(CameraScopeQuery *camInfo, U32 updateMask, S32 updateSkips)
 746{
 747   F32 ret = Parent::getUpdatePriority(camInfo, updateMask, updateSkips);
 748   // if the camera "owns" this object, it should have a slightly higher priority
 749   if(mSourceObject == camInfo->camera)
 750      return ret + 0.2;
 751   return ret;
 752}
 753
 754bool Projectile::onAdd()
 755{
 756   if(!Parent::onAdd())
 757      return false;
 758
 759   if( !mDataBlock )
 760   {
 761      Con::errorf("Projectile::onAdd - Fail - Not datablock");
 762      return false;
 763   }
 764
 765   if (isServerObject())
 766   {
 767      ShapeBase* ptr;
 768      if (Sim::findObject(mSourceObjectId, ptr))
 769      {
 770         mSourceObject = ptr;
 771
 772         // Since we later do processAfter( mSourceObject ) we must clearProcessAfter
 773         // if it is deleted. SceneObject already handles this in onDeleteNotify so
 774         // all we need to do is register for the notification.
 775         deleteNotify( ptr );
 776      }
 777      else
 778      {
 779         if (mSourceObjectId != -1)
 780            Con::errorf(ConsoleLogEntry::General, "Projectile::onAdd: mSourceObjectId is invalid");
 781         mSourceObject = NULL;
 782      }
 783
 784      // If we're on the server, we need to inherit some of our parent's velocity
 785      //
 786      mCurrTick = 0;
 787   }
 788   else
 789   {
 790      if (bool(mDataBlock->projectileShape))
 791      {
 792         mProjectileShape = new TSShapeInstance(mDataBlock->projectileShape, isClientObject());
 793
 794         if (mDataBlock->activateSeq != -1)
 795         {
 796            mActivateThread = mProjectileShape->addThread();
 797            mProjectileShape->setTimeScale(mActivateThread, 1);
 798            mProjectileShape->setSequence(mActivateThread, mDataBlock->activateSeq, 0);
 799         }
 800      }
 801      if (mDataBlock->particleEmitter != NULL)
 802      {
 803         ParticleEmitter* pEmitter = new ParticleEmitter;
 804         pEmitter->onNewDataBlock(mDataBlock->particleEmitter,false);
 805         if (pEmitter->registerObject() == false)
 806         {
 807            Con::warnf(ConsoleLogEntry::General, "Could not register particle emitter for particle of class: %s", mDataBlock->getName());
 808            delete pEmitter;
 809            pEmitter = NULL;
 810         }
 811         mParticleEmitter = pEmitter;
 812      }
 813
 814      if (mDataBlock->particleWaterEmitter != NULL)
 815      {
 816         ParticleEmitter* pEmitter = new ParticleEmitter;
 817         pEmitter->onNewDataBlock(mDataBlock->particleWaterEmitter,false);
 818         if (pEmitter->registerObject() == false)
 819         {
 820            Con::warnf(ConsoleLogEntry::General, "Could not register particle emitter for particle of class: %s", mDataBlock->getName());
 821            delete pEmitter;
 822            pEmitter = NULL;
 823         }
 824         mParticleWaterEmitter = pEmitter;
 825      }
 826   }
 827   if (mSourceObject.isValid())
 828      processAfter(mSourceObject);
 829
 830   // Setup our bounding box
 831   if (bool(mDataBlock->projectileShape) == true)
 832      mObjBox = mDataBlock->projectileShape->mBounds;
 833   else
 834      mObjBox = Box3F(Point3F(0, 0, 0), Point3F(0, 0, 0));
 835
 836   MatrixF initialTransform( true );
 837   initialTransform.setPosition( mCurrPosition );
 838   setTransform( initialTransform );   // calls resetWorldBox
 839
 840   addToScene();
 841
 842   if ( PHYSICSMGR )
 843      mPhysicsWorld = PHYSICSMGR->getWorld( isServerObject() ? "server" : "client" );
 844
 845   return true;
 846}
 847
 848
 849void Projectile::onRemove()
 850{
 851   if( !mParticleEmitter.isNull() )
 852   {
 853      mParticleEmitter->deleteWhenEmpty();
 854      mParticleEmitter = NULL;
 855   }
 856
 857   if( !mParticleWaterEmitter.isNull() )
 858   {
 859      mParticleWaterEmitter->deleteWhenEmpty();
 860      mParticleWaterEmitter = NULL;
 861   }
 862
 863   SFX_DELETE( mSound );
 864
 865   removeFromScene();
 866   Parent::onRemove();
 867}
 868
 869
 870bool Projectile::onNewDataBlock( GameBaseData *dptr, bool reload )
 871{
 872   mDataBlock = dynamic_cast<ProjectileData*>( dptr );
 873   if ( !mDataBlock || !Parent::onNewDataBlock( dptr, reload ) )
 874      return false;
 875
 876   if ( isGhost() )
 877   {
 878      // Create the sound ahead of time.  This reduces runtime
 879      // costs and makes the system easier to understand.
 880
 881      SFX_DELETE( mSound );
 882
 883      if ( mDataBlock->sound )
 884         mSound = SFX->createSource( mDataBlock->sound );
 885   }
 886
 887   return true;
 888}
 889
 890void Projectile::submitLights( LightManager *lm, bool staticLighting )
 891{
 892   if ( staticLighting || mHasExploded || !mDataBlock->lightDesc )
 893      return;
 894   
 895   mDataBlock->lightDesc->submitLight( &mLightState, getRenderTransform(), lm, this );   
 896}
 897
 898bool Projectile::pointInWater(const Point3F &point)
 899{   
 900   // This is pretty much a hack so we can use the existing ContainerQueryInfo
 901   // and findObject router.
 902   
 903   // We only care if we intersect with water at all 
 904   // so build a box at the point that has only 1 z extent.
 905   // And test if water coverage is anything other than zero.
 906
 907   Box3F boundsBox( point, point );
 908   boundsBox.maxExtents.z += 1.0f;
 909
 910   ContainerQueryInfo info;
 911   info.box = boundsBox;
 912   info.mass = 0.0f;
 913   
 914   // Find and retreive physics info from intersecting WaterObject(s)
 915   if(mContainer != NULL)
 916   {
 917      mContainer->findObjects( boundsBox, WaterObjectType, findRouter, &info );
 918   }
 919   else
 920   {
 921      // Handle special case where the projectile has exploded prior to having
 922      // called onAdd() on the client.  This occurs when the projectile on the
 923      // server is created and then explodes in the same network update tick.
 924      // On the client end in NetConnection::ghostReadPacket() the ghost is
 925      // created and then Projectile::unpackUpdate() is called prior to the
 926      // projectile being registered.  Within unpackUpdate() the explosion
 927      // is triggered, but without being registered onAdd() isn't called and
 928      // the container is not set.  As all we're doing is checking if the
 929      // given explosion point is within water, we should be able to use the
 930      // global container here.  We could likely always get away with this,
 931      // but using the actual defined container when possible is the right
 932      // thing to do.  DAW
 933      AssertFatal(isClientObject(), "Server projectile has not been properly added");
 934      gClientContainer.findObjects( boundsBox, WaterObjectType, findRouter, &info );
 935   }
 936
 937   return ( info.waterCoverage > 0.0f );
 938}
 939
 940//----------------------------------------------------------------------------
 941
 942void Projectile::emitParticles(const Point3F& from, const Point3F& to, const Point3F& vel, const U32 ms)
 943{
 944   if ( mHasExploded )
 945      return;
 946
 947   Point3F axis = -vel;
 948
 949   if( axis.isZero() )
 950      axis.set( 0.0, 0.0, 1.0 );
 951   else
 952      axis.normalize();
 953
 954   bool fromWater = pointInWater(from);
 955   bool toWater   = pointInWater(to);
 956
 957   if (!fromWater && !toWater && bool(mParticleEmitter))                                        // not in water
 958      mParticleEmitter->emitParticles(from, to, axis, vel, ms);
 959   else if (fromWater && toWater && bool(mParticleWaterEmitter))                                // in water
 960      mParticleWaterEmitter->emitParticles(from, to, axis, vel, ms);
 961   else if (!fromWater && toWater && mDataBlock->splash)     // entering water
 962   {
 963      // cast the ray to get the surface point of the water
 964      RayInfo rInfo;
 965      if (gClientContainer.castRay(from, to, WaterObjectType, &rInfo))
 966      {
 967         MatrixF trans = getTransform();
 968         trans.setPosition(rInfo.point);
 969
 970         Splash *splash = new Splash();
 971         splash->onNewDataBlock(mDataBlock->splash, false);
 972         splash->setTransform(trans);
 973         splash->setInitialState(trans.getPosition(), Point3F(0.0, 0.0, 1.0));
 974         if (!splash->registerObject())
 975         {
 976            delete splash;
 977            splash = NULL;
 978         }
 979
 980         // create an emitter for the particles out of water and the particles in water
 981         if (mParticleEmitter)
 982            mParticleEmitter->emitParticles(from, rInfo.point, axis, vel, ms);
 983
 984         if (mParticleWaterEmitter)
 985            mParticleWaterEmitter->emitParticles(rInfo.point, to, axis, vel, ms);
 986      }
 987   }
 988   else if (fromWater && !toWater && mDataBlock->splash)     // leaving water
 989   {
 990      // cast the ray in the opposite direction since that point is out of the water, otherwise
 991      //  we hit water immediately and wont get the appropriate surface point
 992      RayInfo rInfo;
 993      if (gClientContainer.castRay(to, from, WaterObjectType, &rInfo))
 994      {
 995         MatrixF trans = getTransform();
 996         trans.setPosition(rInfo.point);
 997
 998         Splash *splash = new Splash();
 999         splash->onNewDataBlock(mDataBlock->splash,false);
1000         splash->setTransform(trans);
1001         splash->setInitialState(trans.getPosition(), Point3F(0.0, 0.0, 1.0));
1002         if (!splash->registerObject())
1003         {
1004            delete splash;
1005            splash = NULL;
1006         }
1007
1008         // create an emitter for the particles out of water and the particles in water
1009         if (mParticleEmitter)
1010            mParticleEmitter->emitParticles(rInfo.point, to, axis, vel, ms);
1011
1012         if (mParticleWaterEmitter)
1013            mParticleWaterEmitter->emitParticles(from, rInfo.point, axis, vel, ms);
1014      }
1015   }
1016}
1017
1018void Projectile::explode( const Point3F &p, const Point3F &n, const U32 collideType )
1019{
1020   // Make sure we don't explode twice...
1021   if ( mHasExploded )
1022      return;
1023
1024   mHasExploded = true;
1025
1026   // Move the explosion point slightly off the surface to avoid problems with radius damage
1027   Point3F explodePos = p + n * 0.001f;
1028
1029   if ( isServerObject() )
1030   {
1031      // Do what the server needs to do, damage the surrounding objects, etc.
1032      mExplosionPosition = explodePos;
1033      mExplosionNormal = n;
1034      mCollideHitType  = collideType;
1035
1036      mDataBlock->onExplode_callback( this, mExplosionPosition, mFadeValue );
1037
1038      setMaskBits(ExplosionMask);
1039
1040      // Just wait till the timeout to self delete. This 
1041      // gives server object time to get ghosted to the client.
1042   } 
1043   else 
1044   {
1045      // Client just plays the explosion at the right place...
1046      //       
1047      Explosion* pExplosion = NULL;
1048
1049      if (mDataBlock->waterExplosion && pointInWater(p))
1050      {
1051         pExplosion = new Explosion;
1052         pExplosion->onNewDataBlock(mDataBlock->waterExplosion, false);
1053      }
1054      else
1055      if (mDataBlock->explosion)
1056      {
1057         pExplosion = new Explosion;
1058         pExplosion->onNewDataBlock(mDataBlock->explosion, false);
1059      }
1060
1061      if( pExplosion )
1062      {
1063         MatrixF xform(true);
1064         xform.setPosition(explodePos);
1065         pExplosion->setTransform(xform);
1066         pExplosion->setInitialState(explodePos, n);
1067         pExplosion->setCollideType( collideType );
1068         if (pExplosion->registerObject() == false)
1069         {
1070            Con::errorf(ConsoleLogEntry::General, "Projectile(%s)::explode: couldn't register explosion",
1071                        mDataBlock->getName() );
1072            delete pExplosion;
1073            pExplosion = NULL;
1074         }
1075      }
1076
1077      // Client (impact) decal.
1078      if ( mDataBlock->decal )     
1079         gDecalManager->addDecal(p, n, 0.0f, mDataBlock->decal);
1080
1081      // Client object
1082      updateSound();
1083   }
1084
1085   /*
1086   // Client and Server both should apply forces to PhysicsWorld objects
1087   // within the explosion. 
1088   if ( false && mPhysicsWorld )
1089   {
1090      F32 force = 200.0f;
1091      mPhysicsWorld->explosion( p, 15.0f, force );
1092   }
1093   */
1094}
1095
1096void Projectile::updateSound()
1097{
1098   if (!mDataBlock->sound)
1099      return;
1100
1101   if ( mSound )
1102   {
1103      if ( mHasExploded )
1104         mSound->stop();
1105      else
1106      {
1107         if ( !mSound->isPlaying() )
1108            mSound->play();
1109
1110         mSound->setVelocity( getVelocity() );
1111         mSound->setTransform( getRenderTransform() );
1112      }
1113   }
1114}
1115
1116void Projectile::processTick( const Move *move )
1117{
1118   Parent::processTick( move );
1119   mCurrTick++;
1120
1121   simulate( TickSec );
1122}
1123
1124void Projectile::simulate( F32 dt )
1125{         
1126   if ( isServerObject() && mCurrTick >= mDataBlock->lifetime )
1127   {
1128      deleteObject();
1129      return;
1130   }
1131   
1132   if ( mHasExploded )
1133      return;
1134
1135   // ... otherwise, we have to do some simulation work.
1136   RayInfo rInfo;
1137   Point3F oldPosition;
1138   Point3F newPosition;
1139
1140   oldPosition = mCurrPosition;
1141   if ( mDataBlock->isBallistic )
1142      mCurrVelocity.z -= 9.81 * mDataBlock->gravityMod * dt;
1143
1144   newPosition = oldPosition + mCurrVelocity * dt;
1145
1146   // disable the source objects collision reponse for a short time while we
1147   // determine if the projectile is capable of moving from the old position
1148   // to the new position, otherwise we'll hit ourself
1149   bool disableSourceObjCollision = (mSourceObject.isValid() && (ignoreSourceTimeout || mCurrTick <= SourceIdTimeoutTicks));
1150   if ( disableSourceObjCollision )
1151      mSourceObject->disableCollision();
1152   disableCollision();
1153
1154   // Determine if the projectile is going to hit any object between the previous
1155   // position and the new position. This code is executed both on the server
1156   // and on the client (for prediction purposes). It is possible that the server
1157   // will have registered a collision while the client prediction has not. If this
1158   // happens the client will be corrected in the next packet update.
1159
1160   // Raycast the abstract PhysicsWorld if a PhysicsPlugin exists.
1161   bool hit = false;
1162
1163   if ( mPhysicsWorld )
1164      hit = mPhysicsWorld->castRay( oldPosition, newPosition, &rInfo, Point3F( newPosition - oldPosition) * mDataBlock->impactForce );            
1165   else 
1166      hit = getContainer()->castRay(oldPosition, newPosition, dynamicCollisionMask | staticCollisionMask, &rInfo);
1167
1168   if ( hit )
1169   {
1170      // make sure the client knows to bounce
1171      if(isServerObject() && (rInfo.object->getTypeMask() & staticCollisionMask) == 0)
1172         setMaskBits( BounceMask );
1173
1174      MatrixF xform( true );
1175      xform.setColumn( 3, rInfo.point );
1176      setTransform( xform );
1177      mCurrPosition    = rInfo.point;
1178
1179      // Get the object type before the onCollision call, in case
1180      // the object is destroyed.
1181      U32 objectType = rInfo.object->getTypeMask();
1182
1183      // re-enable the collision response on the source object since
1184      // we need to process the onCollision and explode calls
1185      if ( disableSourceObjCollision )
1186         mSourceObject->enableCollision();
1187
1188      // Ok, here is how this works:
1189      // onCollision is called to notify the server scripts that a collision has occurred, then
1190      // a call to explode is made to start the explosion process. The call to explode is made
1191      // twice, once on the server and once on the client.
1192      // The server process is responsible for two things:
1193      //    1) setting the ExplosionMask network bit to guarantee that the client calls explode
1194      //    2) initiate the explosion process on the server scripts
1195      // The client process is responsible for only one thing:
1196      //    1) drawing the appropriate explosion
1197
1198      // It is possible that during the processTick the server may have decided that a hit
1199      // has occurred while the client prediction has decided that a hit has not occurred.
1200      // In this particular scenario the client will have failed to call onCollision and
1201      // explode during the processTick. However, the explode function will be called
1202      // during the next packet update, due to the ExplosionMask network bit being set.
1203      // onCollision will remain uncalled on the client however, therefore no client
1204      // specific code should be placed inside the function!
1205      onCollision( rInfo.point, rInfo.normal, rInfo.object );
1206      // Next order of business: do we explode on this hit?
1207      if ( mCurrTick > mDataBlock->armingDelay || mDataBlock->armingDelay == 0 )
1208      {
1209         mCurrVelocity    = Point3F::Zero;
1210         explode( rInfo.point, rInfo.normal, objectType );
1211      }
1212
1213      if ( mDataBlock->isBallistic )
1214      {
1215         // Otherwise, this represents a bounce.  First, reflect our velocity
1216         //  around the normal...
1217         Point3F bounceVel = mCurrVelocity - rInfo.normal * (mDot( mCurrVelocity, rInfo.normal ) * 2.0);
1218         mCurrVelocity = bounceVel;
1219
1220         // Add in surface friction...
1221         Point3F tangent = bounceVel - rInfo.normal * mDot(bounceVel, rInfo.normal);
1222         mCurrVelocity  -= tangent * mDataBlock->bounceFriction;
1223
1224         // Now, take elasticity into account for modulating the speed of the grenade
1225         mCurrVelocity *= mDataBlock->bounceElasticity;
1226
1227         // Set the new position to the impact and the bounce
1228         // will apply on the next frame.
1229         //F32 timeLeft = 1.0f - rInfo.t;
1230         newPosition = oldPosition = rInfo.point + rInfo.normal * 0.05f;
1231      }
1232      else
1233      {
1234         mCurrVelocity    = Point3F::Zero;
1235      }
1236   }
1237
1238   // re-enable the collision response on the source object now
1239   // that we are done processing the ballistic movement
1240   if ( disableSourceObjCollision )
1241      mSourceObject->enableCollision();
1242   enableCollision();
1243
1244   if ( isClientObject() )
1245   {
1246      emitParticles( mCurrPosition, newPosition, mCurrVelocity, U32( dt * 1000.0f ) );
1247      updateSound();
1248   }
1249
1250   mCurrDeltaBase = newPosition;
1251   mCurrBackDelta = mCurrPosition - newPosition;
1252   mCurrPosition = newPosition;
1253
1254   MatrixF xform( true );
1255   xform.setColumn( 3, mCurrPosition );
1256   setTransform( xform );
1257}
1258
1259
1260void Projectile::advanceTime(F32 dt)
1261{
1262   Parent::advanceTime(dt);
1263
1264   if ( mHasExploded || dt == 0.0)
1265      return;
1266
1267   if (mActivateThread &&
1268         mProjectileShape->getDuration(mActivateThread) > mProjectileShape->getTime(mActivateThread) + dt)
1269   {
1270      mProjectileShape->advanceTime(dt, mActivateThread);
1271   }
1272   else
1273   {
1274
1275      if (mMaintainThread)
1276      {
1277         mProjectileShape->advanceTime(dt, mMaintainThread);
1278      }
1279      else if (mActivateThread && mDataBlock->maintainSeq != -1)
1280      {
1281         mMaintainThread = mProjectileShape->addThread();
1282         mProjectileShape->setTimeScale(mMaintainThread, 1);
1283         mProjectileShape->setSequence(mMaintainThread, mDataBlock->maintainSeq, 0);
1284         mProjectileShape->advanceTime(dt, mMaintainThread);
1285      }
1286   }
1287}
1288
1289void Projectile::interpolateTick(F32 delta)
1290{
1291   Parent::interpolateTick(delta);
1292
1293   if( mHasExploded )
1294      return;
1295
1296   Point3F interpPos = mCurrDeltaBase + mCurrBackDelta * delta;
1297   Point3F dir = mCurrVelocity;
1298   if(dir.isZero())
1299      dir.set(0,0,1);
1300   else
1301      dir.normalize();
1302
1303   MatrixF xform(true);
1304   xform = MathUtils::createOrientFromDir(dir);
1305   xform.setPosition(interpPos);
1306   setRenderTransform(xform);
1307
1308   // fade out the projectile image
1309   S32 time = (S32)(mCurrTick - delta);
1310   if(time > mDataBlock->fadeDelay)
1311   {
1312      F32 fade = F32(time - mDataBlock->fadeDelay);
1313      mFadeValue = 1.0 - (fade / F32(mDataBlock->lifetime));
1314   }
1315   else
1316      mFadeValue = 1.0;
1317
1318   updateSound();
1319}
1320
1321
1322
1323//--------------------------------------------------------------------------
1324void Projectile::onCollision(const Point3F& hitPosition, const Point3F& hitNormal, SceneObject* hitObject)
1325{
1326   // No client specific code should be placed or branched from this function
1327   if(isClientObject())
1328      return;
1329
1330   if (hitObject != NULL && isServerObject())
1331   {
1332      mDataBlock->onCollision_callback( this, hitObject, mFadeValue, hitPosition, hitNormal );
1333   }
1334}
1335
1336//--------------------------------------------------------------------------
1337U32 Projectile::packUpdate( NetConnection *con, U32 mask, BitStream *stream )
1338{
1339   U32 retMask = Parent::packUpdate( con, mask, stream );
1340
1341   const bool isInitalUpdate = mask & GameBase::InitialUpdateMask;
1342
1343   // InitialUpdateMask
1344   if ( stream->writeFlag( isInitalUpdate ) )
1345   {
1346      stream->writeRangedU32( mCurrTick, 0, MaxLivingTicks );
1347
1348      if ( mSourceObject.isValid() )
1349      {
1350         // Potentially have to write this to the client, let's make sure it has a
1351         //  ghost on the other side...
1352         S32 ghostIndex = con->getGhostIndex( mSourceObject );
1353         if ( stream->writeFlag( ghostIndex != -1 ) )
1354         {
1355            stream->writeRangedU32( U32(ghostIndex), 
1356                                    0, 
1357                                    NetConnection::MaxGhostCount );
1358
1359            stream->writeRangedU32( U32(mSourceObjectSlot),
1360                                    0, 
1361                                    ShapeBase::MaxMountedImages - 1 );
1362            stream->writeFlag(ignoreSourceTimeout);
1363         }
1364         else 
1365            // have not recieved the ghost for the source object yet, try again later
1366            retMask |= GameBase::InitialUpdateMask;
1367      }
1368      else
1369         stream->writeFlag( false );
1370   }
1371
1372   // ExplosionMask
1373   //
1374   // ExplosionMask will be set during the initial update but hidden is
1375   // only true if we have really exploded.
1376   if ( stream->writeFlag( ( mask & ExplosionMask ) && mHasExploded ) )
1377   {
1378      mathWrite(*stream, mExplosionPosition);
1379      mathWrite(*stream, mExplosionNormal);
1380      stream->write(mCollideHitType);
1381   }
1382
1383   // BounceMask
1384   if ( stream->writeFlag( mask & BounceMask ) )
1385   {
1386      // Bounce against dynamic object
1387      mathWrite(*stream, mCurrPosition);
1388      mathWrite(*stream, mCurrVelocity);
1389   }
1390
1391   return retMask;
1392}
1393
1394void Projectile::unpackUpdate(NetConnection* con, BitStream* stream)
1395{
1396   Parent::unpackUpdate(con, stream);
1397   
1398   if ( stream->readFlag() ) // InitialUpdateMask
1399   {
1400      mCurrTick = stream->readRangedU32( 0, MaxLivingTicks );
1401      if ( stream->readFlag() )
1402      {
1403         mSourceObjectId   = stream->readRangedU32( 0, NetConnection::MaxGhostCount );
1404         mSourceObjectSlot = stream->readRangedU32( 0, ShapeBase::MaxMountedImages - 1 );
1405
1406         ignoreSourceTimeout = stream->readFlag();
1407         NetObject* pObject = con->resolveGhost( mSourceObjectId );
1408         if ( pObject != NULL )
1409            mSourceObject = dynamic_cast<ShapeBase*>( pObject );
1410      }
1411      else
1412      {
1413         mSourceObjectId   = -1;
1414         mSourceObjectSlot = -1;
1415         mSourceObject     = NULL;
1416      }
1417   }
1418   
1419   if ( stream->readFlag() ) // ExplosionMask
1420   {
1421      Point3F explodePoint;
1422      Point3F explodeNormal;
1423      mathRead( *stream, &explodePoint );
1424      mathRead( *stream, &explodeNormal );
1425      stream->read( &mCollideHitType );
1426
1427      // start the explosion visuals
1428      explode( explodePoint, explodeNormal, mCollideHitType );
1429   }
1430
1431   if ( stream->readFlag() ) // BounceMask
1432   {
1433      Point3F pos;
1434      mathRead( *stream, &pos );
1435      mathRead( *stream, &mCurrVelocity );
1436
1437      mCurrDeltaBase = pos;
1438      mCurrBackDelta = mCurrPosition - pos;
1439      mCurrPosition = pos;
1440      setPosition( mCurrPosition );
1441   }
1442}
1443
1444//--------------------------------------------------------------------------
1445void Projectile::prepRenderImage( SceneRenderState* state )
1446{
1447   if (mHasExploded || mFadeValue <= (1.0/255.0))
1448      return;
1449
1450   if ( mDataBlock->lightDesc )
1451   {
1452      mDataBlock->lightDesc->prepRender( state, &mLightState, getRenderTransform() );
1453   }
1454
1455   /*
1456   if ( mFlareData )
1457   {
1458      mFlareState.fullBrightness = mDataBlock->lightDesc->mBrightness;
1459      mFlareState.scale = mFlareScale;
1460      mFlareState.lightInfo = mLight;
1461      mFlareState.lightMat = getTransform();
1462
1463      mFlareData->prepRender( state, &mFlareState );
1464   }
1465   */
1466
1467   prepBatchRender( state );
1468}
1469
1470void Projectile::prepBatchRender( SceneRenderState *state )
1471{
1472   if ( !mProjectileShape )
1473      return;
1474
1475   GFXTransformSaver saver;
1476
1477   // Set up our TS render state.
1478   TSRenderState rdata;
1479   rdata.setSceneState( state );
1480
1481   // We might have some forward lit materials
1482   // so pass down a query to gather lights.
1483   LightQuery query;
1484   query.init( getWorldSphere() );
1485   rdata.setLightQuery( &query );
1486
1487   MatrixF mat = getRenderTransform();
1488   mat.scale( mObjScale );
1489   mat.scale( mDataBlock->scale );
1490   GFX->setWorldMatrix( mat );
1491
1492   mProjectileShape->setDetailFromPosAndScale( state, mat.getPosition(), mObjScale );
1493   mProjectileShape->animate();
1494
1495   mProjectileShape->render( rdata );
1496}
1497
1498DefineEngineMethod(Projectile, presimulate, void, (F32 seconds), (1.0f), 
1499                                       "@brief Updates the projectile's positional and collision information.\n\n"
1500                                       "This function will first delete the projectile if it is a server object and is outside it's ProjectileData::lifetime. "
1501                                       "Also responsible for applying gravity, determining collisions, triggering explosions, "
1502                                       "emitting trail particles, and calculating bounces if necessary."
1503                                       "@param seconds Amount of time, in seconds since the simulation's start, to advance.\n"
1504                                       "@tsexample\n"
1505                                          "// Tell the projectile to process a simulation event, and provide the amount of time\n"
1506                                          "// that has passed since the simulation began.\n"
1507                                          "%seconds = 2.0;\n"
1508                                          "%projectile.presimulate(%seconds);\n"
1509                                       "@endtsexample\n"
1510                                       "@note This function is not called if the SimObject::hidden is true.")
1511{
1512   object->simulate( seconds );
1513}
1514