Moveable.cpp 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194
  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. idMoveable
  26. ===============================================================================
  27. */
  28. const idEventDef EV_BecomeNonSolid( "becomeNonSolid" );
  29. const idEventDef EV_SetOwnerFromSpawnArgs( "<setOwnerFromSpawnArgs>" );
  30. const idEventDef EV_IsAtRest( "isAtRest", NULL, 'd' );
  31. const idEventDef EV_EnableDamage( "enableDamage", "f" );
  32. CLASS_DECLARATION( idEntity, idMoveable )
  33. EVENT( EV_Activate, idMoveable::Event_Activate )
  34. EVENT( EV_BecomeNonSolid, idMoveable::Event_BecomeNonSolid )
  35. EVENT( EV_SetOwnerFromSpawnArgs, idMoveable::Event_SetOwnerFromSpawnArgs )
  36. EVENT( EV_IsAtRest, idMoveable::Event_IsAtRest )
  37. EVENT( EV_EnableDamage, idMoveable::Event_EnableDamage )
  38. END_CLASS
  39. static const float BOUNCE_SOUND_MIN_VELOCITY = 80.0f;
  40. static const float BOUNCE_SOUND_MAX_VELOCITY = 200.0f;
  41. /*
  42. ================
  43. idMoveable::idMoveable
  44. ================
  45. */
  46. idMoveable::idMoveable( void ) {
  47. minDamageVelocity = 100.0f;
  48. maxDamageVelocity = 200.0f;
  49. nextCollideFxTime = 0;
  50. nextDamageTime = 0;
  51. nextSoundTime = 0;
  52. initialSpline = NULL;
  53. initialSplineDir = vec3_zero;
  54. explode = false;
  55. unbindOnDeath = false;
  56. allowStep = false;
  57. canDamage = false;
  58. }
  59. /*
  60. ================
  61. idMoveable::~idMoveable
  62. ================
  63. */
  64. idMoveable::~idMoveable( void ) {
  65. delete initialSpline;
  66. initialSpline = NULL;
  67. }
  68. /*
  69. ================
  70. idMoveable::Spawn
  71. ================
  72. */
  73. void idMoveable::Spawn( void ) {
  74. idTraceModel trm;
  75. float density, friction, bouncyness, mass;
  76. int clipShrink;
  77. idStr clipModelName;
  78. // check if a clip model is set
  79. spawnArgs.GetString( "clipmodel", "", clipModelName );
  80. if ( !clipModelName[0] ) {
  81. clipModelName = spawnArgs.GetString( "model" ); // use the visual model
  82. }
  83. if ( !collisionModelManager->TrmFromModel( clipModelName, trm ) ) {
  84. gameLocal.Error( "idMoveable '%s': cannot load collision model %s", name.c_str(), clipModelName.c_str() );
  85. return;
  86. }
  87. // if the model should be shrinked
  88. clipShrink = spawnArgs.GetInt( "clipshrink" );
  89. if ( clipShrink != 0 ) {
  90. trm.Shrink( clipShrink * CM_CLIP_EPSILON );
  91. }
  92. // get rigid body properties
  93. spawnArgs.GetFloat( "density", "0.5", density );
  94. density = idMath::ClampFloat( 0.001f, 1000.0f, density );
  95. spawnArgs.GetFloat( "friction", "0.05", friction );
  96. friction = idMath::ClampFloat( 0.0f, 1.0f, friction );
  97. spawnArgs.GetFloat( "bouncyness", "0.6", bouncyness );
  98. bouncyness = idMath::ClampFloat( 0.0f, 1.0f, bouncyness );
  99. explode = spawnArgs.GetBool( "explode" );
  100. unbindOnDeath = spawnArgs.GetBool( "unbindondeath" );
  101. fxCollide = spawnArgs.GetString( "fx_collide" );
  102. nextCollideFxTime = 0;
  103. fl.takedamage = true;
  104. damage = spawnArgs.GetString( "def_damage", "" );
  105. canDamage = spawnArgs.GetBool( "damageWhenActive" ) ? false : true;
  106. minDamageVelocity = spawnArgs.GetFloat( "minDamageVelocity", "100" );
  107. maxDamageVelocity = spawnArgs.GetFloat( "maxDamageVelocity", "200" );
  108. nextDamageTime = 0;
  109. nextSoundTime = 0;
  110. health = spawnArgs.GetInt( "health", "0" );
  111. spawnArgs.GetString( "broken", "", brokenModel );
  112. if ( health ) {
  113. if ( brokenModel != "" && !renderModelManager->CheckModel( brokenModel ) ) {
  114. gameLocal.Error( "idMoveable '%s' at (%s): cannot load broken model '%s'", name.c_str(), GetPhysics()->GetOrigin().ToString(0), brokenModel.c_str() );
  115. }
  116. }
  117. // setup the physics
  118. physicsObj.SetSelf( this );
  119. physicsObj.SetClipModel( new idClipModel( trm ), density );
  120. physicsObj.GetClipModel()->SetMaterial( GetRenderModelMaterial() );
  121. physicsObj.SetOrigin( GetPhysics()->GetOrigin() );
  122. physicsObj.SetAxis( GetPhysics()->GetAxis() );
  123. physicsObj.SetBouncyness( bouncyness );
  124. physicsObj.SetFriction( 0.6f, 0.6f, friction );
  125. physicsObj.SetGravity( gameLocal.GetGravity() );
  126. physicsObj.SetContents( CONTENTS_SOLID );
  127. physicsObj.SetClipMask( MASK_SOLID | CONTENTS_BODY | CONTENTS_CORPSE | CONTENTS_MOVEABLECLIP );
  128. SetPhysics( &physicsObj );
  129. if ( spawnArgs.GetFloat( "mass", "10", mass ) ) {
  130. physicsObj.SetMass( mass );
  131. }
  132. if ( spawnArgs.GetBool( "nodrop" ) ) {
  133. physicsObj.PutToRest();
  134. } else {
  135. physicsObj.DropToFloor();
  136. }
  137. if ( spawnArgs.GetBool( "noimpact" ) || spawnArgs.GetBool( "notPushable" ) ) {
  138. physicsObj.DisableImpact();
  139. }
  140. if ( spawnArgs.GetBool( "nonsolid" ) ) {
  141. BecomeNonSolid();
  142. }
  143. allowStep = spawnArgs.GetBool( "allowStep", "1" );
  144. PostEventMS( &EV_SetOwnerFromSpawnArgs, 0 );
  145. }
  146. /*
  147. ================
  148. idMoveable::Save
  149. ================
  150. */
  151. void idMoveable::Save( idSaveGame *savefile ) const {
  152. savefile->WriteString( brokenModel );
  153. savefile->WriteString( damage );
  154. savefile->WriteString( fxCollide );
  155. savefile->WriteInt( nextCollideFxTime );
  156. savefile->WriteFloat( minDamageVelocity );
  157. savefile->WriteFloat( maxDamageVelocity );
  158. savefile->WriteBool( explode );
  159. savefile->WriteBool( unbindOnDeath );
  160. savefile->WriteBool( allowStep );
  161. savefile->WriteBool( canDamage );
  162. savefile->WriteInt( nextDamageTime );
  163. savefile->WriteInt( nextSoundTime );
  164. savefile->WriteInt( initialSpline != NULL ? initialSpline->GetTime( 0 ) : -1 );
  165. savefile->WriteVec3( initialSplineDir );
  166. savefile->WriteStaticObject( physicsObj );
  167. }
  168. /*
  169. ================
  170. idMoveable::Restore
  171. ================
  172. */
  173. void idMoveable::Restore( idRestoreGame *savefile ) {
  174. int initialSplineTime;
  175. savefile->ReadString( brokenModel );
  176. savefile->ReadString( damage );
  177. savefile->ReadString( fxCollide );
  178. savefile->ReadInt( nextCollideFxTime );
  179. savefile->ReadFloat( minDamageVelocity );
  180. savefile->ReadFloat( maxDamageVelocity );
  181. savefile->ReadBool( explode );
  182. savefile->ReadBool( unbindOnDeath );
  183. savefile->ReadBool( allowStep );
  184. savefile->ReadBool( canDamage );
  185. savefile->ReadInt( nextDamageTime );
  186. savefile->ReadInt( nextSoundTime );
  187. savefile->ReadInt( initialSplineTime );
  188. savefile->ReadVec3( initialSplineDir );
  189. if ( initialSplineTime != -1 ) {
  190. InitInitialSpline( initialSplineTime );
  191. } else {
  192. initialSpline = NULL;
  193. }
  194. savefile->ReadStaticObject( physicsObj );
  195. RestorePhysics( &physicsObj );
  196. }
  197. /*
  198. ================
  199. idMoveable::Hide
  200. ================
  201. */
  202. void idMoveable::Hide( void ) {
  203. idEntity::Hide();
  204. physicsObj.SetContents( 0 );
  205. }
  206. /*
  207. ================
  208. idMoveable::Show
  209. ================
  210. */
  211. void idMoveable::Show( void ) {
  212. idEntity::Show();
  213. if ( !spawnArgs.GetBool( "nonsolid" ) ) {
  214. physicsObj.SetContents( CONTENTS_SOLID );
  215. }
  216. }
  217. /*
  218. =================
  219. idMoveable::Collide
  220. =================
  221. */
  222. bool idMoveable::Collide( const trace_t &collision, const idVec3 &velocity ) {
  223. float v, f;
  224. idVec3 dir;
  225. idEntity *ent;
  226. v = -( velocity * collision.c.normal );
  227. if ( v > BOUNCE_SOUND_MIN_VELOCITY && gameLocal.time > nextSoundTime ) {
  228. f = v > BOUNCE_SOUND_MAX_VELOCITY ? 1.0f : idMath::Sqrt( v - BOUNCE_SOUND_MIN_VELOCITY ) * ( 1.0f / idMath::Sqrt( BOUNCE_SOUND_MAX_VELOCITY - BOUNCE_SOUND_MIN_VELOCITY ) );
  229. if ( StartSound( "snd_bounce", SND_CHANNEL_ANY, 0, false, NULL ) ) {
  230. // don't set the volume unless there is a bounce sound as it overrides the entire channel
  231. // which causes footsteps on ai's to not honor their shader parms
  232. SetSoundVolume( f );
  233. }
  234. nextSoundTime = gameLocal.time + 500;
  235. }
  236. if ( canDamage && damage.Length() && gameLocal.time > nextDamageTime ) {
  237. ent = gameLocal.entities[ collision.c.entityNum ];
  238. if ( ent && v > minDamageVelocity ) {
  239. f = v > maxDamageVelocity ? 1.0f : idMath::Sqrt( v - minDamageVelocity ) * ( 1.0f / idMath::Sqrt( maxDamageVelocity - minDamageVelocity ) );
  240. dir = velocity;
  241. dir.NormalizeFast();
  242. ent->Damage( this, GetPhysics()->GetClipModel()->GetOwner(), dir, damage, f, INVALID_JOINT );
  243. nextDamageTime = gameLocal.time + 1000;
  244. }
  245. }
  246. if ( fxCollide.Length() && gameLocal.time > nextCollideFxTime ) {
  247. idEntityFx::StartFx( fxCollide, &collision.c.point, NULL, this, false );
  248. nextCollideFxTime = gameLocal.time + 3500;
  249. }
  250. return false;
  251. }
  252. /*
  253. ============
  254. idMoveable::Killed
  255. ============
  256. */
  257. void idMoveable::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) {
  258. if ( unbindOnDeath ) {
  259. Unbind();
  260. }
  261. if ( brokenModel != "" ) {
  262. SetModel( brokenModel );
  263. }
  264. if ( explode ) {
  265. if ( brokenModel == "" ) {
  266. PostEventMS( &EV_Remove, 1000 );
  267. }
  268. }
  269. if ( renderEntity.gui[ 0 ] ) {
  270. renderEntity.gui[ 0 ] = NULL;
  271. }
  272. ActivateTargets( this );
  273. fl.takedamage = false;
  274. }
  275. /*
  276. ================
  277. idMoveable::AllowStep
  278. ================
  279. */
  280. bool idMoveable::AllowStep( void ) const {
  281. return allowStep;
  282. }
  283. /*
  284. ================
  285. idMoveable::BecomeNonSolid
  286. ================
  287. */
  288. void idMoveable::BecomeNonSolid( void ) {
  289. // set CONTENTS_RENDERMODEL so bullets still collide with the moveable
  290. physicsObj.SetContents( CONTENTS_CORPSE | CONTENTS_RENDERMODEL );
  291. physicsObj.SetClipMask( MASK_SOLID | CONTENTS_CORPSE | CONTENTS_MOVEABLECLIP );
  292. }
  293. /*
  294. ================
  295. idMoveable::EnableDamage
  296. ================
  297. */
  298. void idMoveable::EnableDamage( bool enable, float duration ) {
  299. canDamage = enable;
  300. if ( duration ) {
  301. PostEventSec( &EV_EnableDamage, duration, ( !enable ) ? 0.0f : 1.0f );
  302. }
  303. }
  304. /*
  305. ================
  306. idMoveable::InitInitialSpline
  307. ================
  308. */
  309. void idMoveable::InitInitialSpline( int startTime ) {
  310. int initialSplineTime;
  311. initialSpline = GetSpline();
  312. initialSplineTime = spawnArgs.GetInt( "initialSplineTime", "300" );
  313. if ( initialSpline != NULL ) {
  314. initialSpline->MakeUniform( initialSplineTime );
  315. initialSpline->ShiftTime( startTime - initialSpline->GetTime( 0 ) );
  316. initialSplineDir = initialSpline->GetCurrentFirstDerivative( startTime );
  317. initialSplineDir *= physicsObj.GetAxis().Transpose();
  318. initialSplineDir.Normalize();
  319. BecomeActive( TH_THINK );
  320. }
  321. }
  322. /*
  323. ================
  324. idMoveable::FollowInitialSplinePath
  325. ================
  326. */
  327. bool idMoveable::FollowInitialSplinePath( void ) {
  328. if ( initialSpline != NULL ) {
  329. if ( gameLocal.time < initialSpline->GetTime( initialSpline->GetNumValues() - 1 ) ) {
  330. idVec3 splinePos = initialSpline->GetCurrentValue( gameLocal.time );
  331. idVec3 linearVelocity = ( splinePos - physicsObj.GetOrigin() ) * USERCMD_HZ;
  332. physicsObj.SetLinearVelocity( linearVelocity );
  333. idVec3 splineDir = initialSpline->GetCurrentFirstDerivative( gameLocal.time );
  334. idVec3 dir = initialSplineDir * physicsObj.GetAxis();
  335. idVec3 angularVelocity = dir.Cross( splineDir );
  336. angularVelocity.Normalize();
  337. angularVelocity *= idMath::ACos16( dir * splineDir / splineDir.Length() ) * USERCMD_HZ;
  338. physicsObj.SetAngularVelocity( angularVelocity );
  339. return true;
  340. } else {
  341. delete initialSpline;
  342. initialSpline = NULL;
  343. }
  344. }
  345. return false;
  346. }
  347. /*
  348. ================
  349. idMoveable::Think
  350. ================
  351. */
  352. void idMoveable::Think( void ) {
  353. if ( thinkFlags & TH_THINK ) {
  354. if ( !FollowInitialSplinePath() ) {
  355. BecomeInactive( TH_THINK );
  356. }
  357. }
  358. idEntity::Think();
  359. }
  360. /*
  361. ================
  362. idMoveable::GetRenderModelMaterial
  363. ================
  364. */
  365. const idMaterial *idMoveable::GetRenderModelMaterial( void ) const {
  366. if ( renderEntity.customShader ) {
  367. return renderEntity.customShader;
  368. }
  369. if ( renderEntity.hModel && renderEntity.hModel->NumSurfaces() ) {
  370. return renderEntity.hModel->Surface( 0 )->shader;
  371. }
  372. return NULL;
  373. }
  374. /*
  375. ================
  376. idMoveable::WriteToSnapshot
  377. ================
  378. */
  379. void idMoveable::WriteToSnapshot( idBitMsgDelta &msg ) const {
  380. physicsObj.WriteToSnapshot( msg );
  381. }
  382. /*
  383. ================
  384. idMoveable::ReadFromSnapshot
  385. ================
  386. */
  387. void idMoveable::ReadFromSnapshot( const idBitMsgDelta &msg ) {
  388. physicsObj.ReadFromSnapshot( msg );
  389. if ( msg.HasChanged() ) {
  390. UpdateVisuals();
  391. }
  392. }
  393. /*
  394. ================
  395. idMoveable::Event_BecomeNonSolid
  396. ================
  397. */
  398. void idMoveable::Event_BecomeNonSolid( void ) {
  399. BecomeNonSolid();
  400. }
  401. /*
  402. ================
  403. idMoveable::Event_Activate
  404. ================
  405. */
  406. void idMoveable::Event_Activate( idEntity *activator ) {
  407. float delay;
  408. idVec3 init_velocity, init_avelocity;
  409. Show();
  410. if ( !spawnArgs.GetInt( "notPushable" ) ) {
  411. physicsObj.EnableImpact();
  412. }
  413. physicsObj.Activate();
  414. spawnArgs.GetVector( "init_velocity", "0 0 0", init_velocity );
  415. spawnArgs.GetVector( "init_avelocity", "0 0 0", init_avelocity );
  416. delay = spawnArgs.GetFloat( "init_velocityDelay", "0" );
  417. if ( delay == 0.0f ) {
  418. physicsObj.SetLinearVelocity( init_velocity );
  419. } else {
  420. PostEventSec( &EV_SetLinearVelocity, delay, init_velocity );
  421. }
  422. delay = spawnArgs.GetFloat( "init_avelocityDelay", "0" );
  423. if ( delay == 0.0f ) {
  424. physicsObj.SetAngularVelocity( init_avelocity );
  425. } else {
  426. PostEventSec( &EV_SetAngularVelocity, delay, init_avelocity );
  427. }
  428. InitInitialSpline( gameLocal.time );
  429. }
  430. /*
  431. ================
  432. idMoveable::Event_SetOwnerFromSpawnArgs
  433. ================
  434. */
  435. void idMoveable::Event_SetOwnerFromSpawnArgs( void ) {
  436. idStr owner;
  437. if ( spawnArgs.GetString( "owner", "", owner ) ) {
  438. ProcessEvent( &EV_SetOwner, gameLocal.FindEntity( owner ) );
  439. }
  440. }
  441. /*
  442. ================
  443. idMoveable::Event_IsAtRest
  444. ================
  445. */
  446. void idMoveable::Event_IsAtRest( void ) {
  447. idThread::ReturnInt( physicsObj.IsAtRest() );
  448. }
  449. /*
  450. ================
  451. idMoveable::Event_EnableDamage
  452. ================
  453. */
  454. void idMoveable::Event_EnableDamage( float enable ) {
  455. canDamage = ( enable != 0.0f );
  456. }
  457. /*
  458. ===============================================================================
  459. idBarrel
  460. ===============================================================================
  461. */
  462. CLASS_DECLARATION( idMoveable, idBarrel )
  463. END_CLASS
  464. /*
  465. ================
  466. idBarrel::idBarrel
  467. ================
  468. */
  469. idBarrel::idBarrel() {
  470. radius = 1.0f;
  471. barrelAxis = 0;
  472. lastOrigin.Zero();
  473. lastAxis.Identity();
  474. additionalRotation = 0.0f;
  475. additionalAxis.Identity();
  476. fl.networkSync = true;
  477. }
  478. /*
  479. ================
  480. idBarrel::Save
  481. ================
  482. */
  483. void idBarrel::Save( idSaveGame *savefile ) const {
  484. savefile->WriteFloat( radius );
  485. savefile->WriteInt( barrelAxis );
  486. savefile->WriteVec3( lastOrigin );
  487. savefile->WriteMat3( lastAxis );
  488. savefile->WriteFloat( additionalRotation );
  489. savefile->WriteMat3( additionalAxis );
  490. }
  491. /*
  492. ================
  493. idBarrel::Restore
  494. ================
  495. */
  496. void idBarrel::Restore( idRestoreGame *savefile ) {
  497. savefile->ReadFloat( radius );
  498. savefile->ReadInt( barrelAxis );
  499. savefile->ReadVec3( lastOrigin );
  500. savefile->ReadMat3( lastAxis );
  501. savefile->ReadFloat( additionalRotation );
  502. savefile->ReadMat3( additionalAxis );
  503. }
  504. /*
  505. ================
  506. idBarrel::BarrelThink
  507. ================
  508. */
  509. void idBarrel::BarrelThink( void ) {
  510. bool wasAtRest, onGround;
  511. float movedDistance, rotatedDistance, angle;
  512. idVec3 curOrigin, gravityNormal, dir;
  513. idMat3 curAxis, axis;
  514. wasAtRest = IsAtRest();
  515. // run physics
  516. RunPhysics();
  517. // only need to give the visual model an additional rotation if the physics were run
  518. if ( !wasAtRest ) {
  519. // current physics state
  520. onGround = GetPhysics()->HasGroundContacts();
  521. curOrigin = GetPhysics()->GetOrigin();
  522. curAxis = GetPhysics()->GetAxis();
  523. // if the barrel is on the ground
  524. if ( onGround ) {
  525. gravityNormal = GetPhysics()->GetGravityNormal();
  526. dir = curOrigin - lastOrigin;
  527. dir -= gravityNormal * dir * gravityNormal;
  528. movedDistance = dir.LengthSqr();
  529. // if the barrel moved and the barrel is not aligned with the gravity direction
  530. if ( movedDistance > 0.0f && idMath::Fabs( gravityNormal * curAxis[barrelAxis] ) < 0.7f ) {
  531. // barrel movement since last think frame orthogonal to the barrel axis
  532. movedDistance = idMath::Sqrt( movedDistance );
  533. dir *= 1.0f / movedDistance;
  534. movedDistance = ( 1.0f - idMath::Fabs( dir * curAxis[barrelAxis] ) ) * movedDistance;
  535. // get rotation about barrel axis since last think frame
  536. angle = lastAxis[(barrelAxis+1)%3] * curAxis[(barrelAxis+1)%3];
  537. angle = idMath::ACos( angle );
  538. // distance along cylinder hull
  539. rotatedDistance = angle * radius;
  540. // if the barrel moved further than it rotated about it's axis
  541. if ( movedDistance > rotatedDistance ) {
  542. // additional rotation of the visual model to make it look
  543. // like the barrel rolls instead of slides
  544. angle = 180.0f * (movedDistance - rotatedDistance) / (radius * idMath::PI);
  545. if ( gravityNormal.Cross( curAxis[barrelAxis] ) * dir < 0.0f ) {
  546. additionalRotation += angle;
  547. } else {
  548. additionalRotation -= angle;
  549. }
  550. dir = vec3_origin;
  551. dir[barrelAxis] = 1.0f;
  552. additionalAxis = idRotation( vec3_origin, dir, additionalRotation ).ToMat3();
  553. }
  554. }
  555. }
  556. // save state for next think
  557. lastOrigin = curOrigin;
  558. lastAxis = curAxis;
  559. }
  560. Present();
  561. }
  562. /*
  563. ================
  564. idBarrel::Think
  565. ================
  566. */
  567. void idBarrel::Think( void ) {
  568. if ( thinkFlags & TH_THINK ) {
  569. if ( !FollowInitialSplinePath() ) {
  570. BecomeInactive( TH_THINK );
  571. }
  572. }
  573. BarrelThink();
  574. }
  575. /*
  576. ================
  577. idBarrel::GetPhysicsToVisualTransform
  578. ================
  579. */
  580. bool idBarrel::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) {
  581. origin = vec3_origin;
  582. axis = additionalAxis;
  583. return true;
  584. }
  585. /*
  586. ================
  587. idBarrel::Spawn
  588. ================
  589. */
  590. void idBarrel::Spawn( void ) {
  591. const idBounds &bounds = GetPhysics()->GetBounds();
  592. // radius of the barrel cylinder
  593. radius = ( bounds[1][0] - bounds[0][0] ) * 0.5f;
  594. // always a vertical barrel with cylinder axis parallel to the z-axis
  595. barrelAxis = 2;
  596. lastOrigin = GetPhysics()->GetOrigin();
  597. lastAxis = GetPhysics()->GetAxis();
  598. additionalRotation = 0.0f;
  599. additionalAxis.Identity();
  600. }
  601. /*
  602. ================
  603. idBarrel::ClientPredictionThink
  604. ================
  605. */
  606. void idBarrel::ClientPredictionThink( void ) {
  607. Think();
  608. }
  609. /*
  610. ===============================================================================
  611. idExplodingBarrel
  612. ===============================================================================
  613. */
  614. const idEventDef EV_Respawn( "<respawn>" );
  615. const idEventDef EV_TriggerTargets( "<triggertargets>" );
  616. CLASS_DECLARATION( idBarrel, idExplodingBarrel )
  617. EVENT( EV_Activate, idExplodingBarrel::Event_Activate )
  618. EVENT( EV_Respawn, idExplodingBarrel::Event_Respawn )
  619. EVENT( EV_Explode, idExplodingBarrel::Event_Explode )
  620. EVENT( EV_TriggerTargets, idExplodingBarrel::Event_TriggerTargets )
  621. END_CLASS
  622. /*
  623. ================
  624. idExplodingBarrel::idExplodingBarrel
  625. ================
  626. */
  627. idExplodingBarrel::idExplodingBarrel() {
  628. spawnOrigin.Zero();
  629. spawnAxis.Zero();
  630. state = NORMAL;
  631. particleModelDefHandle = -1;
  632. lightDefHandle = -1;
  633. memset( &particleRenderEntity, 0, sizeof( particleRenderEntity ) );
  634. memset( &light, 0, sizeof( light ) );
  635. particleTime = 0;
  636. lightTime = 0;
  637. time = 0.0f;
  638. }
  639. /*
  640. ================
  641. idExplodingBarrel::~idExplodingBarrel
  642. ================
  643. */
  644. idExplodingBarrel::~idExplodingBarrel() {
  645. if ( particleModelDefHandle >= 0 ){
  646. gameRenderWorld->FreeEntityDef( particleModelDefHandle );
  647. }
  648. if ( lightDefHandle >= 0 ) {
  649. gameRenderWorld->FreeLightDef( lightDefHandle );
  650. }
  651. }
  652. /*
  653. ================
  654. idExplodingBarrel::Save
  655. ================
  656. */
  657. void idExplodingBarrel::Save( idSaveGame *savefile ) const {
  658. savefile->WriteVec3( spawnOrigin );
  659. savefile->WriteMat3( spawnAxis );
  660. savefile->WriteInt( state );
  661. savefile->WriteInt( particleModelDefHandle );
  662. savefile->WriteInt( lightDefHandle );
  663. savefile->WriteRenderEntity( particleRenderEntity );
  664. savefile->WriteRenderLight( light );
  665. savefile->WriteInt( particleTime );
  666. savefile->WriteInt( lightTime );
  667. savefile->WriteFloat( time );
  668. }
  669. /*
  670. ================
  671. idExplodingBarrel::Restore
  672. ================
  673. */
  674. void idExplodingBarrel::Restore( idRestoreGame *savefile ) {
  675. savefile->ReadVec3( spawnOrigin );
  676. savefile->ReadMat3( spawnAxis );
  677. savefile->ReadInt( (int &)state );
  678. savefile->ReadInt( (int &)particleModelDefHandle );
  679. savefile->ReadInt( (int &)lightDefHandle );
  680. savefile->ReadRenderEntity( particleRenderEntity );
  681. savefile->ReadRenderLight( light );
  682. savefile->ReadInt( particleTime );
  683. savefile->ReadInt( lightTime );
  684. savefile->ReadFloat( time );
  685. }
  686. /*
  687. ================
  688. idExplodingBarrel::Spawn
  689. ================
  690. */
  691. void idExplodingBarrel::Spawn( void ) {
  692. health = spawnArgs.GetInt( "health", "5" );
  693. fl.takedamage = true;
  694. spawnOrigin = GetPhysics()->GetOrigin();
  695. spawnAxis = GetPhysics()->GetAxis();
  696. state = NORMAL;
  697. particleModelDefHandle = -1;
  698. lightDefHandle = -1;
  699. lightTime = 0;
  700. particleTime = 0;
  701. time = spawnArgs.GetFloat( "time" );
  702. memset( &particleRenderEntity, 0, sizeof( particleRenderEntity ) );
  703. memset( &light, 0, sizeof( light ) );
  704. }
  705. /*
  706. ================
  707. idExplodingBarrel::Think
  708. ================
  709. */
  710. void idExplodingBarrel::Think( void ) {
  711. idBarrel::BarrelThink();
  712. if ( lightDefHandle >= 0 ){
  713. if ( state == BURNING ) {
  714. // ramp the color up over 250 ms
  715. float pct = (gameLocal.time - lightTime) / 250.f;
  716. if ( pct > 1.0f ) {
  717. pct = 1.0f;
  718. }
  719. light.origin = physicsObj.GetAbsBounds().GetCenter();
  720. light.axis = mat3_identity;
  721. light.shaderParms[ SHADERPARM_RED ] = pct;
  722. light.shaderParms[ SHADERPARM_GREEN ] = pct;
  723. light.shaderParms[ SHADERPARM_BLUE ] = pct;
  724. light.shaderParms[ SHADERPARM_ALPHA ] = pct;
  725. gameRenderWorld->UpdateLightDef( lightDefHandle, &light );
  726. } else {
  727. if ( gameLocal.time - lightTime > 250 ) {
  728. gameRenderWorld->FreeLightDef( lightDefHandle );
  729. lightDefHandle = -1;
  730. }
  731. return;
  732. }
  733. }
  734. if ( !gameLocal.isClient && state != BURNING && state != EXPLODING ) {
  735. BecomeInactive( TH_THINK );
  736. return;
  737. }
  738. if ( particleModelDefHandle >= 0 ){
  739. particleRenderEntity.origin = physicsObj.GetAbsBounds().GetCenter();
  740. particleRenderEntity.axis = mat3_identity;
  741. gameRenderWorld->UpdateEntityDef( particleModelDefHandle, &particleRenderEntity );
  742. }
  743. }
  744. /*
  745. ================
  746. idExplodingBarrel::AddParticles
  747. ================
  748. */
  749. void idExplodingBarrel::AddParticles( const char *name, bool burn ) {
  750. if ( name && *name ) {
  751. if ( particleModelDefHandle >= 0 ){
  752. gameRenderWorld->FreeEntityDef( particleModelDefHandle );
  753. }
  754. memset( &particleRenderEntity, 0, sizeof ( particleRenderEntity ) );
  755. const idDeclModelDef *modelDef = static_cast<const idDeclModelDef *>( declManager->FindType( DECL_MODELDEF, name ) );
  756. if ( modelDef ) {
  757. particleRenderEntity.origin = physicsObj.GetAbsBounds().GetCenter();
  758. particleRenderEntity.axis = mat3_identity;
  759. particleRenderEntity.hModel = modelDef->ModelHandle();
  760. float rgb = ( burn ) ? 0.0f : 1.0f;
  761. particleRenderEntity.shaderParms[ SHADERPARM_RED ] = rgb;
  762. particleRenderEntity.shaderParms[ SHADERPARM_GREEN ] = rgb;
  763. particleRenderEntity.shaderParms[ SHADERPARM_BLUE ] = rgb;
  764. particleRenderEntity.shaderParms[ SHADERPARM_ALPHA ] = rgb;
  765. particleRenderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.realClientTime );
  766. particleRenderEntity.shaderParms[ SHADERPARM_DIVERSITY ] = ( burn ) ? 1.0f : gameLocal.random.RandomInt( 90 );
  767. if ( !particleRenderEntity.hModel ) {
  768. particleRenderEntity.hModel = renderModelManager->FindModel( name );
  769. }
  770. particleModelDefHandle = gameRenderWorld->AddEntityDef( &particleRenderEntity );
  771. if ( burn ) {
  772. BecomeActive( TH_THINK );
  773. }
  774. particleTime = gameLocal.realClientTime;
  775. }
  776. }
  777. }
  778. /*
  779. ================
  780. idExplodingBarrel::AddLight
  781. ================
  782. */
  783. void idExplodingBarrel::AddLight( const char *name, bool burn ) {
  784. if ( lightDefHandle >= 0 ){
  785. gameRenderWorld->FreeLightDef( lightDefHandle );
  786. }
  787. memset( &light, 0, sizeof ( light ) );
  788. light.axis = mat3_identity;
  789. light.lightRadius.x = spawnArgs.GetFloat( "light_radius" );
  790. light.lightRadius.y = light.lightRadius.z = light.lightRadius.x;
  791. light.origin = physicsObj.GetOrigin();
  792. light.origin.z += 128;
  793. light.pointLight = true;
  794. light.shader = declManager->FindMaterial( name );
  795. light.shaderParms[ SHADERPARM_RED ] = 2.0f;
  796. light.shaderParms[ SHADERPARM_GREEN ] = 2.0f;
  797. light.shaderParms[ SHADERPARM_BLUE ] = 2.0f;
  798. light.shaderParms[ SHADERPARM_ALPHA ] = 2.0f;
  799. lightDefHandle = gameRenderWorld->AddLightDef( &light );
  800. lightTime = gameLocal.realClientTime;
  801. BecomeActive( TH_THINK );
  802. }
  803. /*
  804. ================
  805. idExplodingBarrel::ExplodingEffects
  806. ================
  807. */
  808. void idExplodingBarrel::ExplodingEffects( void ) {
  809. const char *temp;
  810. StartSound( "snd_explode", SND_CHANNEL_ANY, 0, false, NULL );
  811. temp = spawnArgs.GetString( "model_damage" );
  812. if ( *temp != '\0' ) {
  813. SetModel( temp );
  814. Show();
  815. }
  816. temp = spawnArgs.GetString( "model_detonate" );
  817. if ( *temp != '\0' ) {
  818. AddParticles( temp, false );
  819. }
  820. temp = spawnArgs.GetString( "mtr_lightexplode" );
  821. if ( *temp != '\0' ) {
  822. AddLight( temp, false );
  823. }
  824. temp = spawnArgs.GetString( "mtr_burnmark" );
  825. if ( *temp != '\0' ) {
  826. gameLocal.ProjectDecal( GetPhysics()->GetOrigin(), GetPhysics()->GetGravity(), 128.0f, true, 96.0f, temp );
  827. }
  828. }
  829. /*
  830. ================
  831. idExplodingBarrel::Killed
  832. ================
  833. */
  834. void idExplodingBarrel::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) {
  835. if ( IsHidden() || state == EXPLODING || state == BURNING ) {
  836. return;
  837. }
  838. float f = spawnArgs.GetFloat( "burn" );
  839. if ( f > 0.0f && state == NORMAL ) {
  840. state = BURNING;
  841. PostEventSec( &EV_Explode, f );
  842. StartSound( "snd_burn", SND_CHANNEL_ANY, 0, false, NULL );
  843. AddParticles( spawnArgs.GetString ( "model_burn", "" ), true );
  844. return;
  845. } else {
  846. state = EXPLODING;
  847. if ( gameLocal.isServer ) {
  848. idBitMsg msg;
  849. byte msgBuf[MAX_EVENT_PARAM_SIZE];
  850. msg.Init( msgBuf, sizeof( msgBuf ) );
  851. msg.WriteLong( gameLocal.time );
  852. ServerSendEvent( EVENT_EXPLODE, &msg, false, -1 );
  853. }
  854. }
  855. // do this before applying radius damage so the ent can trace to any damagable ents nearby
  856. Hide();
  857. physicsObj.SetContents( 0 );
  858. const char *splash = spawnArgs.GetString( "def_splash_damage", "damage_explosion" );
  859. if ( splash && *splash ) {
  860. gameLocal.RadiusDamage( GetPhysics()->GetOrigin(), this, attacker, this, this, splash );
  861. }
  862. ExplodingEffects( );
  863. //FIXME: need to precache all the debris stuff here and in the projectiles
  864. const idKeyValue *kv = spawnArgs.MatchPrefix( "def_debris" );
  865. // bool first = true;
  866. while ( kv ) {
  867. const idDict *debris_args = gameLocal.FindEntityDefDict( kv->GetValue(), false );
  868. if ( debris_args ) {
  869. idEntity *ent;
  870. idVec3 dir;
  871. idDebris *debris;
  872. //if ( first ) {
  873. dir = physicsObj.GetAxis()[1];
  874. // first = false;
  875. //} else {
  876. dir.x += gameLocal.random.CRandomFloat() * 4.0f;
  877. dir.y += gameLocal.random.CRandomFloat() * 4.0f;
  878. //dir.z = gameLocal.random.RandomFloat() * 8.0f;
  879. //}
  880. dir.Normalize();
  881. gameLocal.SpawnEntityDef( *debris_args, &ent, false );
  882. if ( !ent || !ent->IsType( idDebris::Type ) ) {
  883. gameLocal.Error( "'projectile_debris' is not an idDebris" );
  884. }
  885. debris = static_cast<idDebris *>(ent);
  886. debris->Create( this, physicsObj.GetOrigin(), dir.ToMat3() );
  887. debris->Launch();
  888. debris->GetRenderEntity()->shaderParms[ SHADERPARM_TIME_OF_DEATH ] = ( gameLocal.time + 1500 ) * 0.001f;
  889. debris->UpdateVisuals();
  890. }
  891. kv = spawnArgs.MatchPrefix( "def_debris", kv );
  892. }
  893. physicsObj.PutToRest();
  894. CancelEvents( &EV_Explode );
  895. CancelEvents( &EV_Activate );
  896. f = spawnArgs.GetFloat( "respawn" );
  897. if ( f > 0.0f ) {
  898. PostEventSec( &EV_Respawn, f );
  899. } else {
  900. PostEventMS( &EV_Remove, 5000 );
  901. }
  902. if ( spawnArgs.GetBool( "triggerTargets" ) ) {
  903. ActivateTargets( this );
  904. }
  905. }
  906. /*
  907. ================
  908. idExplodingBarrel::Damage
  909. ================
  910. */
  911. void idExplodingBarrel::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir,
  912. const char *damageDefName, const float damageScale, const int location ) {
  913. const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName );
  914. if ( !damageDef ) {
  915. gameLocal.Error( "Unknown damageDef '%s'\n", damageDefName );
  916. }
  917. if ( damageDef->FindKey( "radius" ) && GetPhysics()->GetContents() != 0 && GetBindMaster() == NULL ) {
  918. PostEventMS( &EV_Explode, 400 );
  919. } else {
  920. idEntity::Damage( inflictor, attacker, dir, damageDefName, damageScale, location );
  921. }
  922. }
  923. /*
  924. ================
  925. idExplodingBarrel::Event_TriggerTargets
  926. ================
  927. */
  928. void idExplodingBarrel::Event_TriggerTargets() {
  929. ActivateTargets( this );
  930. }
  931. /*
  932. ================
  933. idExplodingBarrel::Event_Explode
  934. ================
  935. */
  936. void idExplodingBarrel::Event_Explode() {
  937. if ( state == NORMAL || state == BURNING ) {
  938. state = BURNEXPIRED;
  939. Killed( NULL, NULL, 0, vec3_zero, 0 );
  940. }
  941. }
  942. /*
  943. ================
  944. idExplodingBarrel::Event_Respawn
  945. ================
  946. */
  947. void idExplodingBarrel::Event_Respawn() {
  948. int i;
  949. int minRespawnDist = spawnArgs.GetInt( "respawn_range", "256" );
  950. if ( minRespawnDist ) {
  951. float minDist = -1;
  952. for ( i = 0; i < gameLocal.numClients; i++ ) {
  953. if ( !gameLocal.entities[ i ] || !gameLocal.entities[ i ]->IsType( idPlayer::Type ) ) {
  954. continue;
  955. }
  956. idVec3 v = gameLocal.entities[ i ]->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin();
  957. float dist = v.Length();
  958. if ( minDist < 0 || dist < minDist ) {
  959. minDist = dist;
  960. }
  961. }
  962. if ( minDist < minRespawnDist ) {
  963. PostEventSec( &EV_Respawn, spawnArgs.GetInt( "respawn_again", "10" ) );
  964. return;
  965. }
  966. }
  967. const char *temp = spawnArgs.GetString( "model" );
  968. if ( temp && *temp ) {
  969. SetModel( temp );
  970. }
  971. health = spawnArgs.GetInt( "health", "5" );
  972. fl.takedamage = true;
  973. physicsObj.SetOrigin( spawnOrigin );
  974. physicsObj.SetAxis( spawnAxis );
  975. physicsObj.SetContents( CONTENTS_SOLID );
  976. physicsObj.DropToFloor();
  977. state = NORMAL;
  978. Show();
  979. UpdateVisuals();
  980. }
  981. /*
  982. ================
  983. idMoveable::Event_Activate
  984. ================
  985. */
  986. void idExplodingBarrel::Event_Activate( idEntity *activator ) {
  987. Killed( activator, activator, 0, vec3_origin, 0 );
  988. }
  989. /*
  990. ================
  991. idMoveable::WriteToSnapshot
  992. ================
  993. */
  994. void idExplodingBarrel::WriteToSnapshot( idBitMsgDelta &msg ) const {
  995. idMoveable::WriteToSnapshot( msg );
  996. msg.WriteBits( IsHidden(), 1 );
  997. }
  998. /*
  999. ================
  1000. idMoveable::ReadFromSnapshot
  1001. ================
  1002. */
  1003. void idExplodingBarrel::ReadFromSnapshot( const idBitMsgDelta &msg ) {
  1004. idMoveable::ReadFromSnapshot( msg );
  1005. if ( msg.ReadBits( 1 ) ) {
  1006. Hide();
  1007. } else {
  1008. Show();
  1009. }
  1010. }
  1011. /*
  1012. ================
  1013. idExplodingBarrel::ClientReceiveEvent
  1014. ================
  1015. */
  1016. bool idExplodingBarrel::ClientReceiveEvent( int event, int time, const idBitMsg &msg ) {
  1017. switch( event ) {
  1018. case EVENT_EXPLODE: {
  1019. if ( gameLocal.realClientTime - msg.ReadLong() < spawnArgs.GetInt( "explode_lapse", "1000" ) ) {
  1020. ExplodingEffects( );
  1021. }
  1022. return true;
  1023. }
  1024. default: {
  1025. return idBarrel::ClientReceiveEvent( event, time, msg );
  1026. }
  1027. }
  1028. return false;
  1029. }