Projectile.cpp 65 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394
  1. /*
  2. ===========================================================================
  3. Doom 3 GPL Source Code
  4. Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
  5. This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?).
  6. Doom 3 Source Code is free software: you can redistribute it and/or modify
  7. it under the terms of the GNU General Public License as published by
  8. the Free Software Foundation, either version 3 of the License, or
  9. (at your option) any later version.
  10. Doom 3 Source Code is distributed in the hope that it will be useful,
  11. but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. GNU General Public License for more details.
  14. You should have received a copy of the GNU General Public License
  15. along with Doom 3 Source Code. If not, see <http://www.gnu.org/licenses/>.
  16. In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below.
  17. If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
  18. ===========================================================================
  19. */
  20. #include "../idlib/precompiled.h"
  21. #pragma hdrstop
  22. #include "Game_local.h"
  23. /*
  24. ===============================================================================
  25. idProjectile
  26. ===============================================================================
  27. */
  28. static const int BFG_DAMAGE_FREQUENCY = 333;
  29. static const float BOUNCE_SOUND_MIN_VELOCITY = 200.0f;
  30. static const float BOUNCE_SOUND_MAX_VELOCITY = 400.0f;
  31. const idEventDef EV_Explode( "<explode>", NULL );
  32. const idEventDef EV_Fizzle( "<fizzle>", NULL );
  33. const idEventDef EV_RadiusDamage( "<radiusdmg>", "e" );
  34. const idEventDef EV_GetProjectileState( "getProjectileState", NULL, 'd' );
  35. CLASS_DECLARATION( idEntity, idProjectile )
  36. EVENT( EV_Explode, idProjectile::Event_Explode )
  37. EVENT( EV_Fizzle, idProjectile::Event_Fizzle )
  38. EVENT( EV_Touch, idProjectile::Event_Touch )
  39. EVENT( EV_RadiusDamage, idProjectile::Event_RadiusDamage )
  40. EVENT( EV_GetProjectileState, idProjectile::Event_GetProjectileState )
  41. END_CLASS
  42. /*
  43. ================
  44. idProjectile::idProjectile
  45. ================
  46. */
  47. idProjectile::idProjectile( void ) {
  48. owner = NULL;
  49. lightDefHandle = -1;
  50. thrust = 0.0f;
  51. thrust_end = 0;
  52. smokeFly = NULL;
  53. smokeFlyTime = 0;
  54. state = SPAWNED;
  55. lightOffset = vec3_zero;
  56. lightStartTime = 0;
  57. lightEndTime = 0;
  58. lightColor = vec3_zero;
  59. state = SPAWNED;
  60. damagePower = 1.0f;
  61. memset( &projectileFlags, 0, sizeof( projectileFlags ) );
  62. memset( &renderLight, 0, sizeof( renderLight ) );
  63. // note: for net_instanthit projectiles, we will force this back to false at spawn time
  64. fl.networkSync = true;
  65. netSyncPhysics = false;
  66. }
  67. /*
  68. ================
  69. idProjectile::Spawn
  70. ================
  71. */
  72. void idProjectile::Spawn( void ) {
  73. physicsObj.SetSelf( this );
  74. physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f );
  75. physicsObj.SetContents( 0 );
  76. physicsObj.SetClipMask( 0 );
  77. physicsObj.PutToRest();
  78. SetPhysics( &physicsObj );
  79. }
  80. /*
  81. ================
  82. idProjectile::Save
  83. ================
  84. */
  85. void idProjectile::Save( idSaveGame *savefile ) const {
  86. owner.Save( savefile );
  87. projectileFlags_s flags = projectileFlags;
  88. LittleBitField( &flags, sizeof( flags ) );
  89. savefile->Write( &flags, sizeof( flags ) );
  90. savefile->WriteFloat( thrust );
  91. savefile->WriteInt( thrust_end );
  92. savefile->WriteRenderLight( renderLight );
  93. savefile->WriteInt( (int)lightDefHandle );
  94. savefile->WriteVec3( lightOffset );
  95. savefile->WriteInt( lightStartTime );
  96. savefile->WriteInt( lightEndTime );
  97. savefile->WriteVec3( lightColor );
  98. savefile->WriteParticle( smokeFly );
  99. savefile->WriteInt( smokeFlyTime );
  100. savefile->WriteInt( (int)state );
  101. savefile->WriteFloat( damagePower );
  102. savefile->WriteStaticObject( physicsObj );
  103. savefile->WriteStaticObject( thruster );
  104. }
  105. /*
  106. ================
  107. idProjectile::Restore
  108. ================
  109. */
  110. void idProjectile::Restore( idRestoreGame *savefile ) {
  111. owner.Restore( savefile );
  112. savefile->Read( &projectileFlags, sizeof( projectileFlags ) );
  113. LittleBitField( &projectileFlags, sizeof( projectileFlags ) );
  114. savefile->ReadFloat( thrust );
  115. savefile->ReadInt( thrust_end );
  116. savefile->ReadRenderLight( renderLight );
  117. savefile->ReadInt( (int &)lightDefHandle );
  118. savefile->ReadVec3( lightOffset );
  119. savefile->ReadInt( lightStartTime );
  120. savefile->ReadInt( lightEndTime );
  121. savefile->ReadVec3( lightColor );
  122. savefile->ReadParticle( smokeFly );
  123. savefile->ReadInt( smokeFlyTime );
  124. savefile->ReadInt( (int &)state );
  125. savefile->ReadFloat( damagePower );
  126. savefile->ReadStaticObject( physicsObj );
  127. RestorePhysics( &physicsObj );
  128. savefile->ReadStaticObject( thruster );
  129. thruster.SetPhysics( &physicsObj );
  130. if ( smokeFly != NULL ) {
  131. idVec3 dir;
  132. dir = physicsObj.GetLinearVelocity();
  133. dir.NormalizeFast();
  134. gameLocal.smokeParticles->EmitSmoke( smokeFly, gameLocal.time, gameLocal.random.RandomFloat(), GetPhysics()->GetOrigin(), GetPhysics()->GetAxis() );
  135. }
  136. }
  137. /*
  138. ================
  139. idProjectile::GetOwner
  140. ================
  141. */
  142. idEntity *idProjectile::GetOwner( void ) const {
  143. return owner.GetEntity();
  144. }
  145. /*
  146. ================
  147. idProjectile::Create
  148. ================
  149. */
  150. void idProjectile::Create( idEntity *owner, const idVec3 &start, const idVec3 &dir ) {
  151. idDict args;
  152. idStr shaderName;
  153. idVec3 light_color;
  154. idVec3 light_offset;
  155. idVec3 tmp;
  156. idMat3 axis;
  157. Unbind();
  158. // align z-axis of model with the direction
  159. axis = dir.ToMat3();
  160. tmp = axis[2];
  161. axis[2] = axis[0];
  162. axis[0] = -tmp;
  163. physicsObj.SetOrigin( start );
  164. physicsObj.SetAxis( axis );
  165. physicsObj.GetClipModel()->SetOwner( owner );
  166. this->owner = owner;
  167. memset( &renderLight, 0, sizeof( renderLight ) );
  168. shaderName = spawnArgs.GetString( "mtr_light_shader" );
  169. if ( *(const char *)shaderName ) {
  170. renderLight.shader = declManager->FindMaterial( shaderName, false );
  171. renderLight.pointLight = true;
  172. renderLight.lightRadius[0] =
  173. renderLight.lightRadius[1] =
  174. renderLight.lightRadius[2] = spawnArgs.GetFloat( "light_radius" );
  175. spawnArgs.GetVector( "light_color", "1 1 1", light_color );
  176. renderLight.shaderParms[0] = light_color[0];
  177. renderLight.shaderParms[1] = light_color[1];
  178. renderLight.shaderParms[2] = light_color[2];
  179. renderLight.shaderParms[3] = 1.0f;
  180. }
  181. spawnArgs.GetVector( "light_offset", "0 0 0", lightOffset );
  182. lightStartTime = 0;
  183. lightEndTime = 0;
  184. smokeFlyTime = 0;
  185. damagePower = 1.0f;
  186. UpdateVisuals();
  187. state = CREATED;
  188. if ( spawnArgs.GetBool( "net_fullphysics" ) ) {
  189. netSyncPhysics = true;
  190. }
  191. }
  192. /*
  193. =================
  194. idProjectile::~idProjectile
  195. =================
  196. */
  197. idProjectile::~idProjectile() {
  198. StopSound( SND_CHANNEL_ANY, false );
  199. FreeLightDef();
  200. }
  201. /*
  202. =================
  203. idProjectile::FreeLightDef
  204. =================
  205. */
  206. void idProjectile::FreeLightDef( void ) {
  207. if ( lightDefHandle != -1 ) {
  208. gameRenderWorld->FreeLightDef( lightDefHandle );
  209. lightDefHandle = -1;
  210. }
  211. }
  212. /*
  213. =================
  214. idProjectile::Launch
  215. =================
  216. */
  217. void idProjectile::Launch( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity, const float timeSinceFire, const float launchPower, const float dmgPower ) {
  218. float fuse;
  219. float startthrust;
  220. float endthrust;
  221. idVec3 velocity;
  222. idAngles angular_velocity;
  223. float linear_friction;
  224. float angular_friction;
  225. float contact_friction;
  226. float bounce;
  227. float mass;
  228. float speed;
  229. float gravity;
  230. idVec3 gravVec;
  231. idVec3 tmp;
  232. idMat3 axis;
  233. int thrust_start;
  234. int contents;
  235. int clipMask;
  236. // allow characters to throw projectiles during cinematics, but not the player
  237. if ( owner.GetEntity() && !owner.GetEntity()->IsType( idPlayer::Type ) ) {
  238. cinematic = owner.GetEntity()->cinematic;
  239. } else {
  240. cinematic = false;
  241. }
  242. thrust = spawnArgs.GetFloat( "thrust" );
  243. startthrust = spawnArgs.GetFloat( "thrust_start" );
  244. endthrust = spawnArgs.GetFloat( "thrust_end" );
  245. spawnArgs.GetVector( "velocity", "0 0 0", velocity );
  246. speed = velocity.Length() * launchPower;
  247. damagePower = dmgPower;
  248. spawnArgs.GetAngles( "angular_velocity", "0 0 0", angular_velocity );
  249. linear_friction = spawnArgs.GetFloat( "linear_friction" );
  250. angular_friction = spawnArgs.GetFloat( "angular_friction" );
  251. contact_friction = spawnArgs.GetFloat( "contact_friction" );
  252. bounce = spawnArgs.GetFloat( "bounce" );
  253. mass = spawnArgs.GetFloat( "mass" );
  254. gravity = spawnArgs.GetFloat( "gravity" );
  255. fuse = spawnArgs.GetFloat( "fuse" );
  256. projectileFlags.detonate_on_world = spawnArgs.GetBool( "detonate_on_world" );
  257. projectileFlags.detonate_on_actor = spawnArgs.GetBool( "detonate_on_actor" );
  258. projectileFlags.randomShaderSpin = spawnArgs.GetBool( "random_shader_spin" );
  259. if ( mass <= 0 ) {
  260. gameLocal.Error( "Invalid mass on '%s'\n", GetEntityDefName() );
  261. }
  262. thrust *= mass;
  263. thrust_start = SEC2MS( startthrust ) + gameLocal.time;
  264. thrust_end = SEC2MS( endthrust ) + gameLocal.time;
  265. lightStartTime = 0;
  266. lightEndTime = 0;
  267. if ( health ) {
  268. fl.takedamage = true;
  269. }
  270. gravVec = gameLocal.GetGravity();
  271. gravVec.NormalizeFast();
  272. Unbind();
  273. // align z-axis of model with the direction
  274. axis = dir.ToMat3();
  275. tmp = axis[2];
  276. axis[2] = axis[0];
  277. axis[0] = -tmp;
  278. contents = 0;
  279. clipMask = MASK_SHOT_RENDERMODEL;
  280. if ( spawnArgs.GetBool( "detonate_on_trigger" ) ) {
  281. contents |= CONTENTS_TRIGGER;
  282. }
  283. if ( !spawnArgs.GetBool( "no_contents" ) ) {
  284. contents |= CONTENTS_PROJECTILE;
  285. clipMask |= CONTENTS_PROJECTILE;
  286. }
  287. // don't do tracers on client, we don't know origin and direction
  288. if ( spawnArgs.GetBool( "tracers" ) && gameLocal.random.RandomFloat() > 0.5f ) {
  289. SetModel( spawnArgs.GetString( "model_tracer" ) );
  290. projectileFlags.isTracer = true;
  291. }
  292. physicsObj.SetMass( mass );
  293. physicsObj.SetFriction( linear_friction, angular_friction, contact_friction );
  294. if ( contact_friction == 0.0f ) {
  295. physicsObj.NoContact();
  296. }
  297. physicsObj.SetBouncyness( bounce );
  298. physicsObj.SetGravity( gravVec * gravity );
  299. physicsObj.SetContents( contents );
  300. physicsObj.SetClipMask( clipMask );
  301. physicsObj.SetLinearVelocity( axis[ 2 ] * speed + pushVelocity );
  302. physicsObj.SetAngularVelocity( angular_velocity.ToAngularVelocity() * axis );
  303. physicsObj.SetOrigin( start );
  304. physicsObj.SetAxis( axis );
  305. thruster.SetPosition( &physicsObj, 0, idVec3( GetPhysics()->GetBounds()[ 0 ].x, 0, 0 ) );
  306. if ( !gameLocal.isClient ) {
  307. if ( fuse <= 0 ) {
  308. // run physics for 1 second
  309. RunPhysics();
  310. PostEventMS( &EV_Remove, spawnArgs.GetInt( "remove_time", "1500" ) );
  311. } else if ( spawnArgs.GetBool( "detonate_on_fuse" ) ) {
  312. fuse -= timeSinceFire;
  313. if ( fuse < 0.0f ) {
  314. fuse = 0.0f;
  315. }
  316. PostEventSec( &EV_Explode, fuse );
  317. } else {
  318. fuse -= timeSinceFire;
  319. if ( fuse < 0.0f ) {
  320. fuse = 0.0f;
  321. }
  322. PostEventSec( &EV_Fizzle, fuse );
  323. }
  324. }
  325. if ( projectileFlags.isTracer ) {
  326. StartSound( "snd_tracer", SND_CHANNEL_BODY, 0, false, NULL );
  327. } else {
  328. StartSound( "snd_fly", SND_CHANNEL_BODY, 0, false, NULL );
  329. }
  330. smokeFlyTime = 0;
  331. const char *smokeName = spawnArgs.GetString( "smoke_fly" );
  332. if ( *smokeName != '\0' ) {
  333. smokeFly = static_cast<const idDeclParticle *>( declManager->FindType( DECL_PARTICLE, smokeName ) );
  334. smokeFlyTime = gameLocal.time;
  335. }
  336. // used for the plasma bolts but may have other uses as well
  337. if ( projectileFlags.randomShaderSpin ) {
  338. float f = gameLocal.random.RandomFloat();
  339. f *= 0.5f;
  340. renderEntity.shaderParms[SHADERPARM_DIVERSITY] = f;
  341. }
  342. UpdateVisuals();
  343. state = LAUNCHED;
  344. }
  345. /*
  346. ================
  347. idProjectile::Think
  348. ================
  349. */
  350. void idProjectile::Think( void ) {
  351. if ( thinkFlags & TH_THINK ) {
  352. if ( thrust && ( gameLocal.time < thrust_end ) ) {
  353. // evaluate force
  354. thruster.SetForce( GetPhysics()->GetAxis()[ 0 ] * thrust );
  355. thruster.Evaluate( gameLocal.time );
  356. }
  357. }
  358. // run physics
  359. RunPhysics();
  360. Present();
  361. // add the particles
  362. if ( smokeFly != NULL && smokeFlyTime && !IsHidden() ) {
  363. idVec3 dir = -GetPhysics()->GetLinearVelocity();
  364. dir.Normalize();
  365. if ( !gameLocal.smokeParticles->EmitSmoke( smokeFly, smokeFlyTime, gameLocal.random.RandomFloat(), GetPhysics()->GetOrigin(), dir.ToMat3() ) ) {
  366. smokeFlyTime = gameLocal.time;
  367. }
  368. }
  369. // add the light
  370. if ( renderLight.lightRadius.x > 0.0f && g_projectileLights.GetBool() ) {
  371. renderLight.origin = GetPhysics()->GetOrigin() + GetPhysics()->GetAxis() * lightOffset;
  372. renderLight.axis = GetPhysics()->GetAxis();
  373. if ( ( lightDefHandle != -1 ) ) {
  374. if ( lightEndTime > 0 && gameLocal.time <= lightEndTime + gameLocal.GetMSec() ) {
  375. idVec3 color( 0, 0, 0 );
  376. if ( gameLocal.time < lightEndTime ) {
  377. float frac = ( float )( gameLocal.time - lightStartTime ) / ( float )( lightEndTime - lightStartTime );
  378. color.Lerp( lightColor, color, frac );
  379. }
  380. renderLight.shaderParms[SHADERPARM_RED] = color.x;
  381. renderLight.shaderParms[SHADERPARM_GREEN] = color.y;
  382. renderLight.shaderParms[SHADERPARM_BLUE] = color.z;
  383. }
  384. gameRenderWorld->UpdateLightDef( lightDefHandle, &renderLight );
  385. } else {
  386. lightDefHandle = gameRenderWorld->AddLightDef( &renderLight );
  387. }
  388. }
  389. }
  390. /*
  391. =================
  392. idProjectile::Collide
  393. =================
  394. */
  395. bool idProjectile::Collide( const trace_t &collision, const idVec3 &velocity ) {
  396. idEntity *ent;
  397. idEntity *ignore;
  398. const char *damageDefName;
  399. idVec3 dir;
  400. float push;
  401. float damageScale;
  402. if ( state == EXPLODED || state == FIZZLED ) {
  403. return true;
  404. }
  405. // predict the explosion
  406. if ( gameLocal.isClient ) {
  407. if ( ClientPredictionCollide( this, spawnArgs, collision, velocity, !spawnArgs.GetBool( "net_instanthit" ) ) ) {
  408. Explode( collision, NULL );
  409. return true;
  410. }
  411. return false;
  412. }
  413. // remove projectile when a 'noimpact' surface is hit
  414. if ( ( collision.c.material != NULL ) && ( collision.c.material->GetSurfaceFlags() & SURF_NOIMPACT ) ) {
  415. PostEventMS( &EV_Remove, 0 );
  416. common->DPrintf( "Projectile collision no impact\n" );
  417. return true;
  418. }
  419. // get the entity the projectile collided with
  420. ent = gameLocal.entities[ collision.c.entityNum ];
  421. if ( ent == owner.GetEntity() ) {
  422. assert( 0 );
  423. return true;
  424. }
  425. // just get rid of the projectile when it hits a player in noclip
  426. if ( ent->IsType( idPlayer::Type ) && static_cast<idPlayer *>( ent )->noclip ) {
  427. PostEventMS( &EV_Remove, 0 );
  428. return true;
  429. }
  430. // direction of projectile
  431. dir = velocity;
  432. dir.Normalize();
  433. // projectiles can apply an additional impulse next to the rigid body physics impulse
  434. if ( spawnArgs.GetFloat( "push", "0", push ) && push > 0.0f ) {
  435. ent->ApplyImpulse( this, collision.c.id, collision.c.point, push * dir );
  436. }
  437. // MP: projectiles open doors
  438. if ( gameLocal.isMultiplayer && ent->IsType( idDoor::Type ) && !static_cast< idDoor * >(ent)->IsOpen() && !ent->spawnArgs.GetBool( "no_touch" ) ) {
  439. ent->ProcessEvent( &EV_Activate , this );
  440. }
  441. if ( ent->IsType( idActor::Type ) || ( ent->IsType( idAFAttachment::Type ) && static_cast<const idAFAttachment*>(ent)->GetBody()->IsType( idActor::Type ) ) ) {
  442. if ( !projectileFlags.detonate_on_actor ) {
  443. return false;
  444. }
  445. } else {
  446. if ( !projectileFlags.detonate_on_world ) {
  447. if ( !StartSound( "snd_ricochet", SND_CHANNEL_ITEM, 0, true, NULL ) ) {
  448. float len = velocity.Length();
  449. if ( len > BOUNCE_SOUND_MIN_VELOCITY ) {
  450. SetSoundVolume( len > BOUNCE_SOUND_MAX_VELOCITY ? 1.0f : idMath::Sqrt( len - BOUNCE_SOUND_MIN_VELOCITY ) * ( 1.0f / idMath::Sqrt( BOUNCE_SOUND_MAX_VELOCITY - BOUNCE_SOUND_MIN_VELOCITY ) ) );
  451. StartSound( "snd_bounce", SND_CHANNEL_ANY, 0, true, NULL );
  452. }
  453. }
  454. return false;
  455. }
  456. }
  457. SetOrigin( collision.endpos );
  458. SetAxis( collision.endAxis );
  459. // unlink the clip model because we no longer need it
  460. GetPhysics()->UnlinkClip();
  461. damageDefName = spawnArgs.GetString( "def_damage" );
  462. ignore = NULL;
  463. // if the hit entity takes damage
  464. if ( ent->fl.takedamage ) {
  465. if ( damagePower ) {
  466. damageScale = damagePower;
  467. } else {
  468. damageScale = 1.0f;
  469. }
  470. // if the projectile owner is a player
  471. if ( owner.GetEntity() && owner.GetEntity()->IsType( idPlayer::Type ) ) {
  472. // if the projectile hit an actor
  473. if ( ent->IsType( idActor::Type ) ) {
  474. idPlayer *player = static_cast<idPlayer *>( owner.GetEntity() );
  475. player->AddProjectileHits( 1 );
  476. damageScale *= player->PowerUpModifier( PROJECTILE_DAMAGE );
  477. }
  478. }
  479. if ( damageDefName[0] != '\0' ) {
  480. ent->Damage( this, owner.GetEntity(), dir, damageDefName, damageScale, CLIPMODEL_ID_TO_JOINT_HANDLE( collision.c.id ) );
  481. ignore = ent;
  482. }
  483. }
  484. // if the projectile causes a damage effect
  485. if ( spawnArgs.GetBool( "impact_damage_effect" ) ) {
  486. // if the hit entity has a special damage effect
  487. if ( ent->spawnArgs.GetBool( "bleed" ) ) {
  488. ent->AddDamageEffect( collision, velocity, damageDefName );
  489. } else {
  490. AddDefaultDamageEffect( collision, velocity );
  491. }
  492. }
  493. Explode( collision, ignore );
  494. return true;
  495. }
  496. /*
  497. =================
  498. idProjectile::DefaultDamageEffect
  499. =================
  500. */
  501. void idProjectile::DefaultDamageEffect( idEntity *soundEnt, const idDict &projectileDef, const trace_t &collision, const idVec3 &velocity ) {
  502. const char *decal, *sound, *typeName;
  503. surfTypes_t materialType;
  504. if ( collision.c.material != NULL ) {
  505. materialType = collision.c.material->GetSurfaceType();
  506. } else {
  507. materialType = SURFTYPE_METAL;
  508. }
  509. // get material type name
  510. typeName = gameLocal.sufaceTypeNames[ materialType ];
  511. // play impact sound
  512. sound = projectileDef.GetString( va( "snd_%s", typeName ) );
  513. if ( *sound == '\0' ) {
  514. sound = projectileDef.GetString( "snd_metal" );
  515. }
  516. if ( *sound == '\0' ) {
  517. sound = projectileDef.GetString( "snd_impact" );
  518. }
  519. if ( *sound != '\0' ) {
  520. soundEnt->StartSoundShader( declManager->FindSound( sound ), SND_CHANNEL_BODY, 0, false, NULL );
  521. }
  522. // project decal
  523. decal = projectileDef.GetString( va( "mtr_detonate_%s", typeName ) );
  524. if ( *decal == '\0' ) {
  525. decal = projectileDef.GetString( "mtr_detonate" );
  526. }
  527. if ( *decal != '\0' ) {
  528. gameLocal.ProjectDecal( collision.c.point, -collision.c.normal, 8.0f, true, projectileDef.GetFloat( "decal_size", "6.0" ), decal );
  529. }
  530. }
  531. /*
  532. =================
  533. idProjectile::AddDefaultDamageEffect
  534. =================
  535. */
  536. void idProjectile::AddDefaultDamageEffect( const trace_t &collision, const idVec3 &velocity ) {
  537. DefaultDamageEffect( this, spawnArgs, collision, velocity );
  538. if ( gameLocal.isServer && fl.networkSync ) {
  539. idBitMsg msg;
  540. byte msgBuf[MAX_EVENT_PARAM_SIZE];
  541. int excludeClient;
  542. if ( spawnArgs.GetBool( "net_instanthit" ) ) {
  543. excludeClient = owner.GetEntityNum();
  544. } else {
  545. excludeClient = -1;
  546. }
  547. msg.Init( msgBuf, sizeof( msgBuf ) );
  548. msg.BeginWriting();
  549. msg.WriteFloat( collision.c.point[0] );
  550. msg.WriteFloat( collision.c.point[1] );
  551. msg.WriteFloat( collision.c.point[2] );
  552. msg.WriteDir( collision.c.normal, 24 );
  553. msg.WriteLong( ( collision.c.material != NULL ) ? gameLocal.ServerRemapDecl( -1, DECL_MATERIAL, collision.c.material->Index() ) : -1 );
  554. msg.WriteFloat( velocity[0], 5, 10 );
  555. msg.WriteFloat( velocity[1], 5, 10 );
  556. msg.WriteFloat( velocity[2], 5, 10 );
  557. ServerSendEvent( EVENT_DAMAGE_EFFECT, &msg, false, excludeClient );
  558. }
  559. }
  560. /*
  561. ================
  562. idProjectile::Killed
  563. ================
  564. */
  565. void idProjectile::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) {
  566. if ( spawnArgs.GetBool( "detonate_on_death" ) ) {
  567. trace_t collision;
  568. memset( &collision, 0, sizeof( collision ) );
  569. collision.endAxis = GetPhysics()->GetAxis();
  570. collision.endpos = GetPhysics()->GetOrigin();
  571. collision.c.point = GetPhysics()->GetOrigin();
  572. collision.c.normal.Set( 0, 0, 1 );
  573. Explode( collision, NULL );
  574. physicsObj.ClearContacts();
  575. physicsObj.PutToRest();
  576. } else {
  577. Fizzle();
  578. }
  579. }
  580. /*
  581. ================
  582. idProjectile::Fizzle
  583. ================
  584. */
  585. void idProjectile::Fizzle( void ) {
  586. if ( state == EXPLODED || state == FIZZLED ) {
  587. return;
  588. }
  589. StopSound( SND_CHANNEL_BODY, false );
  590. StartSound( "snd_fizzle", SND_CHANNEL_BODY, 0, false, NULL );
  591. // fizzle FX
  592. const char *psystem = spawnArgs.GetString( "smoke_fuse" );
  593. if ( psystem && *psystem ) {
  594. //FIXME:SMOKE gameLocal.particles->SpawnParticles( GetPhysics()->GetOrigin(), vec3_origin, psystem );
  595. }
  596. // we need to work out how long the effects last and then remove them at that time
  597. // for example, bullets have no real effects
  598. if ( smokeFly && smokeFlyTime ) {
  599. smokeFlyTime = 0;
  600. }
  601. fl.takedamage = false;
  602. physicsObj.SetContents( 0 );
  603. physicsObj.GetClipModel()->Unlink();
  604. physicsObj.PutToRest();
  605. Hide();
  606. FreeLightDef();
  607. state = FIZZLED;
  608. if ( gameLocal.isClient ) {
  609. return;
  610. }
  611. CancelEvents( &EV_Fizzle );
  612. PostEventMS( &EV_Remove, spawnArgs.GetInt( "remove_time", "1500" ) );
  613. }
  614. /*
  615. ================
  616. idProjectile::Event_RadiusDamage
  617. ================
  618. */
  619. void idProjectile::Event_RadiusDamage( idEntity *ignore ) {
  620. const char *splash_damage = spawnArgs.GetString( "def_splash_damage" );
  621. if ( splash_damage[0] != '\0' ) {
  622. gameLocal.RadiusDamage( physicsObj.GetOrigin(), this, owner.GetEntity(), ignore, this, splash_damage, damagePower );
  623. }
  624. }
  625. /*
  626. ================
  627. idProjectile::Event_RadiusDamage
  628. ================
  629. */
  630. void idProjectile::Event_GetProjectileState( void ) {
  631. idThread::ReturnInt( state );
  632. }
  633. /*
  634. ================
  635. idProjectile::Explode
  636. ================
  637. */
  638. void idProjectile::Explode( const trace_t &collision, idEntity *ignore ) {
  639. const char *fxname, *light_shader, *sndExplode;
  640. float light_fadetime;
  641. idVec3 normal;
  642. int removeTime;
  643. if ( state == EXPLODED || state == FIZZLED ) {
  644. return;
  645. }
  646. // stop sound
  647. StopSound( SND_CHANNEL_BODY2, false );
  648. // play explode sound
  649. switch ( ( int ) damagePower ) {
  650. case 2: sndExplode = "snd_explode2"; break;
  651. case 3: sndExplode = "snd_explode3"; break;
  652. case 4: sndExplode = "snd_explode4"; break;
  653. default: sndExplode = "snd_explode"; break;
  654. }
  655. StartSound( sndExplode, SND_CHANNEL_BODY, 0, true, NULL );
  656. // we need to work out how long the effects last and then remove them at that time
  657. // for example, bullets have no real effects
  658. if ( smokeFly && smokeFlyTime ) {
  659. smokeFlyTime = 0;
  660. }
  661. Hide();
  662. FreeLightDef();
  663. if ( spawnArgs.GetVector( "detonation_axis", "", normal ) ) {
  664. GetPhysics()->SetAxis( normal.ToMat3() );
  665. }
  666. GetPhysics()->SetOrigin( collision.endpos + 2.0f * collision.c.normal );
  667. // default remove time
  668. removeTime = spawnArgs.GetInt( "remove_time", "1500" );
  669. // change the model, usually to a PRT
  670. fxname = NULL;
  671. if ( g_testParticle.GetInteger() == TEST_PARTICLE_IMPACT ) {
  672. fxname = g_testParticleName.GetString();
  673. } else {
  674. fxname = spawnArgs.GetString( "model_detonate" );
  675. }
  676. int surfaceType = collision.c.material != NULL ? collision.c.material->GetSurfaceType() : SURFTYPE_METAL;
  677. if ( !( fxname && *fxname ) ) {
  678. if ( ( surfaceType == SURFTYPE_NONE ) || ( surfaceType == SURFTYPE_METAL ) || ( surfaceType == SURFTYPE_STONE ) ) {
  679. fxname = spawnArgs.GetString( "model_smokespark" );
  680. } else if ( surfaceType == SURFTYPE_RICOCHET ) {
  681. fxname = spawnArgs.GetString( "model_ricochet" );
  682. } else {
  683. fxname = spawnArgs.GetString( "model_smoke" );
  684. }
  685. }
  686. if ( fxname && *fxname ) {
  687. SetModel( fxname );
  688. renderEntity.shaderParms[SHADERPARM_RED] =
  689. renderEntity.shaderParms[SHADERPARM_GREEN] =
  690. renderEntity.shaderParms[SHADERPARM_BLUE] =
  691. renderEntity.shaderParms[SHADERPARM_ALPHA] = 1.0f;
  692. renderEntity.shaderParms[SHADERPARM_TIMEOFFSET] = -MS2SEC( gameLocal.time );
  693. renderEntity.shaderParms[SHADERPARM_DIVERSITY] = gameLocal.random.CRandomFloat();
  694. Show();
  695. removeTime = ( removeTime > 3000 ) ? removeTime : 3000;
  696. }
  697. // explosion light
  698. light_shader = spawnArgs.GetString( "mtr_explode_light_shader" );
  699. if ( *light_shader ) {
  700. renderLight.shader = declManager->FindMaterial( light_shader, false );
  701. renderLight.pointLight = true;
  702. renderLight.lightRadius[0] =
  703. renderLight.lightRadius[1] =
  704. renderLight.lightRadius[2] = spawnArgs.GetFloat( "explode_light_radius" );
  705. spawnArgs.GetVector( "explode_light_color", "1 1 1", lightColor );
  706. renderLight.shaderParms[SHADERPARM_RED] = lightColor.x;
  707. renderLight.shaderParms[SHADERPARM_GREEN] = lightColor.y;
  708. renderLight.shaderParms[SHADERPARM_BLUE] = lightColor.z;
  709. renderLight.shaderParms[SHADERPARM_ALPHA] = 1.0f;
  710. renderLight.shaderParms[SHADERPARM_TIMEOFFSET] = -MS2SEC( gameLocal.time );
  711. light_fadetime = spawnArgs.GetFloat( "explode_light_fadetime", "0.5" );
  712. lightStartTime = gameLocal.time;
  713. lightEndTime = gameLocal.time + SEC2MS( light_fadetime );
  714. BecomeActive( TH_THINK );
  715. }
  716. fl.takedamage = false;
  717. physicsObj.SetContents( 0 );
  718. physicsObj.PutToRest();
  719. state = EXPLODED;
  720. if ( gameLocal.isClient ) {
  721. return;
  722. }
  723. // alert the ai
  724. gameLocal.AlertAI( owner.GetEntity() );
  725. // bind the projectile to the impact entity if necesary
  726. if ( gameLocal.entities[collision.c.entityNum] && spawnArgs.GetBool( "bindOnImpact" ) ) {
  727. Bind( gameLocal.entities[collision.c.entityNum], true );
  728. }
  729. // splash damage
  730. if ( !projectileFlags.noSplashDamage ) {
  731. float delay = spawnArgs.GetFloat( "delay_splash" );
  732. if ( delay ) {
  733. if ( removeTime < delay * 1000 ) {
  734. removeTime = ( delay + 0.10 ) * 1000;
  735. }
  736. PostEventSec( &EV_RadiusDamage, delay, ignore );
  737. } else {
  738. Event_RadiusDamage( ignore );
  739. }
  740. }
  741. // spawn debris entities
  742. int fxdebris = spawnArgs.GetInt( "debris_count" );
  743. if ( fxdebris ) {
  744. const idDict *debris = gameLocal.FindEntityDefDict( "projectile_debris", false );
  745. if ( debris ) {
  746. int amount = gameLocal.random.RandomInt( fxdebris );
  747. for ( int i = 0; i < amount; i++ ) {
  748. idEntity *ent;
  749. idVec3 dir;
  750. dir.x = gameLocal.random.CRandomFloat() * 4.0f;
  751. dir.y = gameLocal.random.CRandomFloat() * 4.0f;
  752. dir.z = gameLocal.random.RandomFloat() * 8.0f;
  753. dir.Normalize();
  754. gameLocal.SpawnEntityDef( *debris, &ent, false );
  755. if ( !ent || !ent->IsType( idDebris::Type ) ) {
  756. gameLocal.Error( "'projectile_debris' is not an idDebris" );
  757. }
  758. idDebris *debris = static_cast<idDebris *>(ent);
  759. debris->Create( owner.GetEntity(), physicsObj.GetOrigin(), dir.ToMat3() );
  760. debris->Launch();
  761. }
  762. }
  763. debris = gameLocal.FindEntityDefDict( "projectile_shrapnel", false );
  764. if ( debris ) {
  765. int amount = gameLocal.random.RandomInt( fxdebris );
  766. for ( int i = 0; i < amount; i++ ) {
  767. idEntity *ent;
  768. idVec3 dir;
  769. dir.x = gameLocal.random.CRandomFloat() * 8.0f;
  770. dir.y = gameLocal.random.CRandomFloat() * 8.0f;
  771. dir.z = gameLocal.random.RandomFloat() * 8.0f + 8.0f;
  772. dir.Normalize();
  773. gameLocal.SpawnEntityDef( *debris, &ent, false );
  774. if ( !ent || !ent->IsType( idDebris::Type ) ) {
  775. gameLocal.Error( "'projectile_shrapnel' is not an idDebris" );
  776. }
  777. idDebris *debris = static_cast<idDebris *>(ent);
  778. debris->Create( owner.GetEntity(), physicsObj.GetOrigin(), dir.ToMat3() );
  779. debris->Launch();
  780. }
  781. }
  782. }
  783. CancelEvents( &EV_Explode );
  784. PostEventMS( &EV_Remove, removeTime );
  785. }
  786. /*
  787. ================
  788. idProjectile::GetVelocity
  789. ================
  790. */
  791. idVec3 idProjectile::GetVelocity( const idDict *projectile ) {
  792. idVec3 velocity;
  793. projectile->GetVector( "velocity", "0 0 0", velocity );
  794. return velocity;
  795. }
  796. /*
  797. ================
  798. idProjectile::GetGravity
  799. ================
  800. */
  801. idVec3 idProjectile::GetGravity( const idDict *projectile ) {
  802. float gravity;
  803. gravity = projectile->GetFloat( "gravity" );
  804. return idVec3( 0, 0, -gravity );
  805. }
  806. /*
  807. ================
  808. idProjectile::Event_Explode
  809. ================
  810. */
  811. void idProjectile::Event_Explode( void ) {
  812. trace_t collision;
  813. memset( &collision, 0, sizeof( collision ) );
  814. collision.endAxis = GetPhysics()->GetAxis();
  815. collision.endpos = GetPhysics()->GetOrigin();
  816. collision.c.point = GetPhysics()->GetOrigin();
  817. collision.c.normal.Set( 0, 0, 1 );
  818. AddDefaultDamageEffect( collision, collision.c.normal );
  819. Explode( collision, NULL );
  820. }
  821. /*
  822. ================
  823. idProjectile::Event_Fizzle
  824. ================
  825. */
  826. void idProjectile::Event_Fizzle( void ) {
  827. Fizzle();
  828. }
  829. /*
  830. ================
  831. idProjectile::Event_Touch
  832. ================
  833. */
  834. void idProjectile::Event_Touch( idEntity *other, trace_t *trace ) {
  835. if ( IsHidden() ) {
  836. return;
  837. }
  838. if ( other != owner.GetEntity() ) {
  839. trace_t collision;
  840. memset( &collision, 0, sizeof( collision ) );
  841. collision.endAxis = GetPhysics()->GetAxis();
  842. collision.endpos = GetPhysics()->GetOrigin();
  843. collision.c.point = GetPhysics()->GetOrigin();
  844. collision.c.normal.Set( 0, 0, 1 );
  845. AddDefaultDamageEffect( collision, collision.c.normal );
  846. Explode( collision, NULL );
  847. }
  848. }
  849. /*
  850. =================
  851. idProjectile::ClientPredictionCollide
  852. =================
  853. */
  854. bool idProjectile::ClientPredictionCollide( idEntity *soundEnt, const idDict &projectileDef, const trace_t &collision, const idVec3 &velocity, bool addDamageEffect ) {
  855. idEntity *ent;
  856. // remove projectile when a 'noimpact' surface is hit
  857. if ( collision.c.material && ( collision.c.material->GetSurfaceFlags() & SURF_NOIMPACT ) ) {
  858. return false;
  859. }
  860. // get the entity the projectile collided with
  861. ent = gameLocal.entities[ collision.c.entityNum ];
  862. if ( ent == NULL ) {
  863. return false;
  864. }
  865. // don't do anything if hitting a noclip player
  866. if ( ent->IsType( idPlayer::Type ) && static_cast<idPlayer *>( ent )->noclip ) {
  867. return false;
  868. }
  869. if ( ent->IsType( idActor::Type ) || ( ent->IsType( idAFAttachment::Type ) && static_cast<const idAFAttachment*>(ent)->GetBody()->IsType( idActor::Type ) ) ) {
  870. if ( !projectileDef.GetBool( "detonate_on_actor" ) ) {
  871. return false;
  872. }
  873. } else {
  874. if ( !projectileDef.GetBool( "detonate_on_world" ) ) {
  875. return false;
  876. }
  877. }
  878. // if the projectile causes a damage effect
  879. if ( addDamageEffect && projectileDef.GetBool( "impact_damage_effect" ) ) {
  880. // if the hit entity does not have a special damage effect
  881. if ( !ent->spawnArgs.GetBool( "bleed" ) ) {
  882. // predict damage effect
  883. DefaultDamageEffect( soundEnt, projectileDef, collision, velocity );
  884. }
  885. }
  886. return true;
  887. }
  888. /*
  889. ================
  890. idProjectile::ClientPredictionThink
  891. ================
  892. */
  893. void idProjectile::ClientPredictionThink( void ) {
  894. if ( !renderEntity.hModel ) {
  895. return;
  896. }
  897. Think();
  898. }
  899. /*
  900. ================
  901. idProjectile::WriteToSnapshot
  902. ================
  903. */
  904. void idProjectile::WriteToSnapshot( idBitMsgDelta &msg ) const {
  905. msg.WriteBits( owner.GetSpawnId(), 32 );
  906. msg.WriteBits( state, 3 );
  907. msg.WriteBits( fl.hidden, 1 );
  908. if ( netSyncPhysics ) {
  909. msg.WriteBits( 1, 1 );
  910. physicsObj.WriteToSnapshot( msg );
  911. } else {
  912. msg.WriteBits( 0, 1 );
  913. const idVec3 &origin = physicsObj.GetOrigin();
  914. const idVec3 &velocity = physicsObj.GetLinearVelocity();
  915. msg.WriteFloat( origin.x );
  916. msg.WriteFloat( origin.y );
  917. msg.WriteFloat( origin.z );
  918. msg.WriteDeltaFloat( 0.0f, velocity[0], RB_VELOCITY_EXPONENT_BITS, RB_VELOCITY_MANTISSA_BITS );
  919. msg.WriteDeltaFloat( 0.0f, velocity[1], RB_VELOCITY_EXPONENT_BITS, RB_VELOCITY_MANTISSA_BITS );
  920. msg.WriteDeltaFloat( 0.0f, velocity[2], RB_VELOCITY_EXPONENT_BITS, RB_VELOCITY_MANTISSA_BITS );
  921. }
  922. }
  923. /*
  924. ================
  925. idProjectile::ReadFromSnapshot
  926. ================
  927. */
  928. void idProjectile::ReadFromSnapshot( const idBitMsgDelta &msg ) {
  929. projectileState_t newState;
  930. owner.SetSpawnId( msg.ReadBits( 32 ) );
  931. newState = (projectileState_t) msg.ReadBits( 3 );
  932. if ( msg.ReadBits( 1 ) ) {
  933. Hide();
  934. } else {
  935. Show();
  936. }
  937. while( state != newState ) {
  938. switch( state ) {
  939. case SPAWNED: {
  940. Create( owner.GetEntity(), vec3_origin, idVec3( 1, 0, 0 ) );
  941. break;
  942. }
  943. case CREATED: {
  944. // the right origin and direction are required if you want bullet traces
  945. Launch( vec3_origin, idVec3( 1, 0, 0 ), vec3_origin );
  946. break;
  947. }
  948. case LAUNCHED: {
  949. if ( newState == FIZZLED ) {
  950. Fizzle();
  951. } else {
  952. trace_t collision;
  953. memset( &collision, 0, sizeof( collision ) );
  954. collision.endAxis = GetPhysics()->GetAxis();
  955. collision.endpos = GetPhysics()->GetOrigin();
  956. collision.c.point = GetPhysics()->GetOrigin();
  957. collision.c.normal.Set( 0, 0, 1 );
  958. Explode( collision, NULL );
  959. }
  960. break;
  961. }
  962. case FIZZLED:
  963. case EXPLODED: {
  964. StopSound( SND_CHANNEL_BODY2, false );
  965. gameEdit->ParseSpawnArgsToRenderEntity( &spawnArgs, &renderEntity );
  966. state = SPAWNED;
  967. break;
  968. }
  969. }
  970. }
  971. if ( msg.ReadBits( 1 ) ) {
  972. physicsObj.ReadFromSnapshot( msg );
  973. } else {
  974. idVec3 origin;
  975. idVec3 velocity;
  976. idVec3 tmp;
  977. idMat3 axis;
  978. origin.x = msg.ReadFloat();
  979. origin.y = msg.ReadFloat();
  980. origin.z = msg.ReadFloat();
  981. velocity.x = msg.ReadDeltaFloat( 0.0f, RB_VELOCITY_EXPONENT_BITS, RB_VELOCITY_MANTISSA_BITS );
  982. velocity.y = msg.ReadDeltaFloat( 0.0f, RB_VELOCITY_EXPONENT_BITS, RB_VELOCITY_MANTISSA_BITS );
  983. velocity.z = msg.ReadDeltaFloat( 0.0f, RB_VELOCITY_EXPONENT_BITS, RB_VELOCITY_MANTISSA_BITS );
  984. physicsObj.SetOrigin( origin );
  985. physicsObj.SetLinearVelocity( velocity );
  986. // align z-axis of model with the direction
  987. velocity.NormalizeFast();
  988. axis = velocity.ToMat3();
  989. tmp = axis[2];
  990. axis[2] = axis[0];
  991. axis[0] = -tmp;
  992. physicsObj.SetAxis( axis );
  993. }
  994. if ( msg.HasChanged() ) {
  995. UpdateVisuals();
  996. }
  997. }
  998. /*
  999. ================
  1000. idProjectile::ClientReceiveEvent
  1001. ================
  1002. */
  1003. bool idProjectile::ClientReceiveEvent( int event, int time, const idBitMsg &msg ) {
  1004. trace_t collision;
  1005. idVec3 velocity;
  1006. switch( event ) {
  1007. case EVENT_DAMAGE_EFFECT: {
  1008. memset( &collision, 0, sizeof( collision ) );
  1009. collision.c.point[0] = msg.ReadFloat();
  1010. collision.c.point[1] = msg.ReadFloat();
  1011. collision.c.point[2] = msg.ReadFloat();
  1012. collision.c.normal = msg.ReadDir( 24 );
  1013. int index = gameLocal.ClientRemapDecl( DECL_MATERIAL, msg.ReadLong() );
  1014. collision.c.material = ( index != -1 ) ? static_cast<const idMaterial *>( declManager->DeclByIndex( DECL_MATERIAL, index ) ) : NULL;
  1015. velocity[0] = msg.ReadFloat( 5, 10 );
  1016. velocity[1] = msg.ReadFloat( 5, 10 );
  1017. velocity[2] = msg.ReadFloat( 5, 10 );
  1018. DefaultDamageEffect( this, spawnArgs, collision, velocity );
  1019. return true;
  1020. }
  1021. default: {
  1022. return idEntity::ClientReceiveEvent( event, time, msg );
  1023. }
  1024. }
  1025. return false;
  1026. }
  1027. /*
  1028. ===============================================================================
  1029. idGuidedProjectile
  1030. ===============================================================================
  1031. */
  1032. CLASS_DECLARATION( idProjectile, idGuidedProjectile )
  1033. END_CLASS
  1034. /*
  1035. ================
  1036. idGuidedProjectile::idGuidedProjectile
  1037. ================
  1038. */
  1039. idGuidedProjectile::idGuidedProjectile( void ) {
  1040. enemy = NULL;
  1041. speed = 0.0f;
  1042. turn_max = 0.0f;
  1043. clamp_dist = 0.0f;
  1044. rndScale = ang_zero;
  1045. rndAng = ang_zero;
  1046. rndUpdateTime = 0;
  1047. angles = ang_zero;
  1048. burstMode = false;
  1049. burstDist = 0;
  1050. burstVelocity = 0.0f;
  1051. unGuided = false;
  1052. }
  1053. /*
  1054. =================
  1055. idGuidedProjectile::~idGuidedProjectile
  1056. =================
  1057. */
  1058. idGuidedProjectile::~idGuidedProjectile( void ) {
  1059. }
  1060. /*
  1061. ================
  1062. idGuidedProjectile::Spawn
  1063. ================
  1064. */
  1065. void idGuidedProjectile::Spawn( void ) {
  1066. }
  1067. /*
  1068. ================
  1069. idGuidedProjectile::Save
  1070. ================
  1071. */
  1072. void idGuidedProjectile::Save( idSaveGame *savefile ) const {
  1073. enemy.Save( savefile );
  1074. savefile->WriteFloat( speed );
  1075. savefile->WriteAngles( rndScale );
  1076. savefile->WriteAngles( rndAng );
  1077. savefile->WriteInt( rndUpdateTime );
  1078. savefile->WriteFloat( turn_max );
  1079. savefile->WriteFloat( clamp_dist );
  1080. savefile->WriteAngles( angles );
  1081. savefile->WriteBool( burstMode );
  1082. savefile->WriteBool( unGuided );
  1083. savefile->WriteFloat( burstDist );
  1084. savefile->WriteFloat( burstVelocity );
  1085. }
  1086. /*
  1087. ================
  1088. idGuidedProjectile::Restore
  1089. ================
  1090. */
  1091. void idGuidedProjectile::Restore( idRestoreGame *savefile ) {
  1092. enemy.Restore( savefile );
  1093. savefile->ReadFloat( speed );
  1094. savefile->ReadAngles( rndScale );
  1095. savefile->ReadAngles( rndAng );
  1096. savefile->ReadInt( rndUpdateTime );
  1097. savefile->ReadFloat( turn_max );
  1098. savefile->ReadFloat( clamp_dist );
  1099. savefile->ReadAngles( angles );
  1100. savefile->ReadBool( burstMode );
  1101. savefile->ReadBool( unGuided );
  1102. savefile->ReadFloat( burstDist );
  1103. savefile->ReadFloat( burstVelocity );
  1104. }
  1105. /*
  1106. ================
  1107. idGuidedProjectile::GetSeekPos
  1108. ================
  1109. */
  1110. void idGuidedProjectile::GetSeekPos( idVec3 &out ) {
  1111. idEntity *enemyEnt = enemy.GetEntity();
  1112. if ( enemyEnt ) {
  1113. if ( enemyEnt->IsType( idActor::Type ) ) {
  1114. out = static_cast<idActor *>(enemyEnt)->GetEyePosition();
  1115. out.z -= 12.0f;
  1116. } else {
  1117. out = enemyEnt->GetPhysics()->GetOrigin();
  1118. }
  1119. } else {
  1120. out = GetPhysics()->GetOrigin() + physicsObj.GetLinearVelocity() * 2.0f;
  1121. }
  1122. }
  1123. /*
  1124. ================
  1125. idGuidedProjectile::Think
  1126. ================
  1127. */
  1128. void idGuidedProjectile::Think( void ) {
  1129. idVec3 dir;
  1130. idVec3 seekPos;
  1131. idVec3 velocity;
  1132. idVec3 nose;
  1133. idVec3 tmp;
  1134. idMat3 axis;
  1135. idAngles dirAng;
  1136. idAngles diff;
  1137. float dist;
  1138. float frac;
  1139. int i;
  1140. if ( state == LAUNCHED && !unGuided ) {
  1141. GetSeekPos( seekPos );
  1142. if ( rndUpdateTime < gameLocal.time ) {
  1143. rndAng[ 0 ] = rndScale[ 0 ] * gameLocal.random.CRandomFloat();
  1144. rndAng[ 1 ] = rndScale[ 1 ] * gameLocal.random.CRandomFloat();
  1145. rndAng[ 2 ] = rndScale[ 2 ] * gameLocal.random.CRandomFloat();
  1146. rndUpdateTime = gameLocal.time + 200;
  1147. }
  1148. nose = physicsObj.GetOrigin() + 10.0f * physicsObj.GetAxis()[0];
  1149. dir = seekPos - nose;
  1150. dist = dir.Normalize();
  1151. dirAng = dir.ToAngles();
  1152. // make it more accurate as it gets closer
  1153. frac = dist / clamp_dist;
  1154. if ( frac > 1.0f ) {
  1155. frac = 1.0f;
  1156. }
  1157. diff = dirAng - angles + rndAng * frac;
  1158. // clamp the to the max turn rate
  1159. diff.Normalize180();
  1160. for( i = 0; i < 3; i++ ) {
  1161. if ( diff[ i ] > turn_max ) {
  1162. diff[ i ] = turn_max;
  1163. } else if ( diff[ i ] < -turn_max ) {
  1164. diff[ i ] = -turn_max;
  1165. }
  1166. }
  1167. angles += diff;
  1168. // make the visual model always points the dir we're traveling
  1169. dir = angles.ToForward();
  1170. velocity = dir * speed;
  1171. if ( burstMode && dist < burstDist ) {
  1172. unGuided = true;
  1173. velocity *= burstVelocity;
  1174. }
  1175. physicsObj.SetLinearVelocity( velocity );
  1176. // align z-axis of model with the direction
  1177. axis = dir.ToMat3();
  1178. tmp = axis[2];
  1179. axis[2] = axis[0];
  1180. axis[0] = -tmp;
  1181. GetPhysics()->SetAxis( axis );
  1182. }
  1183. idProjectile::Think();
  1184. }
  1185. /*
  1186. =================
  1187. idGuidedProjectile::Launch
  1188. =================
  1189. */
  1190. void idGuidedProjectile::Launch( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity, const float timeSinceFire, const float launchPower, float dmgPower ) {
  1191. idProjectile::Launch( start, dir, pushVelocity, timeSinceFire, launchPower, dmgPower );
  1192. if ( owner.GetEntity() ) {
  1193. if ( owner.GetEntity()->IsType( idAI::Type ) ) {
  1194. enemy = static_cast<idAI *>( owner.GetEntity() )->GetEnemy();
  1195. } else if ( owner.GetEntity()->IsType( idPlayer::Type ) ) {
  1196. trace_t tr;
  1197. idPlayer *player = static_cast<idPlayer*>( owner.GetEntity() );
  1198. idVec3 start = player->GetEyePosition();
  1199. idVec3 end = start + player->viewAxis[0] * 1000.0f;
  1200. gameLocal.clip.TracePoint( tr, start, end, MASK_SHOT_RENDERMODEL | CONTENTS_BODY, owner.GetEntity() );
  1201. if ( tr.fraction < 1.0f ) {
  1202. enemy = gameLocal.GetTraceEntity( tr );
  1203. }
  1204. // ignore actors on the player's team
  1205. if ( enemy.GetEntity() == NULL || !enemy.GetEntity()->IsType( idActor::Type ) || ( static_cast<idActor *>( enemy.GetEntity() )->team == player->team ) ) {
  1206. enemy = player->EnemyWithMostHealth();
  1207. }
  1208. }
  1209. }
  1210. const idVec3 &vel = physicsObj.GetLinearVelocity();
  1211. angles = vel.ToAngles();
  1212. speed = vel.Length();
  1213. rndScale = spawnArgs.GetAngles( "random", "15 15 0" );
  1214. turn_max = spawnArgs.GetFloat( "turn_max", "180" ) / ( float )USERCMD_HZ;
  1215. clamp_dist = spawnArgs.GetFloat( "clamp_dist", "256" );
  1216. burstMode = spawnArgs.GetBool( "burstMode" );
  1217. unGuided = false;
  1218. burstDist = spawnArgs.GetFloat( "burstDist", "64" );
  1219. burstVelocity = spawnArgs.GetFloat( "burstVelocity", "1.25" );
  1220. UpdateVisuals();
  1221. }
  1222. /*
  1223. ===============================================================================
  1224. idSoulCubeMissile
  1225. ===============================================================================
  1226. */
  1227. CLASS_DECLARATION( idGuidedProjectile, idSoulCubeMissile )
  1228. END_CLASS
  1229. /*
  1230. ================
  1231. idSoulCubeMissile::Spawn( void )
  1232. ================
  1233. */
  1234. void idSoulCubeMissile::Spawn( void ) {
  1235. startingVelocity.Zero();
  1236. endingVelocity.Zero();
  1237. accelTime = 0.0f;
  1238. launchTime = 0;
  1239. killPhase = false;
  1240. returnPhase = false;
  1241. smokeKillTime = 0;
  1242. smokeKill = NULL;
  1243. }
  1244. /*
  1245. =================
  1246. idSoulCubeMissile::~idSoulCubeMissile
  1247. =================
  1248. */
  1249. idSoulCubeMissile::~idSoulCubeMissile() {
  1250. }
  1251. /*
  1252. ================
  1253. idSoulCubeMissile::Save
  1254. ================
  1255. */
  1256. void idSoulCubeMissile::Save( idSaveGame *savefile ) const {
  1257. savefile->WriteVec3( startingVelocity );
  1258. savefile->WriteVec3( endingVelocity );
  1259. savefile->WriteFloat( accelTime );
  1260. savefile->WriteInt( launchTime );
  1261. savefile->WriteBool( killPhase );
  1262. savefile->WriteBool( returnPhase );
  1263. savefile->WriteVec3( destOrg);
  1264. savefile->WriteInt( orbitTime );
  1265. savefile->WriteVec3( orbitOrg );
  1266. savefile->WriteInt( smokeKillTime );
  1267. savefile->WriteParticle( smokeKill );
  1268. }
  1269. /*
  1270. ================
  1271. idSoulCubeMissile::Restore
  1272. ================
  1273. */
  1274. void idSoulCubeMissile::Restore( idRestoreGame *savefile ) {
  1275. savefile->ReadVec3( startingVelocity );
  1276. savefile->ReadVec3( endingVelocity );
  1277. savefile->ReadFloat( accelTime );
  1278. savefile->ReadInt( launchTime );
  1279. savefile->ReadBool( killPhase );
  1280. savefile->ReadBool( returnPhase );
  1281. savefile->ReadVec3( destOrg);
  1282. savefile->ReadInt( orbitTime );
  1283. savefile->ReadVec3( orbitOrg );
  1284. savefile->ReadInt( smokeKillTime );
  1285. savefile->ReadParticle( smokeKill );
  1286. }
  1287. /*
  1288. ================
  1289. idSoulCubeMissile::KillTarget
  1290. ================
  1291. */
  1292. void idSoulCubeMissile::KillTarget( const idVec3 &dir ) {
  1293. idEntity *ownerEnt;
  1294. const char *smokeName;
  1295. idActor *act;
  1296. ReturnToOwner();
  1297. if ( enemy.GetEntity() && enemy.GetEntity()->IsType( idActor::Type ) ) {
  1298. act = static_cast<idActor*>( enemy.GetEntity() );
  1299. killPhase = true;
  1300. orbitOrg = act->GetPhysics()->GetAbsBounds().GetCenter();
  1301. orbitTime = gameLocal.time;
  1302. smokeKillTime = 0;
  1303. smokeName = spawnArgs.GetString( "smoke_kill" );
  1304. if ( *smokeName != '\0' ) {
  1305. smokeKill = static_cast<const idDeclParticle *>( declManager->FindType( DECL_PARTICLE, smokeName ) );
  1306. smokeKillTime = gameLocal.time;
  1307. }
  1308. ownerEnt = owner.GetEntity();
  1309. if ( ( act->health > 0 ) && ownerEnt && ownerEnt->IsType( idPlayer::Type ) && ( ownerEnt->health > 0 ) && !act->spawnArgs.GetBool( "boss" ) ) {
  1310. static_cast<idPlayer *>( ownerEnt )->GiveHealthPool( act->health );
  1311. }
  1312. act->Damage( this, owner.GetEntity(), dir, spawnArgs.GetString( "def_damage" ), 1.0f, INVALID_JOINT );
  1313. act->GetAFPhysics()->SetTimeScale( 0.25 );
  1314. StartSound( "snd_explode", SND_CHANNEL_BODY, 0, false, NULL );
  1315. }
  1316. }
  1317. /*
  1318. ================
  1319. idSoulCubeMissile::Think
  1320. ================
  1321. */
  1322. void idSoulCubeMissile::Think( void ) {
  1323. float pct;
  1324. idVec3 seekPos;
  1325. idEntity *ownerEnt;
  1326. if ( state == LAUNCHED ) {
  1327. if ( killPhase ) {
  1328. // orbit the mob, cascading down
  1329. if ( gameLocal.time < orbitTime + 1500 ) {
  1330. if ( !gameLocal.smokeParticles->EmitSmoke( smokeKill, smokeKillTime, gameLocal.random.CRandomFloat(), orbitOrg, mat3_identity ) ) {
  1331. smokeKillTime = gameLocal.time;
  1332. }
  1333. }
  1334. } else {
  1335. if ( accelTime && gameLocal.time < launchTime + accelTime * 1000 ) {
  1336. pct = ( gameLocal.time - launchTime ) / ( accelTime * 1000 );
  1337. speed = ( startingVelocity + ( startingVelocity + endingVelocity ) * pct ).Length();
  1338. }
  1339. }
  1340. idGuidedProjectile::Think();
  1341. GetSeekPos( seekPos );
  1342. if ( ( seekPos - physicsObj.GetOrigin() ).Length() < 32.0f ) {
  1343. if ( returnPhase ) {
  1344. StopSound( SND_CHANNEL_ANY, false );
  1345. StartSound( "snd_return", SND_CHANNEL_BODY2, 0, false, NULL );
  1346. Hide();
  1347. PostEventSec( &EV_Remove, 2.0f );
  1348. ownerEnt = owner.GetEntity();
  1349. if ( ownerEnt && ownerEnt->IsType( idPlayer::Type ) ) {
  1350. static_cast<idPlayer *>( ownerEnt )->SetSoulCubeProjectile( NULL );
  1351. }
  1352. state = FIZZLED;
  1353. } else if ( !killPhase ){
  1354. KillTarget( physicsObj.GetAxis()[0] );
  1355. }
  1356. }
  1357. }
  1358. }
  1359. /*
  1360. ================
  1361. idSoulCubeMissile::GetSeekPos
  1362. ================
  1363. */
  1364. void idSoulCubeMissile::GetSeekPos( idVec3 &out ) {
  1365. if ( returnPhase && owner.GetEntity() && owner.GetEntity()->IsType( idActor::Type ) ) {
  1366. idActor *act = static_cast<idActor*>( owner.GetEntity() );
  1367. out = act->GetEyePosition();
  1368. return;
  1369. }
  1370. if ( destOrg != vec3_zero ) {
  1371. out = destOrg;
  1372. return;
  1373. }
  1374. idGuidedProjectile::GetSeekPos( out );
  1375. }
  1376. /*
  1377. ================
  1378. idSoulCubeMissile::Event_ReturnToOwner
  1379. ================
  1380. */
  1381. void idSoulCubeMissile::ReturnToOwner() {
  1382. speed *= 0.65f;
  1383. killPhase = false;
  1384. returnPhase = true;
  1385. smokeFlyTime = 0;
  1386. }
  1387. /*
  1388. =================
  1389. idSoulCubeMissile::Launch
  1390. =================
  1391. */
  1392. void idSoulCubeMissile::Launch( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity, const float timeSinceFire, const float launchPower, float dmgPower ) {
  1393. idVec3 newStart;
  1394. idVec3 offs;
  1395. idEntity *ownerEnt;
  1396. // push it out a little
  1397. newStart = start + dir * spawnArgs.GetFloat( "launchDist" );
  1398. offs = spawnArgs.GetVector( "launchOffset", "0 0 -4" );
  1399. newStart += offs;
  1400. idGuidedProjectile::Launch( newStart, dir, pushVelocity, timeSinceFire, launchPower, dmgPower );
  1401. if ( enemy.GetEntity() == NULL || !enemy.GetEntity()->IsType( idActor::Type ) ) {
  1402. destOrg = start + dir * 256.0f;
  1403. } else {
  1404. destOrg.Zero();
  1405. }
  1406. physicsObj.SetClipMask( 0 ); // never collide.. think routine will decide when to detonate
  1407. startingVelocity = spawnArgs.GetVector( "startingVelocity", "15 0 0" );
  1408. endingVelocity = spawnArgs.GetVector( "endingVelocity", "1500 0 0" );
  1409. accelTime = spawnArgs.GetFloat( "accelTime", "5" );
  1410. physicsObj.SetLinearVelocity( startingVelocity.Length() * physicsObj.GetAxis()[2] );
  1411. launchTime = gameLocal.time;
  1412. killPhase = false;
  1413. UpdateVisuals();
  1414. ownerEnt = owner.GetEntity();
  1415. if ( ownerEnt && ownerEnt->IsType( idPlayer::Type ) ) {
  1416. static_cast<idPlayer *>( ownerEnt )->SetSoulCubeProjectile( this );
  1417. }
  1418. }
  1419. /*
  1420. ===============================================================================
  1421. idBFGProjectile
  1422. ===============================================================================
  1423. */
  1424. const idEventDef EV_RemoveBeams( "<removeBeams>", NULL );
  1425. CLASS_DECLARATION( idProjectile, idBFGProjectile )
  1426. EVENT( EV_RemoveBeams, idBFGProjectile::Event_RemoveBeams )
  1427. END_CLASS
  1428. /*
  1429. =================
  1430. idBFGProjectile::idBFGProjectile
  1431. =================
  1432. */
  1433. idBFGProjectile::idBFGProjectile() {
  1434. memset( &secondModel, 0, sizeof( secondModel ) );
  1435. secondModelDefHandle = -1;
  1436. nextDamageTime = 0;
  1437. }
  1438. /*
  1439. =================
  1440. idBFGProjectile::~idBFGProjectile
  1441. =================
  1442. */
  1443. idBFGProjectile::~idBFGProjectile() {
  1444. FreeBeams();
  1445. if ( secondModelDefHandle >= 0 ) {
  1446. gameRenderWorld->FreeEntityDef( secondModelDefHandle );
  1447. secondModelDefHandle = -1;
  1448. }
  1449. }
  1450. /*
  1451. ================
  1452. idBFGProjectile::Spawn
  1453. ================
  1454. */
  1455. void idBFGProjectile::Spawn( void ) {
  1456. beamTargets.Clear();
  1457. memset( &secondModel, 0, sizeof( secondModel ) );
  1458. secondModelDefHandle = -1;
  1459. const char *temp = spawnArgs.GetString( "model_two" );
  1460. if ( temp && *temp ) {
  1461. secondModel.hModel = renderModelManager->FindModel( temp );
  1462. secondModel.bounds = secondModel.hModel->Bounds( &secondModel );
  1463. secondModel.shaderParms[ SHADERPARM_RED ] =
  1464. secondModel.shaderParms[ SHADERPARM_GREEN ] =
  1465. secondModel.shaderParms[ SHADERPARM_BLUE ] =
  1466. secondModel.shaderParms[ SHADERPARM_ALPHA ] = 1.0f;
  1467. secondModel.noSelfShadow = true;
  1468. secondModel.noShadow = true;
  1469. }
  1470. nextDamageTime = 0;
  1471. damageFreq = NULL;
  1472. }
  1473. /*
  1474. ================
  1475. idBFGProjectile::Save
  1476. ================
  1477. */
  1478. void idBFGProjectile::Save( idSaveGame *savefile ) const {
  1479. int i;
  1480. savefile->WriteInt( beamTargets.Num() );
  1481. for ( i = 0; i < beamTargets.Num(); i++ ) {
  1482. beamTargets[i].target.Save( savefile );
  1483. savefile->WriteRenderEntity( beamTargets[i].renderEntity );
  1484. savefile->WriteInt( beamTargets[i].modelDefHandle );
  1485. }
  1486. savefile->WriteRenderEntity( secondModel );
  1487. savefile->WriteInt( secondModelDefHandle );
  1488. savefile->WriteInt( nextDamageTime );
  1489. savefile->WriteString( damageFreq );
  1490. }
  1491. /*
  1492. ================
  1493. idBFGProjectile::Restore
  1494. ================
  1495. */
  1496. void idBFGProjectile::Restore( idRestoreGame *savefile ) {
  1497. int i, num;
  1498. savefile->ReadInt( num );
  1499. beamTargets.SetNum( num );
  1500. for ( i = 0; i < num; i++ ) {
  1501. beamTargets[i].target.Restore( savefile );
  1502. savefile->ReadRenderEntity( beamTargets[i].renderEntity );
  1503. savefile->ReadInt( beamTargets[i].modelDefHandle );
  1504. if ( beamTargets[i].modelDefHandle >= 0 ) {
  1505. beamTargets[i].modelDefHandle = gameRenderWorld->AddEntityDef( &beamTargets[i].renderEntity );
  1506. }
  1507. }
  1508. savefile->ReadRenderEntity( secondModel );
  1509. savefile->ReadInt( secondModelDefHandle );
  1510. savefile->ReadInt( nextDamageTime );
  1511. savefile->ReadString( damageFreq );
  1512. if ( secondModelDefHandle >= 0 ) {
  1513. secondModelDefHandle = gameRenderWorld->AddEntityDef( &secondModel );
  1514. }
  1515. }
  1516. /*
  1517. =================
  1518. idBFGProjectile::FreeBeams
  1519. =================
  1520. */
  1521. void idBFGProjectile::FreeBeams() {
  1522. for ( int i = 0; i < beamTargets.Num(); i++ ) {
  1523. if ( beamTargets[i].modelDefHandle >= 0 ) {
  1524. gameRenderWorld->FreeEntityDef( beamTargets[i].modelDefHandle );
  1525. beamTargets[i].modelDefHandle = -1;
  1526. }
  1527. }
  1528. idPlayer *player = gameLocal.GetLocalPlayer();
  1529. if ( player ) {
  1530. player->playerView.EnableBFGVision( false );
  1531. }
  1532. }
  1533. /*
  1534. ================
  1535. idBFGProjectile::Think
  1536. ================
  1537. */
  1538. void idBFGProjectile::Think( void ) {
  1539. if ( state == LAUNCHED ) {
  1540. // update beam targets
  1541. for ( int i = 0; i < beamTargets.Num(); i++ ) {
  1542. if ( beamTargets[i].target.GetEntity() == NULL ) {
  1543. continue;
  1544. }
  1545. idPlayer *player = ( beamTargets[i].target.GetEntity()->IsType( idPlayer::Type ) ) ? static_cast<idPlayer*>( beamTargets[i].target.GetEntity() ) : NULL;
  1546. idVec3 org = beamTargets[i].target.GetEntity()->GetPhysics()->GetAbsBounds().GetCenter();
  1547. beamTargets[i].renderEntity.origin = GetPhysics()->GetOrigin();
  1548. beamTargets[i].renderEntity.shaderParms[ SHADERPARM_BEAM_END_X ] = org.x;
  1549. beamTargets[i].renderEntity.shaderParms[ SHADERPARM_BEAM_END_Y ] = org.y;
  1550. beamTargets[i].renderEntity.shaderParms[ SHADERPARM_BEAM_END_Z ] = org.z;
  1551. beamTargets[i].renderEntity.shaderParms[ SHADERPARM_RED ] =
  1552. beamTargets[i].renderEntity.shaderParms[ SHADERPARM_GREEN ] =
  1553. beamTargets[i].renderEntity.shaderParms[ SHADERPARM_BLUE ] =
  1554. beamTargets[i].renderEntity.shaderParms[ SHADERPARM_ALPHA ] = 1.0f;
  1555. if ( gameLocal.time > nextDamageTime ) {
  1556. bool bfgVision = true;
  1557. if ( damageFreq && *(const char *)damageFreq && beamTargets[i].target.GetEntity() && beamTargets[i].target.GetEntity()->CanDamage( GetPhysics()->GetOrigin(), org ) ) {
  1558. org = beamTargets[i].target.GetEntity()->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin();
  1559. org.Normalize();
  1560. beamTargets[i].target.GetEntity()->Damage( this, owner.GetEntity(), org, damageFreq, ( damagePower ) ? damagePower : 1.0f, INVALID_JOINT );
  1561. } else {
  1562. beamTargets[i].renderEntity.shaderParms[ SHADERPARM_RED ] =
  1563. beamTargets[i].renderEntity.shaderParms[ SHADERPARM_GREEN ] =
  1564. beamTargets[i].renderEntity.shaderParms[ SHADERPARM_BLUE ] =
  1565. beamTargets[i].renderEntity.shaderParms[ SHADERPARM_ALPHA ] = 0.0f;
  1566. bfgVision = false;
  1567. }
  1568. if ( player ) {
  1569. player->playerView.EnableBFGVision( bfgVision );
  1570. }
  1571. nextDamageTime = gameLocal.time + BFG_DAMAGE_FREQUENCY;
  1572. }
  1573. gameRenderWorld->UpdateEntityDef( beamTargets[i].modelDefHandle, &beamTargets[i].renderEntity );
  1574. }
  1575. if ( secondModelDefHandle >= 0 ) {
  1576. secondModel.origin = GetPhysics()->GetOrigin();
  1577. gameRenderWorld->UpdateEntityDef( secondModelDefHandle, &secondModel );
  1578. }
  1579. idAngles ang;
  1580. ang.pitch = ( gameLocal.time & 4095 ) * 360.0f / -4096.0f;
  1581. ang.yaw = ang.pitch;
  1582. ang.roll = 0.0f;
  1583. SetAngles( ang );
  1584. ang.pitch = ( gameLocal.time & 2047 ) * 360.0f / -2048.0f;
  1585. ang.yaw = ang.pitch;
  1586. ang.roll = 0.0f;
  1587. secondModel.axis = ang.ToMat3();
  1588. UpdateVisuals();
  1589. }
  1590. idProjectile::Think();
  1591. }
  1592. /*
  1593. =================
  1594. idBFGProjectile::Launch
  1595. =================
  1596. */
  1597. void idBFGProjectile::Launch( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity, const float timeSinceFire, const float power, const float dmgPower ) {
  1598. idProjectile::Launch( start, dir, pushVelocity, 0.0f, power, dmgPower );
  1599. // dmgPower * radius is the target acquisition area
  1600. // acquisition should make sure that monsters are not dormant
  1601. // which will cut down on hitting monsters not actively fighting
  1602. // but saves on the traces making sure they are visible
  1603. // damage is not applied until the projectile explodes
  1604. idEntity * ent;
  1605. idEntity * entityList[ MAX_GENTITIES ];
  1606. int numListedEntities;
  1607. idBounds bounds;
  1608. idVec3 damagePoint;
  1609. float radius;
  1610. spawnArgs.GetFloat( "damageRadius", "512", radius );
  1611. bounds = idBounds( GetPhysics()->GetOrigin() ).Expand( radius );
  1612. float beamWidth = spawnArgs.GetFloat( "beam_WidthFly" );
  1613. const char *skin = spawnArgs.GetString( "skin_beam" );
  1614. memset( &secondModel, 0, sizeof( secondModel ) );
  1615. secondModelDefHandle = -1;
  1616. const char *temp = spawnArgs.GetString( "model_two" );
  1617. if ( temp && *temp ) {
  1618. secondModel.hModel = renderModelManager->FindModel( temp );
  1619. secondModel.bounds = secondModel.hModel->Bounds( &secondModel );
  1620. secondModel.shaderParms[ SHADERPARM_RED ] =
  1621. secondModel.shaderParms[ SHADERPARM_GREEN ] =
  1622. secondModel.shaderParms[ SHADERPARM_BLUE ] =
  1623. secondModel.shaderParms[ SHADERPARM_ALPHA ] = 1.0f;
  1624. secondModel.noSelfShadow = true;
  1625. secondModel.noShadow = true;
  1626. secondModel.origin = GetPhysics()->GetOrigin();
  1627. secondModel.axis = GetPhysics()->GetAxis();
  1628. secondModelDefHandle = gameRenderWorld->AddEntityDef( &secondModel );
  1629. }
  1630. idVec3 delta( 15.0f, 15.0f, 15.0f );
  1631. //physicsObj.SetAngularExtrapolation( extrapolation_t(EXTRAPOLATION_LINEAR|EXTRAPOLATION_NOSTOP), gameLocal.time, 0, physicsObj.GetAxis().ToAngles(), delta, ang_zero );
  1632. // get all entities touching the bounds
  1633. numListedEntities = gameLocal.clip.EntitiesTouchingBounds( bounds, CONTENTS_BODY, entityList, MAX_GENTITIES );
  1634. for ( int e = 0; e < numListedEntities; e++ ) {
  1635. ent = entityList[ e ];
  1636. assert( ent );
  1637. if ( ent == this || ent == owner.GetEntity() || ent->IsHidden() || !ent->IsActive() || !ent->fl.takedamage || ent->health <= 0 || !ent->IsType( idActor::Type ) ) {
  1638. continue;
  1639. }
  1640. if ( !ent->CanDamage( GetPhysics()->GetOrigin(), damagePoint ) ) {
  1641. continue;
  1642. }
  1643. if ( ent->IsType( idPlayer::Type ) ) {
  1644. idPlayer *player = static_cast<idPlayer*>( ent );
  1645. player->playerView.EnableBFGVision( true );
  1646. }
  1647. beamTarget_t bt;
  1648. memset( &bt.renderEntity, 0, sizeof( renderEntity_t ) );
  1649. bt.renderEntity.origin = GetPhysics()->GetOrigin();
  1650. bt.renderEntity.axis = GetPhysics()->GetAxis();
  1651. bt.renderEntity.shaderParms[ SHADERPARM_BEAM_WIDTH ] = beamWidth;
  1652. bt.renderEntity.shaderParms[ SHADERPARM_RED ] = 1.0f;
  1653. bt.renderEntity.shaderParms[ SHADERPARM_GREEN ] = 1.0f;
  1654. bt.renderEntity.shaderParms[ SHADERPARM_BLUE ] = 1.0f;
  1655. bt.renderEntity.shaderParms[ SHADERPARM_ALPHA ] = 1.0f;
  1656. bt.renderEntity.shaderParms[ SHADERPARM_DIVERSITY] = gameLocal.random.CRandomFloat() * 0.75;
  1657. bt.renderEntity.hModel = renderModelManager->FindModel( "_beam" );
  1658. bt.renderEntity.callback = NULL;
  1659. bt.renderEntity.numJoints = 0;
  1660. bt.renderEntity.joints = NULL;
  1661. bt.renderEntity.bounds.Clear();
  1662. bt.renderEntity.customSkin = declManager->FindSkin( skin );
  1663. bt.target = ent;
  1664. bt.modelDefHandle = gameRenderWorld->AddEntityDef( &bt.renderEntity );
  1665. beamTargets.Append( bt );
  1666. }
  1667. if ( numListedEntities ) {
  1668. StartSound( "snd_beam", SND_CHANNEL_BODY2, 0, false, NULL );
  1669. }
  1670. damageFreq = spawnArgs.GetString( "def_damageFreq" );
  1671. nextDamageTime = gameLocal.time + BFG_DAMAGE_FREQUENCY;
  1672. UpdateVisuals();
  1673. }
  1674. /*
  1675. ================
  1676. idProjectile::Event_RemoveBeams
  1677. ================
  1678. */
  1679. void idBFGProjectile::Event_RemoveBeams() {
  1680. FreeBeams();
  1681. UpdateVisuals();
  1682. }
  1683. /*
  1684. ================
  1685. idProjectile::Explode
  1686. ================
  1687. */
  1688. void idBFGProjectile::Explode( const trace_t &collision, idEntity *ignore ) {
  1689. int i;
  1690. idVec3 dmgPoint;
  1691. idVec3 dir;
  1692. float beamWidth;
  1693. float damageScale;
  1694. const char *damage;
  1695. idPlayer * player;
  1696. idEntity * ownerEnt;
  1697. ownerEnt = owner.GetEntity();
  1698. if ( ownerEnt && ownerEnt->IsType( idPlayer::Type ) ) {
  1699. player = static_cast< idPlayer * >( ownerEnt );
  1700. } else {
  1701. player = NULL;
  1702. }
  1703. beamWidth = spawnArgs.GetFloat( "beam_WidthExplode" );
  1704. damage = spawnArgs.GetString( "def_damage" );
  1705. for ( i = 0; i < beamTargets.Num(); i++ ) {
  1706. if ( ( beamTargets[i].target.GetEntity() == NULL ) || ( ownerEnt == NULL ) ) {
  1707. continue;
  1708. }
  1709. if ( !beamTargets[i].target.GetEntity()->CanDamage( GetPhysics()->GetOrigin(), dmgPoint ) ) {
  1710. continue;
  1711. }
  1712. beamTargets[i].renderEntity.shaderParms[SHADERPARM_BEAM_WIDTH] = beamWidth;
  1713. // if the hit entity takes damage
  1714. if ( damagePower ) {
  1715. damageScale = damagePower;
  1716. } else {
  1717. damageScale = 1.0f;
  1718. }
  1719. // if the projectile owner is a player
  1720. if ( player ) {
  1721. // if the projectile hit an actor
  1722. if ( beamTargets[i].target.GetEntity()->IsType( idActor::Type ) ) {
  1723. player->SetLastHitTime( gameLocal.time );
  1724. player->AddProjectileHits( 1 );
  1725. damageScale *= player->PowerUpModifier( PROJECTILE_DAMAGE );
  1726. }
  1727. }
  1728. if ( damage[0] && ( beamTargets[i].target.GetEntity()->entityNumber > gameLocal.numClients - 1 ) ) {
  1729. dir = beamTargets[i].target.GetEntity()->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin();
  1730. dir.Normalize();
  1731. beamTargets[i].target.GetEntity()->Damage( this, ownerEnt, dir, damage, damageScale, ( collision.c.id < 0 ) ? CLIPMODEL_ID_TO_JOINT_HANDLE( collision.c.id ) : INVALID_JOINT );
  1732. }
  1733. }
  1734. if ( secondModelDefHandle >= 0 ) {
  1735. gameRenderWorld->FreeEntityDef( secondModelDefHandle );
  1736. secondModelDefHandle = -1;
  1737. }
  1738. if ( ignore == NULL ) {
  1739. projectileFlags.noSplashDamage = true;
  1740. }
  1741. if ( !gameLocal.isClient ) {
  1742. if ( ignore != NULL ) {
  1743. PostEventMS( &EV_RemoveBeams, 750 );
  1744. } else {
  1745. PostEventMS( &EV_RemoveBeams, 0 );
  1746. }
  1747. }
  1748. return idProjectile::Explode( collision, ignore );
  1749. }
  1750. /*
  1751. ===============================================================================
  1752. idDebris
  1753. ===============================================================================
  1754. */
  1755. CLASS_DECLARATION( idEntity, idDebris )
  1756. EVENT( EV_Explode, idDebris::Event_Explode )
  1757. EVENT( EV_Fizzle, idDebris::Event_Fizzle )
  1758. END_CLASS
  1759. /*
  1760. ================
  1761. idDebris::Spawn
  1762. ================
  1763. */
  1764. void idDebris::Spawn( void ) {
  1765. owner = NULL;
  1766. smokeFly = NULL;
  1767. smokeFlyTime = 0;
  1768. }
  1769. /*
  1770. ================
  1771. idDebris::Create
  1772. ================
  1773. */
  1774. void idDebris::Create( idEntity *owner, const idVec3 &start, const idMat3 &axis ) {
  1775. Unbind();
  1776. GetPhysics()->SetOrigin( start );
  1777. GetPhysics()->SetAxis( axis );
  1778. GetPhysics()->SetContents( 0 );
  1779. this->owner = owner;
  1780. smokeFly = NULL;
  1781. smokeFlyTime = 0;
  1782. sndBounce = NULL;
  1783. UpdateVisuals();
  1784. }
  1785. /*
  1786. =================
  1787. idDebris::idDebris
  1788. =================
  1789. */
  1790. idDebris::idDebris( void ) {
  1791. owner = NULL;
  1792. smokeFly = NULL;
  1793. smokeFlyTime = 0;
  1794. sndBounce = NULL;
  1795. }
  1796. /*
  1797. =================
  1798. idDebris::~idDebris
  1799. =================
  1800. */
  1801. idDebris::~idDebris( void ) {
  1802. }
  1803. /*
  1804. =================
  1805. idDebris::Save
  1806. =================
  1807. */
  1808. void idDebris::Save( idSaveGame *savefile ) const {
  1809. owner.Save( savefile );
  1810. savefile->WriteStaticObject( physicsObj );
  1811. savefile->WriteParticle( smokeFly );
  1812. savefile->WriteInt( smokeFlyTime );
  1813. savefile->WriteSoundShader( sndBounce );
  1814. }
  1815. /*
  1816. =================
  1817. idDebris::Restore
  1818. =================
  1819. */
  1820. void idDebris::Restore( idRestoreGame *savefile ) {
  1821. owner.Restore( savefile );
  1822. savefile->ReadStaticObject( physicsObj );
  1823. RestorePhysics( &physicsObj );
  1824. savefile->ReadParticle( smokeFly );
  1825. savefile->ReadInt( smokeFlyTime );
  1826. savefile->ReadSoundShader( sndBounce );
  1827. }
  1828. /*
  1829. =================
  1830. idDebris::Launch
  1831. =================
  1832. */
  1833. void idDebris::Launch( void ) {
  1834. float fuse;
  1835. idVec3 velocity;
  1836. idAngles angular_velocity;
  1837. float linear_friction;
  1838. float angular_friction;
  1839. float contact_friction;
  1840. float bounce;
  1841. float mass;
  1842. float gravity;
  1843. idVec3 gravVec;
  1844. bool randomVelocity;
  1845. idMat3 axis;
  1846. renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time );
  1847. spawnArgs.GetVector( "velocity", "0 0 0", velocity );
  1848. spawnArgs.GetAngles( "angular_velocity", "0 0 0", angular_velocity );
  1849. linear_friction = spawnArgs.GetFloat( "linear_friction" );
  1850. angular_friction = spawnArgs.GetFloat( "angular_friction" );
  1851. contact_friction = spawnArgs.GetFloat( "contact_friction" );
  1852. bounce = spawnArgs.GetFloat( "bounce" );
  1853. mass = spawnArgs.GetFloat( "mass" );
  1854. gravity = spawnArgs.GetFloat( "gravity" );
  1855. fuse = spawnArgs.GetFloat( "fuse" );
  1856. randomVelocity = spawnArgs.GetBool ( "random_velocity" );
  1857. if ( mass <= 0 ) {
  1858. gameLocal.Error( "Invalid mass on '%s'\n", GetEntityDefName() );
  1859. }
  1860. if ( randomVelocity ) {
  1861. velocity.x *= gameLocal.random.RandomFloat() + 0.5f;
  1862. velocity.y *= gameLocal.random.RandomFloat() + 0.5f;
  1863. velocity.z *= gameLocal.random.RandomFloat() + 0.5f;
  1864. }
  1865. if ( health ) {
  1866. fl.takedamage = true;
  1867. }
  1868. gravVec = gameLocal.GetGravity();
  1869. gravVec.NormalizeFast();
  1870. axis = GetPhysics()->GetAxis();
  1871. Unbind();
  1872. physicsObj.SetSelf( this );
  1873. // check if a clip model is set
  1874. const char *clipModelName;
  1875. idTraceModel trm;
  1876. spawnArgs.GetString( "clipmodel", "", &clipModelName );
  1877. if ( !clipModelName[0] ) {
  1878. clipModelName = spawnArgs.GetString( "model" ); // use the visual model
  1879. }
  1880. // load the trace model
  1881. if ( !collisionModelManager->TrmFromModel( clipModelName, trm ) ) {
  1882. // default to a box
  1883. physicsObj.SetClipBox( renderEntity.bounds, 1.0f );
  1884. } else {
  1885. physicsObj.SetClipModel( new idClipModel( trm ), 1.0f );
  1886. }
  1887. physicsObj.GetClipModel()->SetOwner( owner.GetEntity() );
  1888. physicsObj.SetMass( mass );
  1889. physicsObj.SetFriction( linear_friction, angular_friction, contact_friction );
  1890. if ( contact_friction == 0.0f ) {
  1891. physicsObj.NoContact();
  1892. }
  1893. physicsObj.SetBouncyness( bounce );
  1894. physicsObj.SetGravity( gravVec * gravity );
  1895. physicsObj.SetContents( 0 );
  1896. physicsObj.SetClipMask( MASK_SOLID | CONTENTS_MOVEABLECLIP );
  1897. physicsObj.SetLinearVelocity( axis[ 0 ] * velocity[ 0 ] + axis[ 1 ] * velocity[ 1 ] + axis[ 2 ] * velocity[ 2 ] );
  1898. physicsObj.SetAngularVelocity( angular_velocity.ToAngularVelocity() * axis );
  1899. physicsObj.SetOrigin( GetPhysics()->GetOrigin() );
  1900. physicsObj.SetAxis( axis );
  1901. SetPhysics( &physicsObj );
  1902. if ( !gameLocal.isClient ) {
  1903. if ( fuse <= 0 ) {
  1904. // run physics for 1 second
  1905. RunPhysics();
  1906. PostEventMS( &EV_Remove, 0 );
  1907. } else if ( spawnArgs.GetBool( "detonate_on_fuse" ) ) {
  1908. if ( fuse < 0.0f ) {
  1909. fuse = 0.0f;
  1910. }
  1911. RunPhysics();
  1912. PostEventSec( &EV_Explode, fuse );
  1913. } else {
  1914. if ( fuse < 0.0f ) {
  1915. fuse = 0.0f;
  1916. }
  1917. PostEventSec( &EV_Fizzle, fuse );
  1918. }
  1919. }
  1920. StartSound( "snd_fly", SND_CHANNEL_BODY, 0, false, NULL );
  1921. smokeFly = NULL;
  1922. smokeFlyTime = 0;
  1923. const char *smokeName = spawnArgs.GetString( "smoke_fly" );
  1924. if ( *smokeName != '\0' ) {
  1925. smokeFly = static_cast<const idDeclParticle *>( declManager->FindType( DECL_PARTICLE, smokeName ) );
  1926. smokeFlyTime = gameLocal.time;
  1927. gameLocal.smokeParticles->EmitSmoke( smokeFly, smokeFlyTime, gameLocal.random.CRandomFloat(), GetPhysics()->GetOrigin(), GetPhysics()->GetAxis() );
  1928. }
  1929. const char *sndName = spawnArgs.GetString( "snd_bounce" );
  1930. if ( *sndName != '\0' ) {
  1931. sndBounce = declManager->FindSound( sndName );
  1932. }
  1933. UpdateVisuals();
  1934. }
  1935. /*
  1936. ================
  1937. idDebris::Think
  1938. ================
  1939. */
  1940. void idDebris::Think( void ) {
  1941. // run physics
  1942. RunPhysics();
  1943. Present();
  1944. if ( smokeFly && smokeFlyTime ) {
  1945. if ( !gameLocal.smokeParticles->EmitSmoke( smokeFly, smokeFlyTime, gameLocal.random.CRandomFloat(), GetPhysics()->GetOrigin(), GetPhysics()->GetAxis() ) ) {
  1946. smokeFlyTime = 0;
  1947. }
  1948. }
  1949. }
  1950. /*
  1951. ================
  1952. idDebris::Killed
  1953. ================
  1954. */
  1955. void idDebris::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) {
  1956. if ( spawnArgs.GetBool( "detonate_on_death" ) ) {
  1957. Explode();
  1958. } else {
  1959. Fizzle();
  1960. }
  1961. }
  1962. /*
  1963. =================
  1964. idDebris::Collide
  1965. =================
  1966. */
  1967. bool idDebris::Collide( const trace_t &collision, const idVec3 &velocity ) {
  1968. if ( sndBounce != NULL ) {
  1969. StartSoundShader( sndBounce, SND_CHANNEL_BODY, 0, false, NULL );
  1970. }
  1971. sndBounce = NULL;
  1972. return false;
  1973. }
  1974. /*
  1975. ================
  1976. idDebris::Fizzle
  1977. ================
  1978. */
  1979. void idDebris::Fizzle( void ) {
  1980. if ( IsHidden() ) {
  1981. // already exploded
  1982. return;
  1983. }
  1984. StopSound( SND_CHANNEL_ANY, false );
  1985. StartSound( "snd_fizzle", SND_CHANNEL_BODY, 0, false, NULL );
  1986. // fizzle FX
  1987. const char *smokeName = spawnArgs.GetString( "smoke_fuse" );
  1988. if ( *smokeName != '\0' ) {
  1989. smokeFly = static_cast<const idDeclParticle *>( declManager->FindType( DECL_PARTICLE, smokeName ) );
  1990. smokeFlyTime = gameLocal.time;
  1991. gameLocal.smokeParticles->EmitSmoke( smokeFly, smokeFlyTime, gameLocal.random.CRandomFloat(), GetPhysics()->GetOrigin(), GetPhysics()->GetAxis() );
  1992. }
  1993. fl.takedamage = false;
  1994. physicsObj.SetContents( 0 );
  1995. physicsObj.PutToRest();
  1996. Hide();
  1997. if ( gameLocal.isClient ) {
  1998. return;
  1999. }
  2000. CancelEvents( &EV_Fizzle );
  2001. PostEventMS( &EV_Remove, 0 );
  2002. }
  2003. /*
  2004. ================
  2005. idDebris::Explode
  2006. ================
  2007. */
  2008. void idDebris::Explode( void ) {
  2009. if ( IsHidden() ) {
  2010. // already exploded
  2011. return;
  2012. }
  2013. StopSound( SND_CHANNEL_ANY, false );
  2014. StartSound( "snd_explode", SND_CHANNEL_BODY, 0, false, NULL );
  2015. Hide();
  2016. // these must not be "live forever" particle systems
  2017. smokeFly = NULL;
  2018. smokeFlyTime = 0;
  2019. const char *smokeName = spawnArgs.GetString( "smoke_detonate" );
  2020. if ( *smokeName != '\0' ) {
  2021. smokeFly = static_cast<const idDeclParticle *>( declManager->FindType( DECL_PARTICLE, smokeName ) );
  2022. smokeFlyTime = gameLocal.time;
  2023. gameLocal.smokeParticles->EmitSmoke( smokeFly, smokeFlyTime, gameLocal.random.CRandomFloat(), GetPhysics()->GetOrigin(), GetPhysics()->GetAxis() );
  2024. }
  2025. fl.takedamage = false;
  2026. physicsObj.SetContents( 0 );
  2027. physicsObj.PutToRest();
  2028. CancelEvents( &EV_Explode );
  2029. PostEventMS( &EV_Remove, 0 );
  2030. }
  2031. /*
  2032. ================
  2033. idDebris::Event_Explode
  2034. ================
  2035. */
  2036. void idDebris::Event_Explode( void ) {
  2037. Explode();
  2038. }
  2039. /*
  2040. ================
  2041. idDebris::Event_Fizzle
  2042. ================
  2043. */
  2044. void idDebris::Event_Fizzle( void ) {
  2045. Fizzle();
  2046. }