projectile.cpp
Engine/source/T3D/projectile.cpp
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