Projectile.cpp 82 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990
  1. /*
  2. ===========================================================================
  3. Doom 3 BFG Edition GPL Source Code
  4. Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
  5. This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
  6. Doom 3 BFG Edition 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 BFG Edition 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 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
  16. In addition, the Doom 3 BFG Edition 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 BFG Edition 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. idCVar g_projectileDebug( "g_projectileDebug", "0", CVAR_BOOL, "Debug projectiles" );
  29. // This is used in MP to simulate frames to catchup a projectile's state. Similiar to how players work much lighter weight.
  30. // This is used in MP to simulate frames to catchup a projectile's state. Similiar to how players work much lighter weight.
  31. idArray< idProjectile::simulatedProjectile_t, idProjectile::MAX_SIMULATED_PROJECTILES > idProjectile::projectilesToSimulate;
  32. static const int BFG_DAMAGE_FREQUENCY = 333;
  33. static const float BOUNCE_SOUND_MIN_VELOCITY = 200.0f;
  34. static const float BOUNCE_SOUND_MAX_VELOCITY = 400.0f;
  35. const idEventDef EV_Explode( "<explode>", NULL );
  36. const idEventDef EV_Fizzle( "<fizzle>", NULL );
  37. const idEventDef EV_RadiusDamage( "<radiusdmg>", "e" );
  38. const idEventDef EV_GetProjectileState( "getProjectileState", NULL, 'd' );
  39. const idEventDef EV_CreateProjectile( "projectileCreateProjectile", "evv" );
  40. const idEventDef EV_LaunchProjectile( "projectileLaunchProjectile", "vvv" );
  41. const idEventDef EV_SetGravity( "setGravity", "f" );
  42. CLASS_DECLARATION( idEntity, idProjectile )
  43. EVENT( EV_Explode, idProjectile::Event_Explode )
  44. EVENT( EV_Fizzle, idProjectile::Event_Fizzle )
  45. EVENT( EV_Touch, idProjectile::Event_Touch )
  46. EVENT( EV_RadiusDamage, idProjectile::Event_RadiusDamage )
  47. EVENT( EV_GetProjectileState, idProjectile::Event_GetProjectileState )
  48. EVENT( EV_CreateProjectile, idProjectile::Event_CreateProjectile )
  49. EVENT( EV_LaunchProjectile, idProjectile::Event_LaunchProjectile )
  50. EVENT( EV_SetGravity, idProjectile::Event_SetGravity )
  51. END_CLASS
  52. /*
  53. ================
  54. idProjectile::idProjectile
  55. ================
  56. */
  57. idProjectile::idProjectile() :
  58. launchOrigin( 0.0f ),
  59. launchAxis( mat3_identity ) {
  60. owner = NULL;
  61. lightDefHandle = -1;
  62. thrust = 0.0f;
  63. thrust_end = 0;
  64. smokeFly = NULL;
  65. smokeFlyTime = 0;
  66. state = SPAWNED;
  67. lightOffset = vec3_zero;
  68. lightStartTime = 0;
  69. lightEndTime = 0;
  70. lightColor = vec3_zero;
  71. damagePower = 1.0f;
  72. launchedFromGrabber = false;
  73. mTouchTriggers = false;
  74. mNoExplodeDisappear = false;
  75. memset( &projectileFlags, 0, sizeof( projectileFlags ) );
  76. memset( &renderLight, 0, sizeof( renderLight ) );
  77. // note: for net_instanthit projectiles, we will force this back to false at spawn time
  78. fl.networkSync = true;
  79. }
  80. /*
  81. ================
  82. idProjectile::Spawn
  83. ================
  84. */
  85. void idProjectile::Spawn() {
  86. physicsObj.SetSelf( this );
  87. physicsObj.SetClipModel( new (TAG_PHYSICS_CLIP_ENTITY) idClipModel( GetPhysics()->GetClipModel() ), 1.0f );
  88. physicsObj.SetContents( 0 );
  89. physicsObj.SetClipMask( 0 );
  90. physicsObj.PutToRest();
  91. SetPhysics( &physicsObj );
  92. mNoExplodeDisappear = spawnArgs.GetBool( "no_explode_disappear", mNoExplodeDisappear );
  93. mTouchTriggers = spawnArgs.GetBool( "touch_triggers", mTouchTriggers );
  94. }
  95. /*
  96. ================
  97. idProjectile::Save
  98. ================
  99. */
  100. void idProjectile::Save( idSaveGame *savefile ) const {
  101. owner.Save( savefile );
  102. projectileFlags_s flags = projectileFlags;
  103. LittleBitField( &flags, sizeof( flags ) );
  104. savefile->Write( &flags, sizeof( flags ) );
  105. savefile->WriteFloat( thrust );
  106. savefile->WriteInt( thrust_end );
  107. savefile->WriteRenderLight( renderLight );
  108. savefile->WriteInt( (int)lightDefHandle );
  109. savefile->WriteVec3( lightOffset );
  110. savefile->WriteInt( lightStartTime );
  111. savefile->WriteInt( lightEndTime );
  112. savefile->WriteVec3( lightColor );
  113. savefile->WriteParticle( smokeFly );
  114. savefile->WriteInt( smokeFlyTime );
  115. savefile->WriteInt( originalTimeGroup );
  116. savefile->WriteInt( (int)state );
  117. savefile->WriteFloat( damagePower );
  118. savefile->WriteStaticObject( physicsObj );
  119. savefile->WriteStaticObject( thruster );
  120. }
  121. /*
  122. ================
  123. idProjectile::Restore
  124. ================
  125. */
  126. void idProjectile::Restore( idRestoreGame *savefile ) {
  127. owner.Restore( savefile );
  128. savefile->Read( &projectileFlags, sizeof( projectileFlags ) );
  129. LittleBitField( &projectileFlags, sizeof( projectileFlags ) );
  130. savefile->ReadFloat( thrust );
  131. savefile->ReadInt( thrust_end );
  132. savefile->ReadRenderLight( renderLight );
  133. savefile->ReadInt( (int &)lightDefHandle );
  134. savefile->ReadVec3( lightOffset );
  135. savefile->ReadInt( lightStartTime );
  136. savefile->ReadInt( lightEndTime );
  137. savefile->ReadVec3( lightColor );
  138. savefile->ReadParticle( smokeFly );
  139. savefile->ReadInt( smokeFlyTime );
  140. savefile->ReadInt( originalTimeGroup );
  141. savefile->ReadInt( (int &)state );
  142. savefile->ReadFloat( damagePower );
  143. savefile->ReadStaticObject( physicsObj );
  144. RestorePhysics( &physicsObj );
  145. savefile->ReadStaticObject( thruster );
  146. thruster.SetPhysics( &physicsObj );
  147. if ( smokeFly != NULL ) {
  148. idVec3 dir;
  149. dir = physicsObj.GetLinearVelocity();
  150. dir.NormalizeFast();
  151. gameLocal.smokeParticles->EmitSmoke( smokeFly, gameLocal.time, gameLocal.random.RandomFloat(), GetPhysics()->GetOrigin(), GetPhysics()->GetAxis(), timeGroup /*_D3XP*/ );
  152. }
  153. if ( lightDefHandle >= 0 ) {
  154. lightDefHandle = gameRenderWorld->AddLightDef( &renderLight );
  155. }
  156. }
  157. /*
  158. ================
  159. idProjectile::GetOwner
  160. ================
  161. */
  162. idEntity *idProjectile::GetOwner() const {
  163. return owner.GetEntity();
  164. }
  165. /*
  166. ================
  167. idProjectile::Create
  168. ================
  169. */
  170. void idProjectile::Create( idEntity *owner, const idVec3 &start, const idVec3 &dir ) {
  171. idDict args;
  172. idStr shaderName;
  173. idVec3 light_color;
  174. idVec3 light_offset;
  175. idVec3 tmp;
  176. idMat3 axis;
  177. Unbind();
  178. // align z-axis of model with the direction
  179. axis = dir.ToMat3();
  180. tmp = axis[2];
  181. axis[2] = axis[0];
  182. axis[0] = -tmp;
  183. physicsObj.SetOrigin( start );
  184. physicsObj.SetAxis( axis );
  185. physicsObj.GetClipModel()->SetOwner( owner );
  186. this->owner = owner;
  187. memset( &renderLight, 0, sizeof( renderLight ) );
  188. shaderName = spawnArgs.GetString( "mtr_light_shader" );
  189. if ( *(const char *)shaderName ) {
  190. renderLight.shader = declManager->FindMaterial( shaderName, false );
  191. renderLight.pointLight = true;
  192. renderLight.lightRadius[0] =
  193. renderLight.lightRadius[1] =
  194. renderLight.lightRadius[2] = spawnArgs.GetFloat( "light_radius" );
  195. #ifdef ID_PC
  196. renderLight.lightRadius *= 1.5f;
  197. renderLight.forceShadows = true;
  198. #endif
  199. spawnArgs.GetVector( "light_color", "1 1 1", light_color );
  200. renderLight.shaderParms[0] = light_color[0];
  201. renderLight.shaderParms[1] = light_color[1];
  202. renderLight.shaderParms[2] = light_color[2];
  203. renderLight.shaderParms[3] = 1.0f;
  204. }
  205. spawnArgs.GetVector( "light_offset", "0 0 0", lightOffset );
  206. lightStartTime = 0;
  207. lightEndTime = 0;
  208. smokeFlyTime = 0;
  209. damagePower = 1.0f;
  210. if(spawnArgs.GetBool("reset_time_offset", "0")) {
  211. renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time );
  212. }
  213. UpdateVisuals();
  214. state = CREATED;
  215. }
  216. /*
  217. =================
  218. idProjectile::~idProjectile
  219. =================
  220. */
  221. idProjectile::~idProjectile() {
  222. StopSound( SND_CHANNEL_ANY, false );
  223. FreeLightDef();
  224. }
  225. /*
  226. =================
  227. idProjectile::FreeLightDef
  228. =================
  229. */
  230. void idProjectile::FreeLightDef() {
  231. if ( lightDefHandle != -1 ) {
  232. gameRenderWorld->FreeLightDef( lightDefHandle );
  233. lightDefHandle = -1;
  234. }
  235. }
  236. /*
  237. =================
  238. idProjectile::Launch
  239. =================
  240. */
  241. void idProjectile::Launch( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity, const float timeSinceFire, const float launchPower, const float dmgPower ) {
  242. float fuse;
  243. float startthrust;
  244. float endthrust;
  245. idVec3 velocity;
  246. idAngles angular_velocity;
  247. float linear_friction;
  248. float angular_friction;
  249. float contact_friction;
  250. float bounce;
  251. float mass;
  252. float speed;
  253. float gravity;
  254. idVec3 gravVec;
  255. idVec3 tmp;
  256. idMat3 axis;
  257. int thrust_start;
  258. int contents;
  259. int clipMask;
  260. // allow characters to throw projectiles during cinematics, but not the player
  261. if ( owner.GetEntity() && !owner.GetEntity()->IsType( idPlayer::Type ) ) {
  262. cinematic = owner.GetEntity()->cinematic;
  263. } else {
  264. cinematic = false;
  265. }
  266. thrust = spawnArgs.GetFloat( "thrust" );
  267. startthrust = spawnArgs.GetFloat( "thrust_start" );
  268. endthrust = spawnArgs.GetFloat( "thrust_end" );
  269. spawnArgs.GetVector( "velocity", "0 0 0", velocity );
  270. speed = velocity.Length() * launchPower;
  271. damagePower = dmgPower;
  272. spawnArgs.GetAngles( "angular_velocity", "0 0 0", angular_velocity );
  273. linear_friction = spawnArgs.GetFloat( "linear_friction" );
  274. angular_friction = spawnArgs.GetFloat( "angular_friction" );
  275. contact_friction = spawnArgs.GetFloat( "contact_friction" );
  276. bounce = spawnArgs.GetFloat( "bounce" );
  277. mass = spawnArgs.GetFloat( "mass" );
  278. gravity = spawnArgs.GetFloat( "gravity" );
  279. fuse = spawnArgs.GetFloat( "fuse" );
  280. projectileFlags.detonate_on_world = spawnArgs.GetBool( "detonate_on_world" );
  281. projectileFlags.detonate_on_actor = spawnArgs.GetBool( "detonate_on_actor" );
  282. projectileFlags.randomShaderSpin = spawnArgs.GetBool( "random_shader_spin" );
  283. if ( mass <= 0 ) {
  284. gameLocal.Error( "Invalid mass on '%s'\n", GetEntityDefName() );
  285. }
  286. thrust *= mass;
  287. thrust_start = SEC2MS( startthrust ) + gameLocal.time;
  288. thrust_end = SEC2MS( endthrust ) + gameLocal.time;
  289. lightStartTime = 0;
  290. lightEndTime = 0;
  291. if ( health ) {
  292. fl.takedamage = true;
  293. }
  294. gravVec = gameLocal.GetGravity();
  295. gravVec.NormalizeFast();
  296. Unbind();
  297. // align z-axis of model with the direction
  298. axis = dir.ToMat3();
  299. tmp = axis[2];
  300. axis[2] = axis[0];
  301. axis[0] = -tmp;
  302. contents = 0;
  303. clipMask = MASK_SHOT_RENDERMODEL;
  304. if ( spawnArgs.GetBool( "detonate_on_trigger" ) ) {
  305. contents |= CONTENTS_TRIGGER;
  306. }
  307. if ( !spawnArgs.GetBool( "no_contents" ) ) {
  308. contents |= CONTENTS_PROJECTILE;
  309. clipMask |= CONTENTS_PROJECTILE;
  310. }
  311. if ( !idStr::Cmp( this->GetEntityDefName(), "projectile_helltime_killer" ) ) {
  312. contents = CONTENTS_MOVEABLECLIP;
  313. clipMask = CONTENTS_MOVEABLECLIP;
  314. fuse = 10.0f;
  315. }
  316. // don't do tracers on client, we don't know origin and direction
  317. if ( spawnArgs.GetBool( "tracers" ) && gameLocal.random.RandomFloat() > 0.5f ) {
  318. SetModel( spawnArgs.GetString( "model_tracer" ) );
  319. projectileFlags.isTracer = true;
  320. }
  321. physicsObj.SetMass( mass );
  322. physicsObj.SetFriction( linear_friction, angular_friction, contact_friction );
  323. if ( contact_friction == 0.0f ) {
  324. physicsObj.NoContact();
  325. }
  326. physicsObj.SetBouncyness( bounce );
  327. physicsObj.SetGravity( gravVec * gravity );
  328. physicsObj.SetContents( contents );
  329. physicsObj.SetClipMask( clipMask );
  330. physicsObj.SetLinearVelocity( axis[ 2 ] * speed + pushVelocity );
  331. physicsObj.SetAngularVelocity( angular_velocity.ToAngularVelocity() * axis );
  332. physicsObj.SetOrigin( start );
  333. physicsObj.SetAxis( axis );
  334. launchOrigin = start;
  335. launchAxis = axis;
  336. thruster.SetPosition( &physicsObj, 0, idVec3( GetPhysics()->GetBounds()[ 0 ].x, 0, 0 ) );
  337. if ( !common->IsClient() || fl.skipReplication ) {
  338. if ( fuse <= 0 ) {
  339. // run physics for 1 second
  340. RunPhysics();
  341. PostEventMS( &EV_Remove, spawnArgs.GetInt( "remove_time", "1500" ) );
  342. } else if ( spawnArgs.GetBool( "detonate_on_fuse" ) ) {
  343. fuse -= timeSinceFire;
  344. if ( fuse < 0.0f ) {
  345. fuse = 0.0f;
  346. }
  347. PostEventSec( &EV_Explode, fuse );
  348. } else {
  349. fuse -= timeSinceFire;
  350. if ( fuse < 0.0f ) {
  351. fuse = 0.0f;
  352. }
  353. PostEventSec( &EV_Fizzle, fuse );
  354. }
  355. }
  356. if ( projectileFlags.isTracer ) {
  357. StartSound( "snd_tracer", SND_CHANNEL_BODY, 0, false, NULL );
  358. } else {
  359. StartSound( "snd_fly", SND_CHANNEL_BODY, 0, false, NULL );
  360. }
  361. smokeFlyTime = 0;
  362. const char *smokeName = spawnArgs.GetString( "smoke_fly" );
  363. if ( *smokeName != '\0' ) {
  364. smokeFly = static_cast<const idDeclParticle *>( declManager->FindType( DECL_PARTICLE, smokeName ) );
  365. smokeFlyTime = gameLocal.time;
  366. }
  367. originalTimeGroup = timeGroup;
  368. // used for the plasma bolts but may have other uses as well
  369. if ( projectileFlags.randomShaderSpin ) {
  370. float f = gameLocal.random.RandomFloat();
  371. f *= 0.5f;
  372. renderEntity.shaderParms[SHADERPARM_DIVERSITY] = f;
  373. }
  374. UpdateVisuals();
  375. state = LAUNCHED;
  376. }
  377. /*
  378. ================
  379. idProjectile::Think
  380. ================
  381. */
  382. void idProjectile::Think() {
  383. if ( thinkFlags & TH_THINK ) {
  384. if ( thrust && ( gameLocal.time < thrust_end ) ) {
  385. // evaluate force
  386. thruster.SetForce( GetPhysics()->GetAxis()[ 0 ] * thrust );
  387. thruster.Evaluate( gameLocal.time );
  388. }
  389. }
  390. if ( mTouchTriggers ) {
  391. TouchTriggers();
  392. }
  393. // if the projectile owner is a player
  394. if ( owner.GetEntity() && owner.GetEntity()->IsType( idPlayer::Type ) ) {
  395. idPlayer *player = static_cast<idPlayer *>( owner.GetEntity() );
  396. // Remove any projectiles spectators threw.
  397. if( player != NULL && player->spectating ) {
  398. PostEventMS( &EV_Remove, 0 );
  399. }
  400. }
  401. // run physics
  402. RunPhysics();
  403. DecayOriginAndAxisDelta();
  404. Present();
  405. AddParticlesAndLight();
  406. }
  407. /*
  408. =================
  409. idProjectile::AddParticlesAndLight
  410. =================
  411. */
  412. void idProjectile::AddParticlesAndLight() {
  413. // add the particles
  414. if ( smokeFly != NULL && smokeFlyTime && !IsHidden() ) {
  415. idVec3 dir = -GetPhysics()->GetLinearVelocity();
  416. dir.Normalize();
  417. SetTimeState ts(originalTimeGroup);
  418. if ( !gameLocal.smokeParticles->EmitSmoke( smokeFly, smokeFlyTime, gameLocal.random.RandomFloat(), GetPhysics()->GetOrigin(), dir.ToMat3(), timeGroup /*_D3XP*/ ) ) {
  419. smokeFlyTime = gameLocal.time;
  420. }
  421. }
  422. // add the light
  423. if ( renderLight.lightRadius.x > 0.0f && g_projectileLights.GetBool() ) {
  424. renderLight.origin = GetPhysics()->GetOrigin() + GetPhysics()->GetAxis() * lightOffset;
  425. renderLight.axis = GetPhysics()->GetAxis();
  426. if ( ( lightDefHandle != -1 ) ) {
  427. if ( lightEndTime > 0 && gameLocal.time <= lightEndTime ) {
  428. idVec3 color( 0, 0, 0 );
  429. if ( gameLocal.time < lightEndTime ) {
  430. float frac = ( float )( gameLocal.time - lightStartTime ) / ( float )( lightEndTime - lightStartTime );
  431. color.Lerp( lightColor, color, frac );
  432. }
  433. renderLight.shaderParms[SHADERPARM_RED] = color.x;
  434. renderLight.shaderParms[SHADERPARM_GREEN] = color.y;
  435. renderLight.shaderParms[SHADERPARM_BLUE] = color.z;
  436. }
  437. gameRenderWorld->UpdateLightDef( lightDefHandle, &renderLight );
  438. } else {
  439. lightDefHandle = gameRenderWorld->AddLightDef( &renderLight );
  440. }
  441. }
  442. }
  443. /*
  444. =================
  445. idProjectile::Collide
  446. =================
  447. */
  448. bool idProjectile::Collide( const trace_t &collision, const idVec3 &velocity ) {
  449. idEntity *ent;
  450. idEntity *ignore;
  451. const char *damageDefName;
  452. idVec3 dir;
  453. float push;
  454. float damageScale;
  455. if ( state == EXPLODED || state == FIZZLED ) {
  456. return true;
  457. }
  458. const bool isHitscan = spawnArgs.GetBool( "net_instanthit" );
  459. // hanlde slow projectiles here.
  460. if ( common->IsClient() && !isHitscan ) {
  461. // This is a replicated slow projectile, predict the explosion.
  462. if ( ClientPredictionCollide( this, spawnArgs, collision, velocity, !isHitscan ) ) {
  463. Explode( collision, NULL );
  464. return true;
  465. }
  466. }
  467. // remove projectile when a 'noimpact' surface is hit
  468. if ( ( collision.c.material != NULL ) && ( collision.c.material->GetSurfaceFlags() & SURF_NOIMPACT ) ) {
  469. PostEventMS( &EV_Remove, 0 );
  470. common->DPrintf( "Projectile collision no impact\n" );
  471. return true;
  472. }
  473. // get the entity the projectile collided with
  474. ent = gameLocal.entities[ collision.c.entityNum ];
  475. if ( ent == owner.GetEntity() ) {
  476. assert( 0 );
  477. return true;
  478. }
  479. // just get rid of the projectile when it hits a player in noclip
  480. if ( ent->IsType( idPlayer::Type ) && static_cast<idPlayer *>( ent )->noclip ) {
  481. PostEventMS( &EV_Remove, 0 );
  482. return true;
  483. }
  484. // direction of projectile
  485. dir = velocity;
  486. dir.Normalize();
  487. // projectiles can apply an additional impulse next to the rigid body physics impulse
  488. if ( spawnArgs.GetFloat( "push", "0", push ) && push > 0.0f ) {
  489. if ( !common->IsClient() ) {
  490. ent->ApplyImpulse( this, collision.c.id, collision.c.point, push * dir );
  491. }
  492. }
  493. // MP: projectiles open doors
  494. if ( common->IsMultiplayer() && ent->IsType( idDoor::Type ) && !static_cast< idDoor * >(ent)->IsOpen() && !ent->spawnArgs.GetBool( "no_touch" ) ) {
  495. if ( !common->IsClient() ) {
  496. ent->ProcessEvent( &EV_Activate , this );
  497. }
  498. }
  499. if ( ent->IsType( idActor::Type ) || ( ent->IsType( idAFAttachment::Type ) && static_cast<const idAFAttachment*>(ent)->GetBody()->IsType( idActor::Type ) ) ) {
  500. if ( !projectileFlags.detonate_on_actor ) {
  501. return false;
  502. }
  503. } else {
  504. if ( !projectileFlags.detonate_on_world ) {
  505. if ( !StartSound( "snd_ricochet", SND_CHANNEL_ITEM, 0, true, NULL ) ) {
  506. float len = velocity.Length();
  507. if ( len > BOUNCE_SOUND_MIN_VELOCITY ) {
  508. 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 ) ) );
  509. StartSound( "snd_bounce", SND_CHANNEL_ANY, 0, true, NULL );
  510. }
  511. }
  512. return false;
  513. }
  514. }
  515. SetOrigin( collision.endpos );
  516. SetAxis( collision.endAxis );
  517. // To see the explosion on the collision surface instead of
  518. // at the muzzle, clear the deltas.
  519. CreateDeltasFromOldOriginAndAxis( GetPhysics()->GetOrigin(), GetPhysics()->GetAxis() );
  520. // unlink the clip model because we no longer need it
  521. GetPhysics()->UnlinkClip();
  522. damageDefName = spawnArgs.GetString( "def_damage" );
  523. ignore = NULL;
  524. // if the projectile causes a damage effect
  525. if ( spawnArgs.GetBool( "impact_damage_effect" ) ) {
  526. // if the hit entity has a special damage effect
  527. if ( ent->spawnArgs.GetBool( "bleed" ) ) {
  528. ent->AddDamageEffect( collision, velocity, damageDefName );
  529. } else {
  530. AddDefaultDamageEffect( collision, velocity );
  531. }
  532. }
  533. // if the hit entity takes damage
  534. if ( ent->fl.takedamage ) {
  535. if ( damagePower ) {
  536. damageScale = damagePower;
  537. } else {
  538. damageScale = 1.0f;
  539. }
  540. // if the projectile owner is a player
  541. if ( owner.GetEntity() && owner.GetEntity()->IsType( idPlayer::Type ) ) {
  542. // if the projectile hit an actor
  543. if ( ent->IsType( idActor::Type ) ) {
  544. idPlayer *player = static_cast<idPlayer *>( owner.GetEntity() );
  545. player->AddProjectileHits( 1 );
  546. damageScale *= player->PowerUpModifier( PROJECTILE_DAMAGE );
  547. }
  548. }
  549. if ( damageDefName[0] != '\0' ) {
  550. bool killedByImpact = true;
  551. if( ent->health <= 0 ) {
  552. killedByImpact = false;
  553. }
  554. // Only handle the server's own attacks here. Attacks by other players on the server occur through
  555. // reliable messages.
  556. if ( !common->IsMultiplayer() || common->IsClient() || ( common->IsServer() && owner.GetEntityNum() == gameLocal.GetLocalClientNum() ) || ( common->IsServer() && !isHitscan ) ) {
  557. ent->Damage( this, owner.GetEntity(), dir, damageDefName, damageScale, CLIPMODEL_ID_TO_JOINT_HANDLE( collision.c.id ) );
  558. } else {
  559. if( ent->IsType( idPlayer::Type ) == false && common->IsServer() ) {
  560. ent->Damage( this, owner.GetEntity(), dir, damageDefName, damageScale, CLIPMODEL_ID_TO_JOINT_HANDLE( collision.c.id ) );
  561. }
  562. }
  563. // Check if we are hitting an actor. and see if we killed him.
  564. if( !common->IsClient() && ent->health <= 0 && killedByImpact ) {
  565. if ( owner.GetEntity() && owner.GetEntity()->IsType( idPlayer::Type ) ) {
  566. if ( ent->IsType( idActor::Type ) && ent != owner.GetEntity() ) {
  567. idPlayer *player = static_cast<idPlayer *>( owner.GetEntity() );
  568. player->AddProjectileKills();
  569. }
  570. }
  571. }
  572. ignore = ent;
  573. }
  574. }
  575. Explode( collision, ignore );
  576. if ( !common->IsClient() && owner.GetEntity() != NULL && owner.GetEntity()->IsType( idPlayer::Type ) ) {
  577. idPlayer *player = static_cast<idPlayer *>( owner.GetEntity() );
  578. int kills = player->GetProjectileKills();
  579. if( kills >= 2 && common->IsMultiplayer() && strstr( GetName(), "projectile_rocket" ) != 0 ) {
  580. player->GetAchievementManager().EventCompletesAchievement( ACHIEVEMENT_MP_KILL_2_GUYS_IN_ROOM_WITH_BFG );
  581. }
  582. // projectile is done dealing damage.
  583. player->ResetProjectileKills();
  584. }
  585. return true;
  586. }
  587. /*
  588. =================
  589. idProjectile::DefaultDamageEffect
  590. =================
  591. */
  592. void idProjectile::DefaultDamageEffect( idEntity *soundEnt, const idDict &projectileDef, const trace_t &collision, const idVec3 &velocity ) {
  593. const char *decal, *sound, *typeName;
  594. surfTypes_t materialType;
  595. if ( collision.c.material != NULL ) {
  596. materialType = collision.c.material->GetSurfaceType();
  597. } else {
  598. materialType = SURFTYPE_METAL;
  599. }
  600. // get material type name
  601. typeName = gameLocal.sufaceTypeNames[ materialType ];
  602. // play impact sound
  603. sound = projectileDef.GetString( va( "snd_%s", typeName ) );
  604. if ( *sound == '\0' ) {
  605. sound = projectileDef.GetString( "snd_metal" );
  606. }
  607. if ( *sound == '\0' ) {
  608. sound = projectileDef.GetString( "snd_impact" );
  609. }
  610. if ( *sound != '\0' ) {
  611. soundEnt->StartSoundShader( declManager->FindSound( sound ), SND_CHANNEL_BODY, 0, false, NULL );
  612. }
  613. // project decal
  614. decal = projectileDef.GetString( va( "mtr_detonate_%s", typeName ) );
  615. if ( *decal == '\0' ) {
  616. decal = projectileDef.GetString( "mtr_detonate" );
  617. }
  618. if ( *decal != '\0' ) {
  619. gameLocal.ProjectDecal( collision.c.point, -collision.c.normal, 8.0f, true, projectileDef.GetFloat( "decal_size", "6.0" ), decal );
  620. }
  621. }
  622. /*
  623. =================
  624. idProjectile::AddDefaultDamageEffect
  625. =================
  626. */
  627. void idProjectile::AddDefaultDamageEffect( const trace_t &collision, const idVec3 &velocity ) {
  628. DefaultDamageEffect( this, spawnArgs, collision, velocity );
  629. if ( common->IsServer() && fl.networkSync ) {
  630. idBitMsg msg;
  631. byte msgBuf[MAX_EVENT_PARAM_SIZE];
  632. lobbyUserID_t excluding;
  633. if ( spawnArgs.GetBool( "net_instanthit" ) && owner.GetEntityNum() < MAX_PLAYERS ) {
  634. excluding = gameLocal.lobbyUserIDs[owner.GetEntityNum()];
  635. }
  636. msg.InitWrite( msgBuf, sizeof( msgBuf ) );
  637. msg.BeginWriting();
  638. msg.WriteFloat( collision.c.point[0] );
  639. msg.WriteFloat( collision.c.point[1] );
  640. msg.WriteFloat( collision.c.point[2] );
  641. msg.WriteDir( collision.c.normal, 24 );
  642. msg.WriteLong( ( collision.c.material != NULL ) ? gameLocal.ServerRemapDecl( -1, DECL_MATERIAL, collision.c.material->Index() ) : -1 );
  643. msg.WriteFloat( velocity[0], 5, 10 );
  644. msg.WriteFloat( velocity[1], 5, 10 );
  645. msg.WriteFloat( velocity[2], 5, 10 );
  646. ServerSendEvent( EVENT_DAMAGE_EFFECT, &msg, false, excluding );
  647. }
  648. }
  649. /*
  650. ================
  651. idProjectile::Killed
  652. ================
  653. */
  654. void idProjectile::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) {
  655. if ( spawnArgs.GetBool( "detonate_on_death" ) ) {
  656. trace_t collision;
  657. memset( &collision, 0, sizeof( collision ) );
  658. collision.endAxis = GetPhysics()->GetAxis();
  659. collision.endpos = GetPhysics()->GetOrigin();
  660. collision.c.point = GetPhysics()->GetOrigin();
  661. collision.c.normal.Set( 0, 0, 1 );
  662. Explode( collision, NULL );
  663. physicsObj.ClearContacts();
  664. physicsObj.PutToRest();
  665. } else {
  666. Fizzle();
  667. }
  668. }
  669. /*
  670. ================
  671. idProjectile::Fizzle
  672. ================
  673. */
  674. void idProjectile::Fizzle() {
  675. if ( state == EXPLODED || state == FIZZLED ) {
  676. return;
  677. }
  678. StopSound( SND_CHANNEL_BODY, false );
  679. StartSound( "snd_fizzle", SND_CHANNEL_BODY, 0, false, NULL );
  680. // fizzle FX
  681. const char *psystem = spawnArgs.GetString( "smoke_fuse" );
  682. if ( psystem != NULL && *psystem != NULL ) {
  683. //FIXME:SMOKE gameLocal.particles->SpawnParticles( GetPhysics()->GetOrigin(), vec3_origin, psystem );
  684. }
  685. // we need to work out how long the effects last and then remove them at that time
  686. // for example, bullets have no real effects
  687. if ( smokeFly && smokeFlyTime ) {
  688. smokeFlyTime = 0;
  689. }
  690. fl.takedamage = false;
  691. physicsObj.SetContents( 0 );
  692. physicsObj.GetClipModel()->Unlink();
  693. physicsObj.PutToRest();
  694. Hide();
  695. FreeLightDef();
  696. state = FIZZLED;
  697. if ( common->IsClient() && !fl.skipReplication ) {
  698. return;
  699. }
  700. CancelEvents( &EV_Fizzle );
  701. PostEventMS( &EV_Remove, spawnArgs.GetInt( "remove_time", "1500" ) );
  702. }
  703. /*
  704. ================
  705. idProjectile::Event_RadiusDamage
  706. ================
  707. */
  708. void idProjectile::Event_RadiusDamage( idEntity *ignore ) {
  709. const char *splash_damage = spawnArgs.GetString( "def_splash_damage" );
  710. if ( splash_damage[0] != '\0' ) {
  711. gameLocal.RadiusDamage( physicsObj.GetOrigin(), this, owner.GetEntity(), ignore, this, splash_damage, damagePower );
  712. }
  713. }
  714. /*
  715. ================
  716. idProjectile::Event_RadiusDamage
  717. ================
  718. */
  719. void idProjectile::Event_GetProjectileState() {
  720. idThread::ReturnInt( state );
  721. }
  722. /*
  723. ================
  724. idProjectile::Explode
  725. ================
  726. */
  727. void idProjectile::Explode( const trace_t &collision, idEntity *ignore ) {
  728. const char *fxname, *light_shader, *sndExplode;
  729. float light_fadetime;
  730. idVec3 normal;
  731. int removeTime;
  732. if ( mNoExplodeDisappear ) {
  733. PostEventMS( &EV_Remove, 0 );
  734. return;
  735. }
  736. if ( state == EXPLODED || state == FIZZLED ) {
  737. return;
  738. }
  739. // activate rumble for player
  740. idPlayer *player = gameLocal.GetLocalPlayer();
  741. const bool isHitscan = spawnArgs.GetBool( "net_instanthit" );
  742. if ( player != NULL && isHitscan == false ) {
  743. // damage
  744. const char *damageDefName = spawnArgs.GetString( "def_damage" );
  745. const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName );
  746. int damage;
  747. if ( damageDef != NULL ) {
  748. damage = damageDef->GetInt( "damage" );
  749. } else {
  750. damage = 200;
  751. }
  752. float damageScale = idMath::ClampFloat( 0.25f, 1.0f, (float)damage * ( 1.0f / 200.0f ) ); // 50...200 -> min...max rumble
  753. // distance
  754. float dist = ( GetPhysics()->GetOrigin() - player->GetPhysics()->GetOrigin() ).LengthFast();
  755. float distScale = 1.0f - idMath::ClampFloat( 0.0f, 1.0f, ( dist * ( 1.0f / 4000.0f ) ) + 0.25f ); // 0...4000 -> max...min rumble
  756. distScale *= damageScale; // apply damage scale here, weaker damage produces less rumble
  757. // determine rumble
  758. float highMag = distScale;
  759. int highDuration = idMath::Ftoi( 300.0f * distScale );
  760. float lowMag = distScale * 0.75f;
  761. int lowDuration = idMath::Ftoi( 500.0f * distScale );
  762. player->SetControllerShake( highMag, highDuration, lowMag, lowDuration );
  763. }
  764. // stop sound
  765. StopSound( SND_CHANNEL_BODY2, false );
  766. // play explode sound
  767. switch ( ( int ) damagePower ) {
  768. case 2: sndExplode = "snd_explode2"; break;
  769. case 3: sndExplode = "snd_explode3"; break;
  770. case 4: sndExplode = "snd_explode4"; break;
  771. default: sndExplode = "snd_explode"; break;
  772. }
  773. StartSound( sndExplode, SND_CHANNEL_BODY, 0, true, NULL );
  774. // we need to work out how long the effects last and then remove them at that time
  775. // for example, bullets have no real effects
  776. if ( smokeFly && smokeFlyTime ) {
  777. smokeFlyTime = 0;
  778. }
  779. Hide();
  780. FreeLightDef();
  781. if ( spawnArgs.GetVector( "detonation_axis", "", normal ) ) {
  782. GetPhysics()->SetAxis( normal.ToMat3() );
  783. }
  784. GetPhysics()->SetOrigin( collision.endpos + 2.0f * collision.c.normal );
  785. // default remove time
  786. if( fl.skipReplication && !spawnArgs.GetBool( "net_instanthit" ) ) {
  787. removeTime = spawnArgs.GetInt( "remove_time", "6000" );
  788. } else {
  789. removeTime = spawnArgs.GetInt( "remove_time", "1500" );
  790. }
  791. // change the model, usually to a PRT
  792. fxname = NULL;
  793. if ( g_testParticle.GetInteger() == TEST_PARTICLE_IMPACT ) {
  794. fxname = g_testParticleName.GetString();
  795. } else {
  796. fxname = spawnArgs.GetString( "model_detonate" );
  797. }
  798. int surfaceType = collision.c.material != NULL ? collision.c.material->GetSurfaceType() : SURFTYPE_METAL;
  799. if ( !( fxname != NULL && *fxname != NULL ) ) {
  800. if ( ( surfaceType == SURFTYPE_NONE ) || ( surfaceType == SURFTYPE_METAL ) || ( surfaceType == SURFTYPE_STONE ) ) {
  801. fxname = spawnArgs.GetString( "model_smokespark" );
  802. } else if ( surfaceType == SURFTYPE_RICOCHET ) {
  803. fxname = spawnArgs.GetString( "model_ricochet" );
  804. } else {
  805. fxname = spawnArgs.GetString( "model_smoke" );
  806. }
  807. }
  808. // If the explosion is in liquid, spawn a particle splash
  809. idVec3 testOrg = GetPhysics()->GetOrigin();
  810. int testC = gameLocal.clip.Contents( testOrg, NULL, mat3_identity, CONTENTS_WATER, this );
  811. if ( testC & CONTENTS_WATER ) {
  812. idFuncEmitter *splashEnt;
  813. idDict splashArgs;
  814. splashArgs.Set( "model", "sludgebulletimpact.prt" );
  815. splashArgs.Set( "start_off", "1" );
  816. splashEnt = static_cast<idFuncEmitter *>( gameLocal.SpawnEntityType( idFuncEmitter::Type, &splashArgs ) );
  817. splashEnt->GetPhysics()->SetOrigin( testOrg );
  818. splashEnt->PostEventMS( &EV_Activate, 0, this );
  819. splashEnt->PostEventMS( &EV_Remove, 1500 );
  820. // HACK - if this is a chaingun bullet, don't do the normal effect
  821. if ( !idStr::Cmp( spawnArgs.GetString( "def_damage" ), "damage_bullet_chaingun" ) ) {
  822. fxname = NULL;
  823. }
  824. }
  825. if ( fxname && *fxname ) {
  826. SetModel( fxname );
  827. renderEntity.shaderParms[SHADERPARM_RED] =
  828. renderEntity.shaderParms[SHADERPARM_GREEN] =
  829. renderEntity.shaderParms[SHADERPARM_BLUE] =
  830. renderEntity.shaderParms[SHADERPARM_ALPHA] = 1.0f;
  831. renderEntity.shaderParms[SHADERPARM_TIMEOFFSET] = -MS2SEC( gameLocal.time );
  832. renderEntity.shaderParms[SHADERPARM_DIVERSITY] = gameLocal.random.CRandomFloat();
  833. Show();
  834. removeTime = ( removeTime > 3000 ) ? removeTime : 3000;
  835. }
  836. // explosion light
  837. light_shader = spawnArgs.GetString( "mtr_explode_light_shader" );
  838. if ( gameLocal.mpGame.IsGametypeFlagBased() && gameLocal.serverInfo.GetBool("si_midnight") )
  839. {
  840. light_shader = "lights/midnight_grenade";
  841. }
  842. if ( *light_shader ) {
  843. renderLight.shader = declManager->FindMaterial( light_shader, false );
  844. renderLight.pointLight = true;
  845. renderLight.lightRadius[0] =
  846. renderLight.lightRadius[1] =
  847. renderLight.lightRadius[2] = spawnArgs.GetFloat( "explode_light_radius" );
  848. #ifdef ID_PC
  849. renderLight.lightRadius *= 2.0f;
  850. renderLight.forceShadows = true;
  851. #endif
  852. // Midnight ctf
  853. if ( gameLocal.mpGame.IsGametypeFlagBased() && gameLocal.serverInfo.GetBool("si_midnight") ) {
  854. renderLight.lightRadius[0] =
  855. renderLight.lightRadius[1] =
  856. renderLight.lightRadius[2] = spawnArgs.GetFloat( "explode_light_radius" ) * 2;
  857. }
  858. spawnArgs.GetVector( "explode_light_color", "1 1 1", lightColor );
  859. renderLight.shaderParms[SHADERPARM_RED] = lightColor.x;
  860. renderLight.shaderParms[SHADERPARM_GREEN] = lightColor.y;
  861. renderLight.shaderParms[SHADERPARM_BLUE] = lightColor.z;
  862. renderLight.shaderParms[SHADERPARM_ALPHA] = 1.0f;
  863. renderLight.shaderParms[SHADERPARM_TIMEOFFSET] = -MS2SEC( gameLocal.time );
  864. // Midnight ctf
  865. if ( gameLocal.mpGame.IsGametypeFlagBased() && gameLocal.serverInfo.GetBool("si_midnight") ) {
  866. light_fadetime = 3.0f;
  867. } else {
  868. light_fadetime = spawnArgs.GetFloat( "explode_light_fadetime", "0.5" );
  869. }
  870. lightStartTime = gameLocal.time;
  871. lightEndTime = MSEC_ALIGN_TO_FRAME( gameLocal.time + SEC2MS( light_fadetime ) );
  872. BecomeActive( TH_THINK );
  873. }
  874. fl.takedamage = false;
  875. physicsObj.SetContents( 0 );
  876. physicsObj.PutToRest();
  877. state = EXPLODED;
  878. if ( common->IsClient() && !fl.skipReplication ) {
  879. return;
  880. }
  881. // alert the ai
  882. gameLocal.AlertAI( owner.GetEntity() );
  883. // bind the projectile to the impact entity if necesary
  884. if ( gameLocal.entities[collision.c.entityNum] && spawnArgs.GetBool( "bindOnImpact" ) ) {
  885. Bind( gameLocal.entities[collision.c.entityNum], true );
  886. }
  887. // splash damage
  888. if ( !projectileFlags.noSplashDamage ) {
  889. float delay = spawnArgs.GetFloat( "delay_splash" );
  890. if ( delay ) {
  891. if ( removeTime < delay * 1000 ) {
  892. removeTime = ( delay + 0.10 ) * 1000;
  893. }
  894. PostEventSec( &EV_RadiusDamage, delay, ignore );
  895. } else {
  896. Event_RadiusDamage( ignore );
  897. }
  898. }
  899. // spawn debris entities
  900. int fxdebris = spawnArgs.GetInt( "debris_count" );
  901. if ( fxdebris ) {
  902. const idDict *debris = gameLocal.FindEntityDefDict( "projectile_debris", false );
  903. if ( debris ) {
  904. int amount = gameLocal.random.RandomInt( fxdebris );
  905. for ( int i = 0; i < amount; i++ ) {
  906. idEntity *ent;
  907. idVec3 dir;
  908. dir.x = gameLocal.random.CRandomFloat() * 4.0f;
  909. dir.y = gameLocal.random.CRandomFloat() * 4.0f;
  910. dir.z = gameLocal.random.RandomFloat() * 8.0f;
  911. dir.Normalize();
  912. gameLocal.SpawnEntityDef( *debris, &ent, false );
  913. if ( ent == NULL || !ent->IsType( idDebris::Type ) ) {
  914. gameLocal.Error( "'projectile_debris' is not an idDebris" );
  915. return;
  916. }
  917. idDebris *debris = static_cast<idDebris *>(ent);
  918. debris->Create( owner.GetEntity(), physicsObj.GetOrigin(), dir.ToMat3() );
  919. debris->Launch();
  920. }
  921. }
  922. debris = gameLocal.FindEntityDefDict( "projectile_shrapnel", false );
  923. if ( debris ) {
  924. int amount = gameLocal.random.RandomInt( fxdebris );
  925. for ( int i = 0; i < amount; i++ ) {
  926. idEntity *ent;
  927. idVec3 dir;
  928. dir.x = gameLocal.random.CRandomFloat() * 8.0f;
  929. dir.y = gameLocal.random.CRandomFloat() * 8.0f;
  930. dir.z = gameLocal.random.RandomFloat() * 8.0f + 8.0f;
  931. dir.Normalize();
  932. gameLocal.SpawnEntityDef( *debris, &ent, false );
  933. if ( ent == NULL || !ent->IsType( idDebris::Type ) ) {
  934. gameLocal.Error( "'projectile_shrapnel' is not an idDebris" );
  935. break;
  936. }
  937. idDebris *debris = static_cast<idDebris *>(ent);
  938. debris->Create( owner.GetEntity(), physicsObj.GetOrigin(), dir.ToMat3() );
  939. debris->Launch();
  940. }
  941. }
  942. }
  943. CancelEvents( &EV_Explode );
  944. PostEventMS( &EV_Remove, removeTime );
  945. }
  946. /*
  947. ================
  948. idProjectile::GetVelocity
  949. ================
  950. */
  951. idVec3 idProjectile::GetVelocity( const idDict *projectile ) {
  952. idVec3 velocity;
  953. projectile->GetVector( "velocity", "0 0 0", velocity );
  954. return velocity;
  955. }
  956. /*
  957. ================
  958. idProjectile::GetGravity
  959. ================
  960. */
  961. idVec3 idProjectile::GetGravity( const idDict *projectile ) {
  962. float gravity;
  963. gravity = projectile->GetFloat( "gravity" );
  964. return idVec3( 0, 0, -gravity );
  965. }
  966. /*
  967. ================
  968. idProjectile::Event_Explode
  969. ================
  970. */
  971. void idProjectile::Event_Explode() {
  972. trace_t collision;
  973. memset( &collision, 0, sizeof( collision ) );
  974. collision.endAxis = GetPhysics()->GetAxis();
  975. collision.endpos = GetPhysics()->GetOrigin();
  976. collision.c.point = GetPhysics()->GetOrigin();
  977. collision.c.normal.Set( 0, 0, 1 );
  978. AddDefaultDamageEffect( collision, collision.c.normal );
  979. Explode( collision, NULL );
  980. }
  981. /*
  982. ================
  983. idProjectile::Event_Fizzle
  984. ================
  985. */
  986. void idProjectile::Event_Fizzle() {
  987. Fizzle();
  988. }
  989. /*
  990. ================
  991. idProjectile::Event_Touch
  992. ================
  993. */
  994. void idProjectile::Event_Touch( idEntity *other, trace_t *trace ) {
  995. if ( common->IsClient() ) {
  996. return;
  997. }
  998. if ( IsHidden() ) {
  999. return;
  1000. }
  1001. // Projectiles do not collide with flags
  1002. if ( other->IsType( idItemTeam::Type ) ) {
  1003. return;
  1004. }
  1005. if ( other != owner.GetEntity() ) {
  1006. trace_t collision;
  1007. memset( &collision, 0, sizeof( collision ) );
  1008. collision.endAxis = GetPhysics()->GetAxis();
  1009. collision.endpos = GetPhysics()->GetOrigin();
  1010. collision.c.point = GetPhysics()->GetOrigin();
  1011. collision.c.normal.Set( 0, 0, 1 );
  1012. AddDefaultDamageEffect( collision, collision.c.normal );
  1013. Explode( collision, NULL );
  1014. }
  1015. }
  1016. /*
  1017. ================
  1018. idProjectile::CatchProjectile
  1019. ================
  1020. */
  1021. void idProjectile::CatchProjectile( idEntity* o, const char* reflectName ) {
  1022. idEntity *prevowner = owner.GetEntity();
  1023. owner = o;
  1024. physicsObj.GetClipModel()->SetOwner( o );
  1025. if ( this->IsType( idGuidedProjectile::Type ) ) {
  1026. idGuidedProjectile *proj = static_cast<idGuidedProjectile*>(this);
  1027. proj->SetEnemy( prevowner );
  1028. }
  1029. idStr s = spawnArgs.GetString( "def_damage" );
  1030. s += reflectName;
  1031. const idDict *damageDef = gameLocal.FindEntityDefDict( s, false );
  1032. if ( damageDef ) {
  1033. spawnArgs.Set( "def_damage", s );
  1034. }
  1035. }
  1036. /*
  1037. ================
  1038. idProjectile::GetProjectileState
  1039. ================
  1040. */
  1041. int idProjectile::GetProjectileState() {
  1042. return (int)state;
  1043. }
  1044. /*
  1045. ================
  1046. idProjectile::Event_CreateProjectile
  1047. ================
  1048. */
  1049. void idProjectile::Event_CreateProjectile( idEntity *owner, const idVec3 &start, const idVec3 &dir ) {
  1050. Create(owner, start, dir);
  1051. }
  1052. /*
  1053. ================
  1054. idProjectile::Event_LaunchProjectile
  1055. ================
  1056. */
  1057. void idProjectile::Event_LaunchProjectile( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity ) {
  1058. Launch(start, dir, pushVelocity);
  1059. }
  1060. /*
  1061. ================
  1062. idProjectile::Event_SetGravity
  1063. ================
  1064. */
  1065. void idProjectile::Event_SetGravity( float gravity ) {
  1066. idVec3 gravVec;
  1067. gravVec = gameLocal.GetGravity();
  1068. gravVec.NormalizeFast();
  1069. physicsObj.SetGravity(gravVec * gravity);
  1070. }
  1071. /*
  1072. =================
  1073. idProjectile::ClientPredictionCollide
  1074. =================
  1075. */
  1076. bool idProjectile::ClientPredictionCollide( idEntity *soundEnt, const idDict &projectileDef, const trace_t &collision, const idVec3 &velocity, bool addDamageEffect ) {
  1077. idEntity *ent;
  1078. // remove projectile when a 'noimpact' surface is hit
  1079. if ( collision.c.material && ( collision.c.material->GetSurfaceFlags() & SURF_NOIMPACT ) ) {
  1080. return false;
  1081. }
  1082. // get the entity the projectile collided with
  1083. ent = gameLocal.entities[ collision.c.entityNum ];
  1084. if ( ent == NULL ) {
  1085. return false;
  1086. }
  1087. // don't do anything if hitting a noclip player
  1088. if ( ent->IsType( idPlayer::Type ) && static_cast<idPlayer *>( ent )->noclip ) {
  1089. return false;
  1090. }
  1091. if ( ent->IsType( idActor::Type ) || ( ent->IsType( idAFAttachment::Type ) && static_cast<const idAFAttachment*>(ent)->GetBody()->IsType( idActor::Type ) ) ) {
  1092. if ( !projectileDef.GetBool( "detonate_on_actor" ) ) {
  1093. return false;
  1094. }
  1095. } else {
  1096. if ( !projectileDef.GetBool( "detonate_on_world" ) ) {
  1097. return false;
  1098. }
  1099. }
  1100. // if the projectile causes a damage effect
  1101. if ( addDamageEffect && projectileDef.GetBool( "impact_damage_effect" ) ) {
  1102. // if the hit entity does not have a special damage effect
  1103. if ( !ent->spawnArgs.GetBool( "bleed" ) ) {
  1104. // predict damage effect
  1105. DefaultDamageEffect( soundEnt, projectileDef, collision, velocity );
  1106. }
  1107. }
  1108. return true;
  1109. }
  1110. /*
  1111. ================
  1112. idProjectile::ClientThink
  1113. ================
  1114. */
  1115. void idProjectile::ClientThink( const int curTime, const float fraction, const bool predict ) {
  1116. if ( fl.skipReplication ) {
  1117. Think();
  1118. } else {
  1119. if ( !renderEntity.hModel ) {
  1120. return;
  1121. }
  1122. InterpolatePhysicsOnly( fraction );
  1123. Present();
  1124. AddParticlesAndLight();
  1125. }
  1126. }
  1127. /*
  1128. ================
  1129. idProjectile::ClientPredictionThink
  1130. ================
  1131. */
  1132. void idProjectile::ClientPredictionThink() {
  1133. if ( !renderEntity.hModel ) {
  1134. return;
  1135. }
  1136. Think();
  1137. }
  1138. /*
  1139. ================
  1140. idProjectile::WriteToSnapshot
  1141. ================
  1142. */
  1143. void idProjectile::WriteToSnapshot( idBitMsg &msg ) const {
  1144. msg.WriteBits( owner.GetSpawnId(), 32 );
  1145. msg.WriteBits( state, 3 );
  1146. msg.WriteBits( fl.hidden, 1 );
  1147. physicsObj.WriteToSnapshot( msg );
  1148. }
  1149. /*
  1150. ================
  1151. idProjectile::ReadFromSnapshot
  1152. ================
  1153. */
  1154. void idProjectile::ReadFromSnapshot( const idBitMsg &msg ) {
  1155. projectileState_t newState;
  1156. owner.SetSpawnId( msg.ReadBits( 32 ) );
  1157. newState = (projectileState_t) msg.ReadBits( 3 );
  1158. if ( msg.ReadBits( 1 ) ) {
  1159. Hide();
  1160. } else {
  1161. Show();
  1162. }
  1163. while( state != newState ) {
  1164. switch( state ) {
  1165. case SPAWNED: {
  1166. Create( owner.GetEntity(), vec3_origin, idVec3( 1, 0, 0 ) );
  1167. break;
  1168. }
  1169. case CREATED: {
  1170. // the right origin and direction are required if you want bullet traces
  1171. Launch( vec3_origin, idVec3( 1, 0, 0 ), vec3_origin );
  1172. break;
  1173. }
  1174. case LAUNCHED: {
  1175. if ( newState == FIZZLED ) {
  1176. Fizzle();
  1177. } else {
  1178. trace_t collision;
  1179. memset( &collision, 0, sizeof( collision ) );
  1180. collision.endAxis = GetPhysics()->GetAxis();
  1181. collision.endpos = GetPhysics()->GetOrigin();
  1182. collision.c.point = GetPhysics()->GetOrigin();
  1183. collision.c.normal.Set( 0, 0, 1 );
  1184. Explode( collision, NULL );
  1185. }
  1186. break;
  1187. }
  1188. case FIZZLED:
  1189. case EXPLODED: {
  1190. StopSound( SND_CHANNEL_BODY2, false );
  1191. gameEdit->ParseSpawnArgsToRenderEntity( &spawnArgs, &renderEntity );
  1192. state = SPAWNED;
  1193. break;
  1194. }
  1195. }
  1196. }
  1197. physicsObj.ReadFromSnapshot( msg );
  1198. if ( msg.HasChanged() ) {
  1199. UpdateVisuals();
  1200. }
  1201. }
  1202. /*
  1203. ================
  1204. idProjectile::ClientReceiveEvent
  1205. ================
  1206. */
  1207. bool idProjectile::ClientReceiveEvent( int event, int time, const idBitMsg &msg ) {
  1208. trace_t collision;
  1209. idVec3 velocity;
  1210. switch( event ) {
  1211. case EVENT_DAMAGE_EFFECT: {
  1212. memset( &collision, 0, sizeof( collision ) );
  1213. collision.c.point[0] = msg.ReadFloat();
  1214. collision.c.point[1] = msg.ReadFloat();
  1215. collision.c.point[2] = msg.ReadFloat();
  1216. collision.c.normal = msg.ReadDir( 24 );
  1217. int index = gameLocal.ClientRemapDecl( DECL_MATERIAL, msg.ReadLong() );
  1218. collision.c.material = ( index != -1 ) ? static_cast<const idMaterial *>( declManager->DeclByIndex( DECL_MATERIAL, index ) ) : NULL;
  1219. velocity[0] = msg.ReadFloat( 5, 10 );
  1220. velocity[1] = msg.ReadFloat( 5, 10 );
  1221. velocity[2] = msg.ReadFloat( 5, 10 );
  1222. DefaultDamageEffect( this, spawnArgs, collision, velocity );
  1223. return true;
  1224. }
  1225. default: {
  1226. return idEntity::ClientReceiveEvent( event, time, msg );
  1227. }
  1228. }
  1229. }
  1230. /*
  1231. ========================
  1232. idProjectile::QueueToSimulate
  1233. ========================
  1234. */
  1235. void idProjectile::QueueToSimulate( int startTime ) {
  1236. assert( common->IsMultiplayer() && common->IsServer() );
  1237. for ( int i = 0; i < MAX_SIMULATED_PROJECTILES; i++ ) {
  1238. if ( projectilesToSimulate[i].projectile == NULL ) {
  1239. projectilesToSimulate[i].projectile = this;
  1240. projectilesToSimulate[i].startTime= startTime;
  1241. if ( g_projectileDebug.GetBool() ) {
  1242. int delta = gameLocal.GetServerGameTimeMs() - startTime;
  1243. idLib::Printf( "Simulating projectile %d. Approx %d delay.\n", GetEntityNumber(), delta);
  1244. }
  1245. return;
  1246. }
  1247. }
  1248. idLib::Warning("Unable to simulate more projectiles this frame");
  1249. }
  1250. /*
  1251. ========================
  1252. idProjectile::SimulateProjectileFrame
  1253. ========================
  1254. */
  1255. void idProjectile::SimulateProjectileFrame( int msec, int endTime ) {
  1256. idVec3 oldOrigin = GetPhysics()->GetOrigin();
  1257. GetPhysics()->Evaluate( msec, endTime );
  1258. SetOrigin( GetPhysics()->GetOrigin() );
  1259. SetAxis( GetPhysics()->GetAxis() );
  1260. if ( g_projectileDebug.GetBool() ) {
  1261. float delta = ( GetPhysics()->GetOrigin() - oldOrigin ).Length();
  1262. idLib::Printf( "Simulated projectile %d. Delta: %.2f \n", GetEntityNumber(), delta );
  1263. //clientGame->renderWorld->DebugLine( idColor::colorYellow, oldOrigin, GetPhysics()->GetOrigin(), 5000 );
  1264. }
  1265. }
  1266. /*
  1267. ========================
  1268. idProjectile::PostSimulate
  1269. ========================
  1270. */
  1271. void idProjectile::PostSimulate( int endTime ) {
  1272. if ( state == EXPLODED || state == FIZZLED ) {
  1273. // Already exploded. To see the explosion on the collision surface instead of
  1274. // at the muzzle, don't set the deltas to the launch origin and axis.
  1275. CreateDeltasFromOldOriginAndAxis( GetPhysics()->GetOrigin(), GetPhysics()->GetAxis() );
  1276. } else {
  1277. CreateDeltasFromOldOriginAndAxis( launchOrigin, launchAxis );
  1278. }
  1279. }
  1280. /*
  1281. ===============================================================================
  1282. idGuidedProjectile
  1283. ===============================================================================
  1284. */
  1285. const idEventDef EV_SetEnemy( "setEnemy", "E" );
  1286. CLASS_DECLARATION( idProjectile, idGuidedProjectile )
  1287. EVENT( EV_SetEnemy, idGuidedProjectile::Event_SetEnemy )
  1288. END_CLASS
  1289. /*
  1290. ================
  1291. idGuidedProjectile::idGuidedProjectile
  1292. ================
  1293. */
  1294. idGuidedProjectile::idGuidedProjectile() {
  1295. enemy = NULL;
  1296. speed = 0.0f;
  1297. turn_max = 0.0f;
  1298. clamp_dist = 0.0f;
  1299. rndScale = ang_zero;
  1300. rndAng = ang_zero;
  1301. rndUpdateTime = 0;
  1302. angles = ang_zero;
  1303. burstMode = false;
  1304. burstDist = 0;
  1305. burstVelocity = 0.0f;
  1306. unGuided = false;
  1307. }
  1308. /*
  1309. =================
  1310. idGuidedProjectile::~idGuidedProjectile
  1311. =================
  1312. */
  1313. idGuidedProjectile::~idGuidedProjectile() {
  1314. }
  1315. /*
  1316. ================
  1317. idGuidedProjectile::Spawn
  1318. ================
  1319. */
  1320. void idGuidedProjectile::Spawn() {
  1321. }
  1322. /*
  1323. ================
  1324. idGuidedProjectile::Save
  1325. ================
  1326. */
  1327. void idGuidedProjectile::Save( idSaveGame *savefile ) const {
  1328. enemy.Save( savefile );
  1329. savefile->WriteFloat( speed );
  1330. savefile->WriteAngles( rndScale );
  1331. savefile->WriteAngles( rndAng );
  1332. savefile->WriteInt( rndUpdateTime );
  1333. savefile->WriteFloat( turn_max );
  1334. savefile->WriteFloat( clamp_dist );
  1335. savefile->WriteAngles( angles );
  1336. savefile->WriteBool( burstMode );
  1337. savefile->WriteBool( unGuided );
  1338. savefile->WriteFloat( burstDist );
  1339. savefile->WriteFloat( burstVelocity );
  1340. }
  1341. /*
  1342. ================
  1343. idGuidedProjectile::Restore
  1344. ================
  1345. */
  1346. void idGuidedProjectile::Restore( idRestoreGame *savefile ) {
  1347. enemy.Restore( savefile );
  1348. savefile->ReadFloat( speed );
  1349. savefile->ReadAngles( rndScale );
  1350. savefile->ReadAngles( rndAng );
  1351. savefile->ReadInt( rndUpdateTime );
  1352. savefile->ReadFloat( turn_max );
  1353. savefile->ReadFloat( clamp_dist );
  1354. savefile->ReadAngles( angles );
  1355. savefile->ReadBool( burstMode );
  1356. savefile->ReadBool( unGuided );
  1357. savefile->ReadFloat( burstDist );
  1358. savefile->ReadFloat( burstVelocity );
  1359. }
  1360. /*
  1361. ================
  1362. idGuidedProjectile::GetSeekPos
  1363. ================
  1364. */
  1365. void idGuidedProjectile::GetSeekPos( idVec3 &out ) {
  1366. idEntity *enemyEnt = enemy.GetEntity();
  1367. if ( enemyEnt ) {
  1368. if ( enemyEnt->IsType( idActor::Type ) ) {
  1369. out = static_cast<idActor *>(enemyEnt)->GetEyePosition();
  1370. out.z -= 12.0f;
  1371. } else {
  1372. out = enemyEnt->GetPhysics()->GetOrigin();
  1373. }
  1374. } else {
  1375. out = GetPhysics()->GetOrigin() + physicsObj.GetLinearVelocity() * 2.0f;
  1376. }
  1377. }
  1378. /*
  1379. ================
  1380. idGuidedProjectile::Think
  1381. ================
  1382. */
  1383. void idGuidedProjectile::Think() {
  1384. idVec3 dir;
  1385. idVec3 seekPos;
  1386. idVec3 velocity;
  1387. idVec3 nose;
  1388. idVec3 tmp;
  1389. idMat3 axis;
  1390. idAngles dirAng;
  1391. idAngles diff;
  1392. float dist;
  1393. float frac;
  1394. int i;
  1395. if ( state == LAUNCHED && !unGuided ) {
  1396. GetSeekPos( seekPos );
  1397. if ( rndUpdateTime < gameLocal.time ) {
  1398. rndAng[ 0 ] = rndScale[ 0 ] * gameLocal.random.CRandomFloat();
  1399. rndAng[ 1 ] = rndScale[ 1 ] * gameLocal.random.CRandomFloat();
  1400. rndAng[ 2 ] = rndScale[ 2 ] * gameLocal.random.CRandomFloat();
  1401. rndUpdateTime = gameLocal.time + 200;
  1402. }
  1403. nose = physicsObj.GetOrigin() + 10.0f * physicsObj.GetAxis()[0];
  1404. dir = seekPos - nose;
  1405. dist = dir.Normalize();
  1406. dirAng = dir.ToAngles();
  1407. // make it more accurate as it gets closer
  1408. frac = dist / clamp_dist;
  1409. if ( frac > 1.0f ) {
  1410. frac = 1.0f;
  1411. }
  1412. diff = dirAng - angles + rndAng * frac;
  1413. // clamp the to the max turn rate
  1414. diff.Normalize180();
  1415. for( i = 0; i < 3; i++ ) {
  1416. if ( diff[ i ] > turn_max ) {
  1417. diff[ i ] = turn_max;
  1418. } else if ( diff[ i ] < -turn_max ) {
  1419. diff[ i ] = -turn_max;
  1420. }
  1421. }
  1422. angles += diff;
  1423. // make the visual model always points the dir we're traveling
  1424. dir = angles.ToForward();
  1425. velocity = dir * speed;
  1426. if ( burstMode && dist < burstDist ) {
  1427. unGuided = true;
  1428. velocity *= burstVelocity;
  1429. }
  1430. physicsObj.SetLinearVelocity( velocity );
  1431. // align z-axis of model with the direction
  1432. axis = dir.ToMat3();
  1433. tmp = axis[2];
  1434. axis[2] = axis[0];
  1435. axis[0] = -tmp;
  1436. GetPhysics()->SetAxis( axis );
  1437. }
  1438. idProjectile::Think();
  1439. }
  1440. /*
  1441. =================
  1442. idGuidedProjectile::Launch
  1443. =================
  1444. */
  1445. void idGuidedProjectile::Launch( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity, const float timeSinceFire, const float launchPower, float dmgPower ) {
  1446. idProjectile::Launch( start, dir, pushVelocity, timeSinceFire, launchPower, dmgPower );
  1447. if ( owner.GetEntity() ) {
  1448. if ( owner.GetEntity()->IsType( idAI::Type ) ) {
  1449. enemy = static_cast<idAI *>( owner.GetEntity() )->GetEnemy();
  1450. } else if ( owner.GetEntity()->IsType( idPlayer::Type ) ) {
  1451. trace_t tr;
  1452. idPlayer *player = static_cast<idPlayer*>( owner.GetEntity() );
  1453. idVec3 start = player->GetEyePosition();
  1454. idVec3 end = start + player->viewAxis[0] * 1000.0f;
  1455. gameLocal.clip.TracePoint( tr, start, end, MASK_SHOT_RENDERMODEL | CONTENTS_BODY, owner.GetEntity() );
  1456. if ( tr.fraction < 1.0f ) {
  1457. enemy = gameLocal.GetTraceEntity( tr );
  1458. }
  1459. // ignore actors on the player's team
  1460. if ( enemy.GetEntity() == NULL || !enemy.GetEntity()->IsType( idActor::Type ) || ( static_cast<idActor *>( enemy.GetEntity() )->team == player->team ) ) {
  1461. enemy = player->EnemyWithMostHealth();
  1462. }
  1463. }
  1464. }
  1465. const idVec3 &vel = physicsObj.GetLinearVelocity();
  1466. angles = vel.ToAngles();
  1467. speed = vel.Length();
  1468. rndScale = spawnArgs.GetAngles( "random", "15 15 0" );
  1469. turn_max = spawnArgs.GetFloat( "turn_max", "180" ) / com_engineHz_latched;
  1470. clamp_dist = spawnArgs.GetFloat( "clamp_dist", "256" );
  1471. burstMode = spawnArgs.GetBool( "burstMode" );
  1472. unGuided = false;
  1473. burstDist = spawnArgs.GetFloat( "burstDist", "64" );
  1474. burstVelocity = spawnArgs.GetFloat( "burstVelocity", "1.25" );
  1475. UpdateVisuals();
  1476. }
  1477. void idGuidedProjectile::SetEnemy( idEntity *ent ) {
  1478. enemy = ent;
  1479. }
  1480. void idGuidedProjectile::Event_SetEnemy(idEntity *ent) {
  1481. SetEnemy(ent);
  1482. }
  1483. /*
  1484. ===============================================================================
  1485. idSoulCubeMissile
  1486. ===============================================================================
  1487. */
  1488. CLASS_DECLARATION( idGuidedProjectile, idSoulCubeMissile )
  1489. END_CLASS
  1490. /*
  1491. ================
  1492. idSoulCubeMissile::Spawn()
  1493. ================
  1494. */
  1495. void idSoulCubeMissile::Spawn() {
  1496. startingVelocity.Zero();
  1497. endingVelocity.Zero();
  1498. accelTime = 0.0f;
  1499. launchTime = 0;
  1500. killPhase = false;
  1501. returnPhase = false;
  1502. smokeKillTime = 0;
  1503. smokeKill = NULL;
  1504. }
  1505. /*
  1506. =================
  1507. idSoulCubeMissile::~idSoulCubeMissile
  1508. =================
  1509. */
  1510. idSoulCubeMissile::~idSoulCubeMissile() {
  1511. }
  1512. /*
  1513. ================
  1514. idSoulCubeMissile::Save
  1515. ================
  1516. */
  1517. void idSoulCubeMissile::Save( idSaveGame *savefile ) const {
  1518. savefile->WriteVec3( startingVelocity );
  1519. savefile->WriteVec3( endingVelocity );
  1520. savefile->WriteFloat( accelTime );
  1521. savefile->WriteInt( launchTime );
  1522. savefile->WriteBool( killPhase );
  1523. savefile->WriteBool( returnPhase );
  1524. savefile->WriteVec3( destOrg);
  1525. savefile->WriteInt( orbitTime );
  1526. savefile->WriteVec3( orbitOrg );
  1527. savefile->WriteInt( smokeKillTime );
  1528. savefile->WriteParticle( smokeKill );
  1529. }
  1530. /*
  1531. ================
  1532. idSoulCubeMissile::Restore
  1533. ================
  1534. */
  1535. void idSoulCubeMissile::Restore( idRestoreGame *savefile ) {
  1536. savefile->ReadVec3( startingVelocity );
  1537. savefile->ReadVec3( endingVelocity );
  1538. savefile->ReadFloat( accelTime );
  1539. savefile->ReadInt( launchTime );
  1540. savefile->ReadBool( killPhase );
  1541. savefile->ReadBool( returnPhase );
  1542. savefile->ReadVec3( destOrg);
  1543. savefile->ReadInt( orbitTime );
  1544. savefile->ReadVec3( orbitOrg );
  1545. savefile->ReadInt( smokeKillTime );
  1546. savefile->ReadParticle( smokeKill );
  1547. }
  1548. /*
  1549. ================
  1550. idSoulCubeMissile::KillTarget
  1551. ================
  1552. */
  1553. void idSoulCubeMissile::KillTarget( const idVec3 &dir ) {
  1554. idEntity *ownerEnt;
  1555. const char *smokeName;
  1556. idActor *act;
  1557. ReturnToOwner();
  1558. if ( enemy.GetEntity() && enemy.GetEntity()->IsType( idActor::Type ) ) {
  1559. act = static_cast<idActor*>( enemy.GetEntity() );
  1560. killPhase = true;
  1561. orbitOrg = act->GetPhysics()->GetAbsBounds().GetCenter();
  1562. orbitTime = gameLocal.time;
  1563. smokeKillTime = 0;
  1564. smokeName = spawnArgs.GetString( "smoke_kill" );
  1565. if ( *smokeName != '\0' ) {
  1566. smokeKill = static_cast<const idDeclParticle *>( declManager->FindType( DECL_PARTICLE, smokeName ) );
  1567. smokeKillTime = gameLocal.time;
  1568. }
  1569. ownerEnt = owner.GetEntity();
  1570. if ( ( act->health > 0 ) && ownerEnt != NULL && ownerEnt->IsType( idPlayer::Type ) && ( ownerEnt->health > 0 ) && !act->spawnArgs.GetBool( "boss" ) ) {
  1571. static_cast<idPlayer *>( ownerEnt )->GiveHealthPool( act->health );
  1572. }
  1573. act->Damage( this, owner.GetEntity(), dir, spawnArgs.GetString( "def_damage" ), 1.0f, INVALID_JOINT );
  1574. act->GetAFPhysics()->SetTimeScale( 0.25 );
  1575. StartSound( "snd_explode", SND_CHANNEL_BODY, 0, false, NULL );
  1576. }
  1577. }
  1578. /*
  1579. ================
  1580. idSoulCubeMissile::Think
  1581. ================
  1582. */
  1583. void idSoulCubeMissile::Think() {
  1584. float pct;
  1585. idVec3 seekPos;
  1586. idEntity *ownerEnt;
  1587. if ( state == LAUNCHED ) {
  1588. if ( killPhase ) {
  1589. // orbit the mob, cascading down
  1590. if ( gameLocal.time < orbitTime + 1500 ) {
  1591. if ( !gameLocal.smokeParticles->EmitSmoke( smokeKill, smokeKillTime, gameLocal.random.CRandomFloat(), orbitOrg, mat3_identity, timeGroup /*_D3XP*/ ) ) {
  1592. smokeKillTime = gameLocal.time;
  1593. }
  1594. }
  1595. } else {
  1596. if ( accelTime && gameLocal.time < launchTime + accelTime * 1000 ) {
  1597. pct = ( gameLocal.time - launchTime ) / ( accelTime * 1000 );
  1598. speed = ( startingVelocity + ( startingVelocity + endingVelocity ) * pct ).Length();
  1599. }
  1600. }
  1601. idGuidedProjectile::Think();
  1602. GetSeekPos( seekPos );
  1603. if ( ( seekPos - physicsObj.GetOrigin() ).Length() < 32.0f ) {
  1604. if ( returnPhase ) {
  1605. StopSound( SND_CHANNEL_ANY, false );
  1606. StartSound( "snd_return", SND_CHANNEL_BODY2, 0, false, NULL );
  1607. Hide();
  1608. PostEventSec( &EV_Remove, 2.0f );
  1609. ownerEnt = owner.GetEntity();
  1610. if ( ownerEnt != NULL && ownerEnt->IsType( idPlayer::Type ) ) {
  1611. static_cast<idPlayer *>( ownerEnt )->SetSoulCubeProjectile( NULL );
  1612. }
  1613. state = FIZZLED;
  1614. } else if ( !killPhase ){
  1615. KillTarget( physicsObj.GetAxis()[0] );
  1616. }
  1617. }
  1618. }
  1619. }
  1620. /*
  1621. ================
  1622. idSoulCubeMissile::GetSeekPos
  1623. ================
  1624. */
  1625. void idSoulCubeMissile::GetSeekPos( idVec3 &out ) {
  1626. if ( returnPhase && owner.GetEntity() && owner.GetEntity()->IsType( idActor::Type ) ) {
  1627. idActor *act = static_cast<idActor*>( owner.GetEntity() );
  1628. out = act->GetEyePosition();
  1629. return;
  1630. }
  1631. if ( destOrg != vec3_zero ) {
  1632. out = destOrg;
  1633. return;
  1634. }
  1635. idGuidedProjectile::GetSeekPos( out );
  1636. }
  1637. /*
  1638. ================
  1639. idSoulCubeMissile::Event_ReturnToOwner
  1640. ================
  1641. */
  1642. void idSoulCubeMissile::ReturnToOwner() {
  1643. speed *= 0.65f;
  1644. killPhase = false;
  1645. returnPhase = true;
  1646. smokeFlyTime = 0;
  1647. }
  1648. /*
  1649. =================
  1650. idSoulCubeMissile::Launch
  1651. =================
  1652. */
  1653. void idSoulCubeMissile::Launch( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity, const float timeSinceFire, const float launchPower, float dmgPower ) {
  1654. idVec3 newStart;
  1655. idVec3 offs;
  1656. idEntity *ownerEnt;
  1657. // push it out a little
  1658. newStart = start + dir * spawnArgs.GetFloat( "launchDist" );
  1659. offs = spawnArgs.GetVector( "launchOffset", "0 0 -4" );
  1660. newStart += offs;
  1661. idGuidedProjectile::Launch( newStart, dir, pushVelocity, timeSinceFire, launchPower, dmgPower );
  1662. if ( enemy.GetEntity() == NULL || !enemy.GetEntity()->IsType( idActor::Type ) ) {
  1663. destOrg = start + dir * 256.0f;
  1664. } else {
  1665. destOrg.Zero();
  1666. }
  1667. physicsObj.SetClipMask( 0 ); // never collide.. think routine will decide when to detonate
  1668. startingVelocity = spawnArgs.GetVector( "startingVelocity", "15 0 0" );
  1669. endingVelocity = spawnArgs.GetVector( "endingVelocity", "1500 0 0" );
  1670. accelTime = spawnArgs.GetFloat( "accelTime", "5" );
  1671. physicsObj.SetLinearVelocity( startingVelocity.Length() * physicsObj.GetAxis()[2] );
  1672. launchTime = gameLocal.time;
  1673. killPhase = false;
  1674. UpdateVisuals();
  1675. ownerEnt = owner.GetEntity();
  1676. if ( ownerEnt != NULL && ownerEnt->IsType( idPlayer::Type ) ) {
  1677. static_cast<idPlayer *>( ownerEnt )->SetSoulCubeProjectile( this );
  1678. }
  1679. }
  1680. /*
  1681. ===============================================================================
  1682. idBFGProjectile
  1683. ===============================================================================
  1684. */
  1685. const idEventDef EV_RemoveBeams( "<removeBeams>", NULL );
  1686. CLASS_DECLARATION( idProjectile, idBFGProjectile )
  1687. EVENT( EV_RemoveBeams, idBFGProjectile::Event_RemoveBeams )
  1688. END_CLASS
  1689. /*
  1690. =================
  1691. idBFGProjectile::idBFGProjectile
  1692. =================
  1693. */
  1694. idBFGProjectile::idBFGProjectile() {
  1695. memset( &secondModel, 0, sizeof( secondModel ) );
  1696. secondModelDefHandle = -1;
  1697. nextDamageTime = 0;
  1698. }
  1699. /*
  1700. =================
  1701. idBFGProjectile::~idBFGProjectile
  1702. =================
  1703. */
  1704. idBFGProjectile::~idBFGProjectile() {
  1705. FreeBeams();
  1706. if ( secondModelDefHandle >= 0 ) {
  1707. gameRenderWorld->FreeEntityDef( secondModelDefHandle );
  1708. secondModelDefHandle = -1;
  1709. }
  1710. }
  1711. /*
  1712. ================
  1713. idBFGProjectile::Spawn
  1714. ================
  1715. */
  1716. void idBFGProjectile::Spawn() {
  1717. beamTargets.Clear();
  1718. memset( &secondModel, 0, sizeof( secondModel ) );
  1719. secondModelDefHandle = -1;
  1720. const char *temp = spawnArgs.GetString( "model_two" );
  1721. if ( temp != NULL && *temp != NULL ) {
  1722. secondModel.hModel = renderModelManager->FindModel( temp );
  1723. secondModel.bounds = secondModel.hModel->Bounds( &secondModel );
  1724. secondModel.shaderParms[ SHADERPARM_RED ] =
  1725. secondModel.shaderParms[ SHADERPARM_GREEN ] =
  1726. secondModel.shaderParms[ SHADERPARM_BLUE ] =
  1727. secondModel.shaderParms[ SHADERPARM_ALPHA ] = 1.0f;
  1728. secondModel.noSelfShadow = true;
  1729. secondModel.noShadow = true;
  1730. }
  1731. nextDamageTime = 0;
  1732. damageFreq = NULL;
  1733. }
  1734. /*
  1735. ================
  1736. idBFGProjectile::Save
  1737. ================
  1738. */
  1739. void idBFGProjectile::Save( idSaveGame *savefile ) const {
  1740. int i;
  1741. savefile->WriteInt( beamTargets.Num() );
  1742. for ( i = 0; i < beamTargets.Num(); i++ ) {
  1743. beamTargets[i].target.Save( savefile );
  1744. savefile->WriteRenderEntity( beamTargets[i].renderEntity );
  1745. savefile->WriteInt( beamTargets[i].modelDefHandle );
  1746. }
  1747. savefile->WriteRenderEntity( secondModel );
  1748. savefile->WriteInt( secondModelDefHandle );
  1749. savefile->WriteInt( nextDamageTime );
  1750. savefile->WriteString( damageFreq );
  1751. }
  1752. /*
  1753. ================
  1754. idBFGProjectile::Restore
  1755. ================
  1756. */
  1757. void idBFGProjectile::Restore( idRestoreGame *savefile ) {
  1758. int i, num;
  1759. savefile->ReadInt( num );
  1760. beamTargets.SetNum( num );
  1761. for ( i = 0; i < num; i++ ) {
  1762. beamTargets[i].target.Restore( savefile );
  1763. savefile->ReadRenderEntity( beamTargets[i].renderEntity );
  1764. savefile->ReadInt( beamTargets[i].modelDefHandle );
  1765. if ( beamTargets[i].modelDefHandle >= 0 ) {
  1766. beamTargets[i].modelDefHandle = gameRenderWorld->AddEntityDef( &beamTargets[i].renderEntity );
  1767. }
  1768. }
  1769. savefile->ReadRenderEntity( secondModel );
  1770. savefile->ReadInt( secondModelDefHandle );
  1771. savefile->ReadInt( nextDamageTime );
  1772. savefile->ReadString( damageFreq );
  1773. if ( secondModelDefHandle >= 0 ) {
  1774. secondModelDefHandle = gameRenderWorld->AddEntityDef( &secondModel );
  1775. }
  1776. }
  1777. /*
  1778. =================
  1779. idBFGProjectile::FreeBeams
  1780. =================
  1781. */
  1782. void idBFGProjectile::FreeBeams() {
  1783. for ( int i = 0; i < beamTargets.Num(); i++ ) {
  1784. if ( beamTargets[i].modelDefHandle >= 0 ) {
  1785. gameRenderWorld->FreeEntityDef( beamTargets[i].modelDefHandle );
  1786. beamTargets[i].modelDefHandle = -1;
  1787. }
  1788. }
  1789. idPlayer *player = gameLocal.GetLocalPlayer();
  1790. if ( player ) {
  1791. player->playerView.EnableBFGVision( false );
  1792. }
  1793. }
  1794. /*
  1795. ================
  1796. idBFGProjectile::Think
  1797. ================
  1798. */
  1799. void idBFGProjectile::Think() {
  1800. if ( state == LAUNCHED ) {
  1801. // update beam targets
  1802. for ( int i = 0; i < beamTargets.Num(); i++ ) {
  1803. if ( beamTargets[i].target.GetEntity() == NULL ) {
  1804. continue;
  1805. }
  1806. idPlayer *player = ( beamTargets[i].target.GetEntity()->IsType( idPlayer::Type ) ) ? static_cast<idPlayer*>( beamTargets[i].target.GetEntity() ) : NULL;
  1807. // Major hack for end boss. :(
  1808. idAnimatedEntity *beamEnt;
  1809. idVec3 org;
  1810. bool forceDamage = false;
  1811. beamEnt = static_cast<idAnimatedEntity*>(beamTargets[i].target.GetEntity());
  1812. if ( !idStr::Cmp( beamEnt->GetEntityDefName(), "monster_boss_d3xp_maledict" ) ) {
  1813. SetTimeState ts( beamEnt->timeGroup );
  1814. idMat3 temp;
  1815. jointHandle_t bodyJoint;
  1816. bodyJoint = beamEnt->GetAnimator()->GetJointHandle( "Chest1" );
  1817. beamEnt->GetJointWorldTransform( bodyJoint, gameLocal.time, org, temp );
  1818. forceDamage = true;
  1819. } else {
  1820. org = beamEnt->GetPhysics()->GetAbsBounds().GetCenter();
  1821. }
  1822. beamTargets[i].renderEntity.origin = GetPhysics()->GetOrigin();
  1823. beamTargets[i].renderEntity.shaderParms[ SHADERPARM_BEAM_END_X ] = org.x;
  1824. beamTargets[i].renderEntity.shaderParms[ SHADERPARM_BEAM_END_Y ] = org.y;
  1825. beamTargets[i].renderEntity.shaderParms[ SHADERPARM_BEAM_END_Z ] = org.z;
  1826. beamTargets[i].renderEntity.shaderParms[ SHADERPARM_RED ] =
  1827. beamTargets[i].renderEntity.shaderParms[ SHADERPARM_GREEN ] =
  1828. beamTargets[i].renderEntity.shaderParms[ SHADERPARM_BLUE ] =
  1829. beamTargets[i].renderEntity.shaderParms[ SHADERPARM_ALPHA ] = 1.0f;
  1830. if ( gameLocal.time > nextDamageTime ) {
  1831. bool bfgVision = true;
  1832. if ( damageFreq && *(const char *)damageFreq && beamTargets[i].target.GetEntity() && ( forceDamage || beamTargets[i].target.GetEntity()->CanDamage( GetPhysics()->GetOrigin(), org ) ) ) {
  1833. org = beamTargets[i].target.GetEntity()->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin();
  1834. org.Normalize();
  1835. beamTargets[i].target.GetEntity()->Damage( this, owner.GetEntity(), org, damageFreq, ( damagePower ) ? damagePower : 1.0f, INVALID_JOINT );
  1836. } else {
  1837. beamTargets[i].renderEntity.shaderParms[ SHADERPARM_RED ] =
  1838. beamTargets[i].renderEntity.shaderParms[ SHADERPARM_GREEN ] =
  1839. beamTargets[i].renderEntity.shaderParms[ SHADERPARM_BLUE ] =
  1840. beamTargets[i].renderEntity.shaderParms[ SHADERPARM_ALPHA ] = 0.0f;
  1841. bfgVision = false;
  1842. }
  1843. if ( player ) {
  1844. player->playerView.EnableBFGVision( bfgVision );
  1845. }
  1846. nextDamageTime = gameLocal.time + BFG_DAMAGE_FREQUENCY;
  1847. }
  1848. gameRenderWorld->UpdateEntityDef( beamTargets[i].modelDefHandle, &beamTargets[i].renderEntity );
  1849. }
  1850. if ( secondModelDefHandle >= 0 ) {
  1851. secondModel.origin = GetPhysics()->GetOrigin();
  1852. gameRenderWorld->UpdateEntityDef( secondModelDefHandle, &secondModel );
  1853. }
  1854. idAngles ang;
  1855. ang.pitch = ( gameLocal.time & 4095 ) * 360.0f / -4096.0f;
  1856. ang.yaw = ang.pitch;
  1857. ang.roll = 0.0f;
  1858. SetAngles( ang );
  1859. ang.pitch = ( gameLocal.time & 2047 ) * 360.0f / -2048.0f;
  1860. ang.yaw = ang.pitch;
  1861. ang.roll = 0.0f;
  1862. secondModel.axis = ang.ToMat3();
  1863. UpdateVisuals();
  1864. }
  1865. idProjectile::Think();
  1866. }
  1867. /*
  1868. =================
  1869. idBFGProjectile::Launch
  1870. =================
  1871. */
  1872. void idBFGProjectile::Launch( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity, const float timeSinceFire, const float power, const float dmgPower ) {
  1873. idProjectile::Launch( start, dir, pushVelocity, 0.0f, power, dmgPower );
  1874. // dmgPower * radius is the target acquisition area
  1875. // acquisition should make sure that monsters are not dormant
  1876. // which will cut down on hitting monsters not actively fighting
  1877. // but saves on the traces making sure they are visible
  1878. // damage is not applied until the projectile explodes
  1879. idEntity * ent;
  1880. idEntity * entityList[ MAX_GENTITIES ];
  1881. int numListedEntities;
  1882. idBounds bounds;
  1883. idVec3 damagePoint;
  1884. float radius;
  1885. spawnArgs.GetFloat( "damageRadius", "512", radius );
  1886. bounds = idBounds( GetPhysics()->GetOrigin() ).Expand( radius );
  1887. float beamWidth = spawnArgs.GetFloat( "beam_WidthFly" );
  1888. const char *skin = spawnArgs.GetString( "skin_beam" );
  1889. memset( &secondModel, 0, sizeof( secondModel ) );
  1890. secondModelDefHandle = -1;
  1891. const char *temp = spawnArgs.GetString( "model_two" );
  1892. if ( temp != NULL && *temp != NULL ) {
  1893. secondModel.hModel = renderModelManager->FindModel( temp );
  1894. secondModel.bounds = secondModel.hModel->Bounds( &secondModel );
  1895. secondModel.shaderParms[ SHADERPARM_RED ] =
  1896. secondModel.shaderParms[ SHADERPARM_GREEN ] =
  1897. secondModel.shaderParms[ SHADERPARM_BLUE ] =
  1898. secondModel.shaderParms[ SHADERPARM_ALPHA ] = 1.0f;
  1899. secondModel.noSelfShadow = true;
  1900. secondModel.noShadow = true;
  1901. secondModel.origin = GetPhysics()->GetOrigin();
  1902. secondModel.axis = GetPhysics()->GetAxis();
  1903. secondModelDefHandle = gameRenderWorld->AddEntityDef( &secondModel );
  1904. }
  1905. idVec3 delta( 15.0f, 15.0f, 15.0f );
  1906. //physicsObj.SetAngularExtrapolation( extrapolation_t(EXTRAPOLATION_LINEAR|EXTRAPOLATION_NOSTOP), gameLocal.time, 0, physicsObj.GetAxis().ToAngles(), delta, ang_zero );
  1907. // get all entities touching the bounds
  1908. numListedEntities = gameLocal.clip.EntitiesTouchingBounds( bounds, CONTENTS_BODY, entityList, MAX_GENTITIES );
  1909. for ( int e = 0; e < numListedEntities; e++ ) {
  1910. ent = entityList[ e ];
  1911. assert( ent );
  1912. if ( ent == this || ent == owner.GetEntity() || ent->IsHidden() || !ent->IsActive() || !ent->fl.takedamage || ent->health <= 0 || !ent->IsType( idActor::Type ) ) {
  1913. continue;
  1914. }
  1915. if ( !ent->CanDamage( GetPhysics()->GetOrigin(), damagePoint ) ) {
  1916. continue;
  1917. }
  1918. if ( ent->IsType( idPlayer::Type ) ) {
  1919. idPlayer *player = static_cast<idPlayer*>( ent );
  1920. player->playerView.EnableBFGVision( true );
  1921. }
  1922. beamTarget_t bt;
  1923. memset( &bt.renderEntity, 0, sizeof( renderEntity_t ) );
  1924. bt.renderEntity.origin = GetPhysics()->GetOrigin();
  1925. bt.renderEntity.axis = GetPhysics()->GetAxis();
  1926. bt.renderEntity.shaderParms[ SHADERPARM_BEAM_WIDTH ] = beamWidth;
  1927. bt.renderEntity.shaderParms[ SHADERPARM_RED ] = 1.0f;
  1928. bt.renderEntity.shaderParms[ SHADERPARM_GREEN ] = 1.0f;
  1929. bt.renderEntity.shaderParms[ SHADERPARM_BLUE ] = 1.0f;
  1930. bt.renderEntity.shaderParms[ SHADERPARM_ALPHA ] = 1.0f;
  1931. bt.renderEntity.shaderParms[ SHADERPARM_DIVERSITY] = gameLocal.random.CRandomFloat() * 0.75;
  1932. bt.renderEntity.hModel = renderModelManager->FindModel( "_beam" );
  1933. bt.renderEntity.callback = NULL;
  1934. bt.renderEntity.numJoints = 0;
  1935. bt.renderEntity.joints = NULL;
  1936. bt.renderEntity.bounds.Clear();
  1937. bt.renderEntity.customSkin = declManager->FindSkin( skin );
  1938. bt.target = ent;
  1939. bt.modelDefHandle = gameRenderWorld->AddEntityDef( &bt.renderEntity );
  1940. beamTargets.Append( bt );
  1941. }
  1942. // Major hack for end boss. :(
  1943. idAnimatedEntity *maledict = static_cast<idAnimatedEntity*>(gameLocal.FindEntity( "monster_boss_d3xp_maledict_1" ));
  1944. if ( maledict ) {
  1945. SetTimeState ts( maledict->timeGroup );
  1946. idVec3 realPoint;
  1947. idMat3 temp;
  1948. float dist;
  1949. jointHandle_t bodyJoint;
  1950. bodyJoint = maledict->GetAnimator()->GetJointHandle( "Chest1" );
  1951. maledict->GetJointWorldTransform( bodyJoint, gameLocal.time, realPoint, temp );
  1952. dist = idVec3( realPoint - GetPhysics()->GetOrigin() ).Length();
  1953. if ( dist < radius ) {
  1954. beamTarget_t bt;
  1955. memset( &bt.renderEntity, 0, sizeof( renderEntity_t ) );
  1956. bt.renderEntity.origin = GetPhysics()->GetOrigin();
  1957. bt.renderEntity.axis = GetPhysics()->GetAxis();
  1958. bt.renderEntity.shaderParms[ SHADERPARM_BEAM_WIDTH ] = beamWidth;
  1959. bt.renderEntity.shaderParms[ SHADERPARM_RED ] = 1.0f;
  1960. bt.renderEntity.shaderParms[ SHADERPARM_GREEN ] = 1.0f;
  1961. bt.renderEntity.shaderParms[ SHADERPARM_BLUE ] = 1.0f;
  1962. bt.renderEntity.shaderParms[ SHADERPARM_ALPHA ] = 1.0f;
  1963. bt.renderEntity.shaderParms[ SHADERPARM_DIVERSITY] = gameLocal.random.CRandomFloat() * 0.75;
  1964. bt.renderEntity.hModel = renderModelManager->FindModel( "_beam" );
  1965. bt.renderEntity.callback = NULL;
  1966. bt.renderEntity.numJoints = 0;
  1967. bt.renderEntity.joints = NULL;
  1968. bt.renderEntity.bounds.Clear();
  1969. bt.renderEntity.customSkin = declManager->FindSkin( skin );
  1970. bt.target = maledict;
  1971. bt.modelDefHandle = gameRenderWorld->AddEntityDef( &bt.renderEntity );
  1972. beamTargets.Append( bt );
  1973. numListedEntities++;
  1974. }
  1975. }
  1976. if ( numListedEntities ) {
  1977. StartSound( "snd_beam", SND_CHANNEL_BODY2, 0, false, NULL );
  1978. }
  1979. damageFreq = spawnArgs.GetString( "def_damageFreq" );
  1980. nextDamageTime = gameLocal.time + BFG_DAMAGE_FREQUENCY;
  1981. UpdateVisuals();
  1982. }
  1983. /*
  1984. ================
  1985. idProjectile::Event_RemoveBeams
  1986. ================
  1987. */
  1988. void idBFGProjectile::Event_RemoveBeams() {
  1989. FreeBeams();
  1990. UpdateVisuals();
  1991. }
  1992. /*
  1993. ================
  1994. idProjectile::Explode
  1995. ================
  1996. */
  1997. void idBFGProjectile::Explode( const trace_t &collision, idEntity *ignore ) {
  1998. int i;
  1999. idVec3 dmgPoint;
  2000. idVec3 dir;
  2001. float beamWidth;
  2002. float damageScale;
  2003. const char *damage;
  2004. idPlayer * player;
  2005. idEntity * ownerEnt;
  2006. ownerEnt = owner.GetEntity();
  2007. if ( ownerEnt != NULL && ownerEnt->IsType( idPlayer::Type ) ) {
  2008. player = static_cast< idPlayer * >( ownerEnt );
  2009. } else {
  2010. player = NULL;
  2011. }
  2012. beamWidth = spawnArgs.GetFloat( "beam_WidthExplode" );
  2013. damage = spawnArgs.GetString( "def_damage" );
  2014. for ( i = 0; i < beamTargets.Num(); i++ ) {
  2015. if ( ( beamTargets[i].target.GetEntity() == NULL ) || ( ownerEnt == NULL ) ) {
  2016. continue;
  2017. }
  2018. if ( !beamTargets[i].target.GetEntity()->CanDamage( GetPhysics()->GetOrigin(), dmgPoint ) ) {
  2019. continue;
  2020. }
  2021. beamTargets[i].renderEntity.shaderParms[SHADERPARM_BEAM_WIDTH] = beamWidth;
  2022. // if the hit entity takes damage
  2023. if ( damagePower ) {
  2024. damageScale = damagePower;
  2025. } else {
  2026. damageScale = 1.0f;
  2027. }
  2028. // if the projectile owner is a player
  2029. if ( player ) {
  2030. // if the projectile hit an actor
  2031. if ( beamTargets[i].target.GetEntity()->IsType( idActor::Type ) ) {
  2032. player->SetLastHitTime( gameLocal.time );
  2033. player->AddProjectileHits( 1 );
  2034. damageScale *= player->PowerUpModifier( PROJECTILE_DAMAGE );
  2035. }
  2036. }
  2037. if ( damage[0] && ( beamTargets[i].target.GetEntity()->entityNumber > gameLocal.numClients - 1 ) ) {
  2038. dir = beamTargets[i].target.GetEntity()->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin();
  2039. dir.Normalize();
  2040. beamTargets[i].target.GetEntity()->Damage( this, ownerEnt, dir, damage, damageScale, ( collision.c.id < 0 ) ? CLIPMODEL_ID_TO_JOINT_HANDLE( collision.c.id ) : INVALID_JOINT );
  2041. }
  2042. }
  2043. if ( secondModelDefHandle >= 0 ) {
  2044. gameRenderWorld->FreeEntityDef( secondModelDefHandle );
  2045. secondModelDefHandle = -1;
  2046. }
  2047. if ( ignore == NULL ) {
  2048. projectileFlags.noSplashDamage = true;
  2049. }
  2050. if ( !common->IsClient() || fl.skipReplication ) {
  2051. if ( ignore != NULL ) {
  2052. PostEventMS( &EV_RemoveBeams, 750 );
  2053. } else {
  2054. PostEventMS( &EV_RemoveBeams, 0 );
  2055. }
  2056. }
  2057. return idProjectile::Explode( collision, ignore );
  2058. }
  2059. /*
  2060. ===============================================================================
  2061. idDebris
  2062. ===============================================================================
  2063. */
  2064. CLASS_DECLARATION( idEntity, idDebris )
  2065. EVENT( EV_Explode, idDebris::Event_Explode )
  2066. EVENT( EV_Fizzle, idDebris::Event_Fizzle )
  2067. END_CLASS
  2068. /*
  2069. ================
  2070. idDebris::Spawn
  2071. ================
  2072. */
  2073. void idDebris::Spawn() {
  2074. owner = NULL;
  2075. smokeFly = NULL;
  2076. smokeFlyTime = 0;
  2077. }
  2078. /*
  2079. ================
  2080. idDebris::Create
  2081. ================
  2082. */
  2083. void idDebris::Create( idEntity *owner, const idVec3 &start, const idMat3 &axis ) {
  2084. Unbind();
  2085. GetPhysics()->SetOrigin( start );
  2086. GetPhysics()->SetAxis( axis );
  2087. GetPhysics()->SetContents( 0 );
  2088. this->owner = owner;
  2089. smokeFly = NULL;
  2090. smokeFlyTime = 0;
  2091. sndBounce = NULL;
  2092. noGrab = true;
  2093. UpdateVisuals();
  2094. }
  2095. /*
  2096. =================
  2097. idDebris::idDebris
  2098. =================
  2099. */
  2100. idDebris::idDebris() {
  2101. owner = NULL;
  2102. smokeFly = NULL;
  2103. smokeFlyTime = 0;
  2104. sndBounce = NULL;
  2105. }
  2106. /*
  2107. =================
  2108. idDebris::~idDebris
  2109. =================
  2110. */
  2111. idDebris::~idDebris() {
  2112. }
  2113. /*
  2114. =================
  2115. idDebris::Save
  2116. =================
  2117. */
  2118. void idDebris::Save( idSaveGame *savefile ) const {
  2119. owner.Save( savefile );
  2120. savefile->WriteStaticObject( physicsObj );
  2121. savefile->WriteParticle( smokeFly );
  2122. savefile->WriteInt( smokeFlyTime );
  2123. savefile->WriteSoundShader( sndBounce );
  2124. }
  2125. /*
  2126. =================
  2127. idDebris::Restore
  2128. =================
  2129. */
  2130. void idDebris::Restore( idRestoreGame *savefile ) {
  2131. owner.Restore( savefile );
  2132. savefile->ReadStaticObject( physicsObj );
  2133. RestorePhysics( &physicsObj );
  2134. savefile->ReadParticle( smokeFly );
  2135. savefile->ReadInt( smokeFlyTime );
  2136. savefile->ReadSoundShader( sndBounce );
  2137. }
  2138. /*
  2139. =================
  2140. idDebris::Launch
  2141. =================
  2142. */
  2143. void idDebris::Launch() {
  2144. float fuse;
  2145. idVec3 velocity;
  2146. idAngles angular_velocity;
  2147. float linear_friction;
  2148. float angular_friction;
  2149. float contact_friction;
  2150. float bounce;
  2151. float mass;
  2152. float gravity;
  2153. idVec3 gravVec;
  2154. bool randomVelocity;
  2155. idMat3 axis;
  2156. renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time );
  2157. spawnArgs.GetVector( "velocity", "0 0 0", velocity );
  2158. spawnArgs.GetAngles( "angular_velocity", "0 0 0", angular_velocity );
  2159. linear_friction = spawnArgs.GetFloat( "linear_friction" );
  2160. angular_friction = spawnArgs.GetFloat( "angular_friction" );
  2161. contact_friction = spawnArgs.GetFloat( "contact_friction" );
  2162. bounce = spawnArgs.GetFloat( "bounce" );
  2163. mass = spawnArgs.GetFloat( "mass" );
  2164. gravity = spawnArgs.GetFloat( "gravity" );
  2165. fuse = spawnArgs.GetFloat( "fuse" );
  2166. randomVelocity = spawnArgs.GetBool ( "random_velocity" );
  2167. if ( mass <= 0 ) {
  2168. gameLocal.Error( "Invalid mass on '%s'\n", GetEntityDefName() );
  2169. }
  2170. if ( randomVelocity ) {
  2171. velocity.x *= gameLocal.random.RandomFloat() + 0.5f;
  2172. velocity.y *= gameLocal.random.RandomFloat() + 0.5f;
  2173. velocity.z *= gameLocal.random.RandomFloat() + 0.5f;
  2174. }
  2175. if ( health ) {
  2176. fl.takedamage = true;
  2177. }
  2178. gravVec = gameLocal.GetGravity();
  2179. gravVec.NormalizeFast();
  2180. axis = GetPhysics()->GetAxis();
  2181. Unbind();
  2182. physicsObj.SetSelf( this );
  2183. // check if a clip model is set
  2184. const char *clipModelName;
  2185. idTraceModel trm;
  2186. spawnArgs.GetString( "clipmodel", "", &clipModelName );
  2187. if ( !clipModelName[0] ) {
  2188. clipModelName = spawnArgs.GetString( "model" ); // use the visual model
  2189. }
  2190. // load the trace model
  2191. if ( !collisionModelManager->TrmFromModel( clipModelName, trm ) ) {
  2192. // default to a box
  2193. physicsObj.SetClipBox( renderEntity.bounds, 1.0f );
  2194. } else {
  2195. physicsObj.SetClipModel( new (TAG_PHYSICS_CLIP_ENTITY) idClipModel( trm ), 1.0f );
  2196. }
  2197. physicsObj.GetClipModel()->SetOwner( owner.GetEntity() );
  2198. physicsObj.SetMass( mass );
  2199. physicsObj.SetFriction( linear_friction, angular_friction, contact_friction );
  2200. if ( contact_friction == 0.0f ) {
  2201. physicsObj.NoContact();
  2202. }
  2203. physicsObj.SetBouncyness( bounce );
  2204. physicsObj.SetGravity( gravVec * gravity );
  2205. physicsObj.SetContents( 0 );
  2206. physicsObj.SetClipMask( MASK_SOLID | CONTENTS_MOVEABLECLIP );
  2207. physicsObj.SetLinearVelocity( axis[ 0 ] * velocity[ 0 ] + axis[ 1 ] * velocity[ 1 ] + axis[ 2 ] * velocity[ 2 ] );
  2208. physicsObj.SetAngularVelocity( angular_velocity.ToAngularVelocity() * axis );
  2209. physicsObj.SetOrigin( GetPhysics()->GetOrigin() );
  2210. physicsObj.SetAxis( axis );
  2211. SetPhysics( &physicsObj );
  2212. if ( !common->IsClient() ) {
  2213. if ( fuse <= 0 ) {
  2214. // run physics for 1 second
  2215. RunPhysics();
  2216. PostEventMS( &EV_Remove, 0 );
  2217. } else if ( spawnArgs.GetBool( "detonate_on_fuse" ) ) {
  2218. if ( fuse < 0.0f ) {
  2219. fuse = 0.0f;
  2220. }
  2221. RunPhysics();
  2222. PostEventSec( &EV_Explode, fuse );
  2223. } else {
  2224. if ( fuse < 0.0f ) {
  2225. fuse = 0.0f;
  2226. }
  2227. PostEventSec( &EV_Fizzle, fuse );
  2228. }
  2229. }
  2230. StartSound( "snd_fly", SND_CHANNEL_BODY, 0, false, NULL );
  2231. smokeFly = NULL;
  2232. smokeFlyTime = 0;
  2233. const char *smokeName = spawnArgs.GetString( "smoke_fly" );
  2234. if ( *smokeName != '\0' ) {
  2235. smokeFly = static_cast<const idDeclParticle *>( declManager->FindType( DECL_PARTICLE, smokeName ) );
  2236. smokeFlyTime = gameLocal.time;
  2237. gameLocal.smokeParticles->EmitSmoke( smokeFly, smokeFlyTime, gameLocal.random.CRandomFloat(), GetPhysics()->GetOrigin(), GetPhysics()->GetAxis(), timeGroup /*_D3XP*/ );
  2238. }
  2239. const char *sndName = spawnArgs.GetString( "snd_bounce" );
  2240. if ( *sndName != '\0' ) {
  2241. sndBounce = declManager->FindSound( sndName );
  2242. }
  2243. UpdateVisuals();
  2244. }
  2245. /*
  2246. ================
  2247. idDebris::Think
  2248. ================
  2249. */
  2250. void idDebris::Think() {
  2251. // run physics
  2252. RunPhysics();
  2253. Present();
  2254. if ( smokeFly && smokeFlyTime ) {
  2255. if ( !gameLocal.smokeParticles->EmitSmoke( smokeFly, smokeFlyTime, gameLocal.random.CRandomFloat(), GetPhysics()->GetOrigin(), GetPhysics()->GetAxis(), timeGroup /*_D3XP*/ ) ) {
  2256. smokeFlyTime = 0;
  2257. }
  2258. }
  2259. }
  2260. /*
  2261. ================
  2262. idDebris::Killed
  2263. ================
  2264. */
  2265. void idDebris::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) {
  2266. if ( spawnArgs.GetBool( "detonate_on_death" ) ) {
  2267. Explode();
  2268. } else {
  2269. Fizzle();
  2270. }
  2271. }
  2272. /*
  2273. =================
  2274. idDebris::Collide
  2275. =================
  2276. */
  2277. bool idDebris::Collide( const trace_t &collision, const idVec3 &velocity ) {
  2278. if ( sndBounce != NULL ) {
  2279. StartSoundShader( sndBounce, SND_CHANNEL_BODY, 0, false, NULL );
  2280. }
  2281. sndBounce = NULL;
  2282. return false;
  2283. }
  2284. /*
  2285. ================
  2286. idDebris::Fizzle
  2287. ================
  2288. */
  2289. void idDebris::Fizzle() {
  2290. if ( IsHidden() ) {
  2291. // already exploded
  2292. return;
  2293. }
  2294. StopSound( SND_CHANNEL_ANY, false );
  2295. StartSound( "snd_fizzle", SND_CHANNEL_BODY, 0, false, NULL );
  2296. // fizzle FX
  2297. const char *smokeName = spawnArgs.GetString( "smoke_fuse" );
  2298. if ( *smokeName != '\0' ) {
  2299. smokeFly = static_cast<const idDeclParticle *>( declManager->FindType( DECL_PARTICLE, smokeName ) );
  2300. smokeFlyTime = gameLocal.time;
  2301. gameLocal.smokeParticles->EmitSmoke( smokeFly, smokeFlyTime, gameLocal.random.CRandomFloat(), GetPhysics()->GetOrigin(), GetPhysics()->GetAxis(), timeGroup /*_D3XP*/ );
  2302. }
  2303. fl.takedamage = false;
  2304. physicsObj.SetContents( 0 );
  2305. physicsObj.PutToRest();
  2306. Hide();
  2307. if ( common->IsClient() && !fl.skipReplication ) {
  2308. return;
  2309. }
  2310. CancelEvents( &EV_Fizzle );
  2311. PostEventMS( &EV_Remove, 0 );
  2312. }
  2313. /*
  2314. ================
  2315. idDebris::Explode
  2316. ================
  2317. */
  2318. void idDebris::Explode() {
  2319. if ( IsHidden() ) {
  2320. // already exploded
  2321. return;
  2322. }
  2323. StopSound( SND_CHANNEL_ANY, false );
  2324. StartSound( "snd_explode", SND_CHANNEL_BODY, 0, false, NULL );
  2325. Hide();
  2326. // these must not be "live forever" particle systems
  2327. smokeFly = NULL;
  2328. smokeFlyTime = 0;
  2329. const char *smokeName = spawnArgs.GetString( "smoke_detonate" );
  2330. if ( *smokeName != '\0' ) {
  2331. smokeFly = static_cast<const idDeclParticle *>( declManager->FindType( DECL_PARTICLE, smokeName ) );
  2332. smokeFlyTime = gameLocal.time;
  2333. gameLocal.smokeParticles->EmitSmoke( smokeFly, smokeFlyTime, gameLocal.random.CRandomFloat(), GetPhysics()->GetOrigin(), GetPhysics()->GetAxis(), timeGroup /*_D3XP*/ );
  2334. }
  2335. fl.takedamage = false;
  2336. physicsObj.SetContents( 0 );
  2337. physicsObj.PutToRest();
  2338. CancelEvents( &EV_Explode );
  2339. PostEventMS( &EV_Remove, 0 );
  2340. }
  2341. /*
  2342. ================
  2343. idDebris::Event_Explode
  2344. ================
  2345. */
  2346. void idDebris::Event_Explode() {
  2347. Explode();
  2348. }
  2349. /*
  2350. ================
  2351. idDebris::Event_Fizzle
  2352. ================
  2353. */
  2354. void idDebris::Event_Fizzle() {
  2355. Fizzle();
  2356. }
  2357. /*
  2358. ===============================================================================
  2359. idHomingProjectile
  2360. ===============================================================================
  2361. */
  2362. CLASS_DECLARATION( idProjectile, idHomingProjectile )
  2363. EVENT( EV_SetEnemy, idHomingProjectile::Event_SetEnemy )
  2364. END_CLASS
  2365. /*
  2366. ================
  2367. idHomingProjectile::idHomingProjectile
  2368. ================
  2369. */
  2370. idHomingProjectile::idHomingProjectile() {
  2371. enemy = NULL;
  2372. speed = 0.0f;
  2373. turn_max = 0.0f;
  2374. clamp_dist = 0.0f;
  2375. rndScale = ang_zero;
  2376. rndAng = ang_zero;
  2377. angles = ang_zero;
  2378. burstMode = false;
  2379. burstDist = 0;
  2380. burstVelocity = 0.0f;
  2381. unGuided = false;
  2382. seekPos = vec3_origin;
  2383. }
  2384. /*
  2385. =================
  2386. idHomingProjectile::~idHomingProjectile
  2387. =================
  2388. */
  2389. idHomingProjectile::~idHomingProjectile() {
  2390. }
  2391. /*
  2392. ================
  2393. idHomingProjectile::Spawn
  2394. ================
  2395. */
  2396. void idHomingProjectile::Spawn() {
  2397. }
  2398. /*
  2399. ================
  2400. idHomingProjectile::Save
  2401. ================
  2402. */
  2403. void idHomingProjectile::Save( idSaveGame *savefile ) const {
  2404. enemy.Save( savefile );
  2405. savefile->WriteFloat( speed );
  2406. savefile->WriteAngles( rndScale );
  2407. savefile->WriteAngles( rndAng );
  2408. savefile->WriteFloat( turn_max );
  2409. savefile->WriteFloat( clamp_dist );
  2410. savefile->WriteAngles( angles );
  2411. savefile->WriteBool( burstMode );
  2412. savefile->WriteBool( unGuided );
  2413. savefile->WriteFloat( burstDist );
  2414. savefile->WriteFloat( burstVelocity );
  2415. savefile->WriteVec3( seekPos );
  2416. }
  2417. /*
  2418. ================
  2419. idHomingProjectile::Restore
  2420. ================
  2421. */
  2422. void idHomingProjectile::Restore( idRestoreGame *savefile ) {
  2423. enemy.Restore( savefile );
  2424. savefile->ReadFloat( speed );
  2425. savefile->ReadAngles( rndScale );
  2426. savefile->ReadAngles( rndAng );
  2427. savefile->ReadFloat( turn_max );
  2428. savefile->ReadFloat( clamp_dist );
  2429. savefile->ReadAngles( angles );
  2430. savefile->ReadBool( burstMode );
  2431. savefile->ReadBool( unGuided );
  2432. savefile->ReadFloat( burstDist );
  2433. savefile->ReadFloat( burstVelocity );
  2434. savefile->ReadVec3( seekPos );
  2435. }
  2436. /*
  2437. ================
  2438. idHomingProjectile::Think
  2439. ================
  2440. */
  2441. void idHomingProjectile::Think() {
  2442. if ( seekPos == vec3_zero ) {
  2443. // ai def uses a single def_projectile .. guardian has two projectile types so when seekPos is zero, just run regular projectile
  2444. idProjectile::Think();
  2445. return;
  2446. }
  2447. idVec3 dir;
  2448. idVec3 velocity;
  2449. idVec3 nose;
  2450. idVec3 tmp;
  2451. idMat3 axis;
  2452. idAngles dirAng;
  2453. idAngles diff;
  2454. float dist;
  2455. float frac;
  2456. int i;
  2457. nose = physicsObj.GetOrigin() + 10.0f * physicsObj.GetAxis()[0];
  2458. dir = seekPos - nose;
  2459. dist = dir.Normalize();
  2460. dirAng = dir.ToAngles();
  2461. // make it more accurate as it gets closer
  2462. frac = ( dist * 2.0f ) / clamp_dist;
  2463. if ( frac > 1.0f ) {
  2464. frac = 1.0f;
  2465. }
  2466. diff = dirAng - angles * frac;
  2467. // clamp the to the max turn rate
  2468. diff.Normalize180();
  2469. for( i = 0; i < 3; i++ ) {
  2470. if ( diff[ i ] > turn_max ) {
  2471. diff[ i ] = turn_max;
  2472. } else if ( diff[ i ] < -turn_max ) {
  2473. diff[ i ] = -turn_max;
  2474. }
  2475. }
  2476. angles += diff;
  2477. // make the visual model always points the dir we're traveling
  2478. dir = angles.ToForward();
  2479. velocity = dir * speed;
  2480. if ( burstMode && dist < burstDist ) {
  2481. unGuided = true;
  2482. velocity *= burstVelocity;
  2483. }
  2484. physicsObj.SetLinearVelocity( velocity );
  2485. // align z-axis of model with the direction
  2486. axis = dir.ToMat3();
  2487. tmp = axis[2];
  2488. axis[2] = axis[0];
  2489. axis[0] = -tmp;
  2490. GetPhysics()->SetAxis( axis );
  2491. idProjectile::Think();
  2492. }
  2493. /*
  2494. =================
  2495. idHomingProjectile::Launch
  2496. =================
  2497. */
  2498. void idHomingProjectile::Launch( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity, const float timeSinceFire, const float launchPower, float dmgPower ) {
  2499. idProjectile::Launch( start, dir, pushVelocity, timeSinceFire, launchPower, dmgPower );
  2500. if ( owner.GetEntity() ) {
  2501. if ( owner.GetEntity()->IsType( idAI::Type ) ) {
  2502. enemy = static_cast<idAI *>( owner.GetEntity() )->GetEnemy();
  2503. } else if ( owner.GetEntity()->IsType( idPlayer::Type ) ) {
  2504. trace_t tr;
  2505. idPlayer *player = static_cast<idPlayer*>( owner.GetEntity() );
  2506. idVec3 start = player->GetEyePosition();
  2507. idVec3 end = start + player->viewAxis[0] * 1000.0f;
  2508. gameLocal.clip.TracePoint( tr, start, end, MASK_SHOT_RENDERMODEL | CONTENTS_BODY, owner.GetEntity() );
  2509. if ( tr.fraction < 1.0f ) {
  2510. enemy = gameLocal.GetTraceEntity( tr );
  2511. }
  2512. // ignore actors on the player's team
  2513. if ( enemy.GetEntity() == NULL || !enemy.GetEntity()->IsType( idActor::Type ) || ( static_cast<idActor *>( enemy.GetEntity() )->team == player->team ) ) {
  2514. enemy = player->EnemyWithMostHealth();
  2515. }
  2516. }
  2517. }
  2518. const idVec3 &vel = physicsObj.GetLinearVelocity();
  2519. angles = vel.ToAngles();
  2520. speed = vel.Length();
  2521. rndScale = spawnArgs.GetAngles( "random", "15 15 0" );
  2522. turn_max = spawnArgs.GetFloat( "turn_max", "180" ) / com_engineHz_latched;
  2523. clamp_dist = spawnArgs.GetFloat( "clamp_dist", "256" );
  2524. burstMode = spawnArgs.GetBool( "burstMode" );
  2525. unGuided = false;
  2526. burstDist = spawnArgs.GetFloat( "burstDist", "64" );
  2527. burstVelocity = spawnArgs.GetFloat( "burstVelocity", "1.25" );
  2528. UpdateVisuals();
  2529. }
  2530. void idHomingProjectile::SetEnemy( idEntity *ent ) {
  2531. enemy = ent;
  2532. }
  2533. void idHomingProjectile::SetSeekPos( idVec3 pos ) {
  2534. seekPos = pos;
  2535. }
  2536. void idHomingProjectile::Event_SetEnemy(idEntity *ent) {
  2537. SetEnemy(ent);
  2538. }