g_breakable.cpp 47 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536
  1. // leave this line at the top for all g_xxxx.cpp files...
  2. #include "g_headers.h"
  3. #include "g_local.h"
  4. #include "g_functions.h"
  5. #include "..\cgame\cg_media.h"
  6. //client side shortcut hacks from cg_local.h
  7. //extern void CG_SurfaceExplosion( vec3_t origin, vec3_t normal, float radius, float shake_speed, qboolean smoke );
  8. extern void CG_MiscModelExplosion( vec3_t mins, vec3_t maxs, int size, material_t chunkType );
  9. extern void CG_Chunks( int owner, vec3_t origin, const vec3_t normal, const vec3_t mins, const vec3_t maxs,
  10. float speed, int numChunks, material_t chunkType, int customChunk, float baseScale, int customSound = 0 );
  11. extern void G_SetEnemy( gentity_t *self, gentity_t *enemy );
  12. extern gentity_t *G_CreateObject ( gentity_t *owner, vec3_t origin, vec3_t angles, int modelIndex, int frame, trType_t trType, int effectID );
  13. extern qboolean player_locked;
  14. //---------------------------------------------------
  15. static void CacheChunkEffects( material_t material )
  16. {
  17. switch( material )
  18. {
  19. case MAT_GLASS:
  20. G_EffectIndex( "chunks/glassbreak" );
  21. break;
  22. case MAT_GLASS_METAL:
  23. G_EffectIndex( "chunks/glassbreak" );
  24. G_EffectIndex( "chunks/metalexplode" );
  25. break;
  26. case MAT_ELECTRICAL:
  27. case MAT_ELEC_METAL:
  28. G_EffectIndex( "chunks/sparkexplode" );
  29. break;
  30. case MAT_METAL:
  31. case MAT_METAL2:
  32. case MAT_METAL3:
  33. case MAT_CRATE1:
  34. case MAT_CRATE2:
  35. G_EffectIndex( "chunks/metalexplode" );
  36. break;
  37. case MAT_GRATE1:
  38. G_EffectIndex( "chunks/grateexplode" );
  39. break;
  40. case MAT_DRK_STONE:
  41. case MAT_LT_STONE:
  42. case MAT_GREY_STONE:
  43. case MAT_WHITE_METAL: // what is this crap really supposed to be??
  44. G_EffectIndex( "chunks/rockbreaklg" );
  45. G_EffectIndex( "chunks/rockbreakmed" );
  46. break;
  47. case MAT_ROPE:
  48. G_EffectIndex( "chunks/ropebreak" );
  49. // G_SoundIndex(); // FIXME: give it a sound
  50. break;
  51. }
  52. }
  53. //--------------------------------------
  54. void funcBBrushDieGo (gentity_t *self)
  55. {
  56. vec3_t org, dir, up;
  57. gentity_t *attacker = self->enemy;
  58. float scale;
  59. int numChunks, size = 0;
  60. material_t chunkType = self->material;
  61. // if a missile is stuck to us, blow it up so we don't look dumb
  62. // FIXME: flag me so I should know to do this check!
  63. for ( int i = 0; i < MAX_GENTITIES; i++ )
  64. {
  65. if ( g_entities[i].s.groundEntityNum == self->s.number && ( g_entities[i].s.eFlags & EF_MISSILE_STICK ))
  66. {
  67. G_Damage( &g_entities[i], self, self, NULL, NULL, 99999, 0, MOD_CRUSH ); //?? MOD?
  68. }
  69. }
  70. //NOTE: MUST do this BEFORE clearing contents, or you may not open the area portal!!!
  71. gi.AdjustAreaPortalState( self, qtrue );
  72. //So chunks don't get stuck inside me
  73. self->s.solid = 0;
  74. self->contents = 0;
  75. self->clipmask = 0;
  76. gi.linkentity(self);
  77. VectorSet(up, 0, 0, 1);
  78. if ( self->target && attacker != NULL )
  79. {
  80. G_UseTargets(self, attacker);
  81. }
  82. VectorSubtract( self->absmax, self->absmin, org );// size
  83. numChunks = random() * 6 + 18;
  84. // This formula really has no logical basis other than the fact that it seemed to be the closest to yielding the results that I wanted.
  85. // Volume is length * width * height...then break that volume down based on how many chunks we have
  86. scale = sqrt( sqrt( org[0] * org[1] * org[2] )) * 1.75f;
  87. if ( scale > 48 )
  88. {
  89. size = 2;
  90. }
  91. else if ( scale > 24 )
  92. {
  93. size = 1;
  94. }
  95. scale = scale / numChunks;
  96. if ( self->radius > 0.0f )
  97. {
  98. // designer wants to scale number of chunks, helpful because the above scale code is far from perfect
  99. // I do this after the scale calculation because it seems that the chunk size generally seems to be very close, it's just the number of chunks is a bit weak
  100. numChunks *= self->radius;
  101. }
  102. VectorMA( self->absmin, 0.5, org, org );
  103. VectorAdd( self->absmin,self->absmax, org );
  104. VectorScale( org, 0.5f, org );
  105. if ( attacker != NULL && attacker->client )
  106. {
  107. VectorSubtract( org, attacker->currentOrigin, dir );
  108. VectorNormalize( dir );
  109. }
  110. else
  111. {
  112. VectorCopy(up, dir);
  113. }
  114. if ( !(self->spawnflags & 2048) ) // NO_EXPLOSION
  115. {
  116. // we are allowed to explode
  117. CG_MiscModelExplosion( self->absmin, self->absmax, size, chunkType );
  118. }
  119. if ( self->splashDamage > 0 && self->splashRadius > 0 )
  120. {
  121. //explode
  122. AddSightEvent( attacker, org, 256, AEL_DISCOVERED, 100 );
  123. AddSoundEvent( attacker, org, 128, AEL_DISCOVERED, qfalse, qtrue );//FIXME: am I on ground or not?
  124. G_RadiusDamage( org, self, self->splashDamage, self->splashRadius, self, MOD_UNKNOWN );
  125. gentity_t *te = G_TempEntity( org, EV_GENERAL_SOUND );
  126. te->s.eventParm = G_SoundIndex("sound/weapons/explosions/cargoexplode.wav");
  127. }
  128. else
  129. {//just break
  130. AddSightEvent( attacker, org, 128, AEL_DISCOVERED );
  131. AddSoundEvent( attacker, org, 64, AEL_SUSPICIOUS, qfalse, qtrue );//FIXME: am I on ground or not?
  132. }
  133. //FIXME: base numChunks off size?
  134. CG_Chunks( self->s.number, org, dir, self->absmin, self->absmax, 300, numChunks, chunkType, 0, scale, self->noise_index );
  135. self->e_ThinkFunc = thinkF_G_FreeEntity;
  136. self->nextthink = level.time + 50;
  137. //G_FreeEntity( self );
  138. }
  139. void funcBBrushDie (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod,int dFlags,int hitLoc)
  140. {
  141. self->takedamage = qfalse;//stop chain reaction runaway loops
  142. G_SetEnemy(self, attacker);
  143. if(self->delay)
  144. {
  145. self->e_ThinkFunc = thinkF_funcBBrushDieGo;
  146. self->nextthink = level.time + floor(self->delay * 1000.0f);
  147. return;
  148. }
  149. funcBBrushDieGo(self);
  150. }
  151. void funcBBrushUse (gentity_t *self, gentity_t *other, gentity_t *activator)
  152. {
  153. G_ActivateBehavior( self, BSET_USE );
  154. if(self->spawnflags & 64)
  155. {//Using it doesn't break it, makes it use it's targets
  156. if(self->target && self->target[0])
  157. {
  158. G_UseTargets(self, activator);
  159. }
  160. }
  161. else
  162. {
  163. funcBBrushDie(self, other, activator, self->health, MOD_UNKNOWN);
  164. }
  165. }
  166. void funcBBrushPain(gentity_t *self, gentity_t *inflictor, gentity_t *attacker, const vec3_t point, int damage, int mod,int hitLoc)
  167. {
  168. if ( self->painDebounceTime > level.time )
  169. {
  170. return;
  171. }
  172. if ( self->paintarget )
  173. {
  174. G_UseTargets2 (self, self->activator, self->paintarget);
  175. }
  176. G_ActivateBehavior( self, BSET_PAIN );
  177. if ( self->material == MAT_DRK_STONE
  178. || self->material == MAT_LT_STONE
  179. || self->material == MAT_GREY_STONE )
  180. {
  181. vec3_t org, dir;
  182. float scale;
  183. VectorSubtract( self->absmax, self->absmin, org );// size
  184. // This formula really has no logical basis other than the fact that it seemed to be the closest to yielding the results that I wanted.
  185. // Volume is length * width * height...then break that volume down based on how many chunks we have
  186. scale = VectorLength( org ) / 100.0f;
  187. VectorMA( self->absmin, 0.5, org, org );
  188. VectorAdd( self->absmin,self->absmax, org );
  189. VectorScale( org, 0.5f, org );
  190. if ( attacker != NULL && attacker->client )
  191. {
  192. VectorSubtract( attacker->currentOrigin, org, dir );
  193. VectorNormalize( dir );
  194. }
  195. else
  196. {
  197. VectorSet( dir, 0, 0, 1 );
  198. }
  199. CG_Chunks( self->s.number, org, dir, self->absmin, self->absmax, 300, Q_irand( 1, 3 ), self->material, 0, scale );
  200. }
  201. if ( self->wait == -1 )
  202. {
  203. self->e_PainFunc = painF_NULL;
  204. return;
  205. }
  206. self->painDebounceTime = level.time + self->wait;
  207. }
  208. static void InitBBrush ( gentity_t *ent )
  209. {
  210. float light;
  211. vec3_t color;
  212. qboolean lightSet, colorSet;
  213. VectorCopy( ent->s.origin, ent->pos1 );
  214. gi.SetBrushModel( ent, ent->model );
  215. ent->e_DieFunc = dieF_funcBBrushDie;
  216. ent->svFlags |= SVF_BBRUSH;
  217. // if the "model2" key is set, use a seperate model
  218. // for drawing, but clip against the brushes
  219. if ( ent->model2 )
  220. {
  221. ent->s.modelindex2 = G_ModelIndex( ent->model2 );
  222. }
  223. // if the "color" or "light" keys are set, setup constantLight
  224. lightSet = G_SpawnFloat( "light", "100", &light );
  225. colorSet = G_SpawnVector( "color", "1 1 1", color );
  226. if ( lightSet || colorSet )
  227. {
  228. int r, g, b, i;
  229. r = color[0] * 255;
  230. if ( r > 255 ) {
  231. r = 255;
  232. }
  233. g = color[1] * 255;
  234. if ( g > 255 ) {
  235. g = 255;
  236. }
  237. b = color[2] * 255;
  238. if ( b > 255 ) {
  239. b = 255;
  240. }
  241. i = light / 4;
  242. if ( i > 255 ) {
  243. i = 255;
  244. }
  245. ent->s.constantLight = r | ( g << 8 ) | ( b << 16 ) | ( i << 24 );
  246. }
  247. if(ent->spawnflags & 128)
  248. {//Can be used by the player's BUTTON_USE
  249. ent->svFlags |= SVF_PLAYER_USABLE;
  250. }
  251. ent->s.eType = ET_MOVER;
  252. gi.linkentity (ent);
  253. ent->s.pos.trType = TR_STATIONARY;
  254. VectorCopy( ent->pos1, ent->s.pos.trBase );
  255. }
  256. void funcBBrushTouch( gentity_t *ent, gentity_t *other, trace_t *trace )
  257. {
  258. }
  259. /*QUAKED func_breakable (0 .8 .5) ? INVINCIBLE IMPACT CRUSHER THIN SABERONLY HEAVY_WEAP USE_NOT_BREAK PLAYER_USE NO_EXPLOSION
  260. INVINCIBLE - can only be broken by being used
  261. IMPACT - does damage on impact
  262. CRUSHER - won't reverse movement when hit an obstacle
  263. THIN - can be broken by impact damage, like glass
  264. SABERONLY - only takes damage from sabers
  265. HEAVY_WEAP - only takes damage by a heavy weapon, like an emplaced gun or AT-ST gun.
  266. USE_NOT_BREAK - Using it doesn't make it break, still can be destroyed by damage
  267. PLAYER_USE - Player can use it with the use button
  268. NO_EXPLOSION - Does not play an explosion effect, though will still create chunks if specified
  269. When destroyed, fires it's trigger and chunks and plays sound "noise" or sound for type if no noise specified
  270. "targetname" entities with matching target will fire it
  271. "paintarget" target to fire when hit (but not destroyed)
  272. "wait" how long minimum to wait between firing paintarget each time hit
  273. "delay" When killed or used, how long (in seconds) to wait before blowing up (none by default)
  274. "model2" .md3 model to also draw
  275. "modelAngles" md3 model's angles <pitch yaw roll> (in addition to any rotation on the part of the brush entity itself)
  276. "target" all entities with a matching targetname will be used when this is destoryed
  277. "health" default is 10
  278. "radius" Chunk code tries to pick a good volume of chunks, but you can alter this to scale the number of spawned chunks. (default 1) (.5) is half as many chunks, (2) is twice as many chunks
  279. "NPC_targetname" - Only the NPC with this name can damage this
  280. "forcevisible" - When you turn on force sight (any level), you can see these draw through the entire level...
  281. "redCrosshair" - crosshair turns red when you look at this
  282. Damage: default is none
  283. "splashDamage" - damage to do
  284. "splashRadius" - radius for above damage
  285. "team" - This cannot take damage from members of this team:
  286. "player"
  287. "neutral"
  288. "enemy"
  289. Don't know if these work:
  290. "color" constantLight color
  291. "light" constantLight radius
  292. "material" - default is "0 - MAT_METAL" - choose from this list:
  293. 0 = MAT_METAL (basic blue-grey scorched-DEFAULT)
  294. 1 = MAT_GLASS
  295. 2 = MAT_ELECTRICAL (sparks only)
  296. 3 = MAT_ELEC_METAL (METAL2 chunks and sparks)
  297. 4 = MAT_DRK_STONE (brown stone chunks)
  298. 5 = MAT_LT_STONE (tan stone chunks)
  299. 6 = MAT_GLASS_METAL (glass and METAL2 chunks)
  300. 7 = MAT_METAL2 (electronic type of metal)
  301. 8 = MAT_NONE (no chunks)
  302. 9 = MAT_GREY_STONE (grey colored stone)
  303. 10 = MAT_METAL3 (METAL and METAL2 chunk combo)
  304. 11 = MAT_CRATE1 (yellow multi-colored crate chunks)
  305. 12 = MAT_GRATE1 (grate chunks--looks horrible right now)
  306. 13 = MAT_ROPE (for yavin_trial, no chunks, just wispy bits )
  307. 14 = MAT_CRATE2 (red multi-colored crate chunks)
  308. 15 = MAT_WHITE_METAL (white angular chunks for Stu, NS_hideout )
  309. */
  310. void SP_func_breakable( gentity_t *self )
  311. {
  312. if(!(self->spawnflags & 1))
  313. {
  314. if(!self->health)
  315. {
  316. self->health = 10;
  317. }
  318. }
  319. if ( self->spawnflags & 16 ) // saber only
  320. {
  321. self->flags |= FL_DMG_BY_SABER_ONLY;
  322. }
  323. else if ( self->spawnflags & 32 ) // heavy weap
  324. {
  325. self->flags |= FL_DMG_BY_HEAVY_WEAP_ONLY;
  326. }
  327. if (self->health)
  328. {
  329. self->takedamage = qtrue;
  330. }
  331. G_SoundIndex("sound/weapons/explosions/cargoexplode.wav");//precaching
  332. G_SpawnFloat( "radius", "1", &self->radius ); // used to scale chunk code if desired by a designer
  333. G_SpawnInt( "material", "0", (int*)&self->material );
  334. CacheChunkEffects( self->material );
  335. self->e_UseFunc = useF_funcBBrushUse;
  336. //if ( self->paintarget )
  337. {
  338. self->e_PainFunc = painF_funcBBrushPain;
  339. }
  340. self->e_TouchFunc = touchF_funcBBrushTouch;
  341. if ( self->team && self->team[0] )
  342. {
  343. self->noDamageTeam = (team_t)GetIDForString( TeamTable, self->team );
  344. if(self->noDamageTeam == TEAM_FREE)
  345. {
  346. G_Error("team name %s not recognized\n", self->team);
  347. }
  348. }
  349. self->team = NULL;
  350. if (!self->model) {
  351. G_Error("func_breakable with NULL model\n");
  352. }
  353. InitBBrush( self );
  354. char buffer[MAX_QPATH];
  355. char *s;
  356. if ( G_SpawnString( "noise", "*NOSOUND*", &s ) )
  357. {
  358. Q_strncpyz( buffer, s, sizeof(buffer) );
  359. COM_DefaultExtension( buffer, sizeof(buffer), ".wav");
  360. self->noise_index = G_SoundIndex(buffer);
  361. }
  362. int forceVisible = 0;
  363. G_SpawnInt( "forcevisible", "0", &forceVisible );
  364. if ( forceVisible )
  365. {//can see these through walls with force sight, so must be broadcast
  366. if ( VectorCompare( self->s.origin, vec3_origin ) )
  367. {//no origin brush
  368. self->svFlags |= SVF_BROADCAST;
  369. }
  370. self->s.eFlags |= EF_FORCE_VISIBLE;
  371. }
  372. int redCrosshair = 0;
  373. G_SpawnInt( "redCrosshair", "0", &redCrosshair );
  374. if ( redCrosshair )
  375. {//can see these through walls with force sight, so must be broadcast
  376. self->flags |= FL_RED_CROSSHAIR;
  377. }
  378. }
  379. void misc_model_breakable_pain ( gentity_t *self, gentity_t *inflictor, gentity_t *other, const vec3_t point, int damage, int mod,int hitLoc )
  380. {
  381. if ( self->health > 0 )
  382. {
  383. // still alive, react to the pain
  384. if ( self->paintarget )
  385. {
  386. G_UseTargets2 (self, self->activator, self->paintarget);
  387. }
  388. // Don't do script if dead
  389. G_ActivateBehavior( self, BSET_PAIN );
  390. }
  391. }
  392. void misc_model_breakable_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath,int dFlags,int hitLoc )
  393. {
  394. int numChunks;
  395. float size = 0, scale;
  396. vec3_t dir, up, dis;
  397. if (self->e_DieFunc == dieF_NULL) //i was probably already killed since my die func was removed
  398. {
  399. #ifndef FINAL_BUILD
  400. gi.Printf(S_COLOR_YELLOW"Recursive misc_model_breakable_die. Use targets probably pointing back at self.\n");
  401. #endif
  402. return; //this happens when you have a cyclic target chain!
  403. }
  404. //NOTE: Stop any scripts that are currently running (FLUSH)... ?
  405. //Turn off animation
  406. self->s.frame = self->startFrame = self->endFrame = 0;
  407. self->svFlags &= ~SVF_ANIMATING;
  408. self->health = 0;
  409. //Throw some chunks
  410. AngleVectors( self->s.apos.trBase, dir, NULL, NULL );
  411. VectorNormalize( dir );
  412. numChunks = random() * 6 + 20;
  413. VectorSubtract( self->absmax, self->absmin, dis );
  414. // This formula really has no logical basis other than the fact that it seemed to be the closest to yielding the results that I wanted.
  415. // Volume is length * width * height...then break that volume down based on how many chunks we have
  416. scale = sqrt( sqrt( dis[0] * dis[1] * dis[2] )) * 1.75f;
  417. if ( scale > 48 )
  418. {
  419. size = 2;
  420. }
  421. else if ( scale > 24 )
  422. {
  423. size = 1;
  424. }
  425. scale = scale / numChunks;
  426. if ( self->radius > 0.0f )
  427. {
  428. // designer wants to scale number of chunks, helpful because the above scale code is far from perfect
  429. // I do this after the scale calculation because it seems that the chunk size generally seems to be very close, it's just the number of chunks is a bit weak
  430. numChunks *= self->radius;
  431. }
  432. VectorAdd( self->absmax, self->absmin, dis );
  433. VectorScale( dis, 0.5f, dis );
  434. CG_Chunks( self->s.number, dis, dir, self->absmin, self->absmax, 300, numChunks, self->material, self->s.modelindex3, scale );
  435. self->e_PainFunc = painF_NULL;
  436. self->e_DieFunc = dieF_NULL;
  437. // self->e_UseFunc = useF_NULL;
  438. self->takedamage = qfalse;
  439. if ( !(self->spawnflags & 4) )
  440. {//We don't want to stay solid
  441. self->s.solid = 0;
  442. self->contents = 0;
  443. self->clipmask = 0;
  444. if (self!=0)
  445. {
  446. NAV::WayEdgesNowClear(self);
  447. }
  448. gi.linkentity(self);
  449. }
  450. VectorSet(up, 0, 0, 1);
  451. if(self->target)
  452. {
  453. G_UseTargets(self, attacker);
  454. }
  455. if(inflictor->client)
  456. {
  457. VectorSubtract( self->currentOrigin, inflictor->currentOrigin, dir );
  458. VectorNormalize( dir );
  459. }
  460. else
  461. {
  462. VectorCopy(up, dir);
  463. }
  464. if ( !(self->spawnflags & 2048) ) // NO_EXPLOSION
  465. {
  466. // Ok, we are allowed to explode, so do it now!
  467. if(self->splashDamage > 0 && self->splashRadius > 0)
  468. {//explode
  469. vec3_t org;
  470. AddSightEvent( attacker, self->currentOrigin, 256, AEL_DISCOVERED, 100 );
  471. AddSoundEvent( attacker, self->currentOrigin, 128, AEL_DISCOVERED, qfalse, qtrue );//FIXME: am I on ground or not?
  472. //FIXME: specify type of explosion? (barrel, electrical, etc.) Also, maybe just use the explosion effect below since it's
  473. // a bit better?
  474. // up the origin a little for the damage check, because several models have their origin on the ground, so they don't alwasy do damage, not the optimal solution...
  475. VectorCopy( self->currentOrigin, org );
  476. if ( self->mins[2] > -4 )
  477. {//origin is going to be below it or very very low in the model
  478. //center the origin
  479. org[2] = self->currentOrigin[2] + self->mins[2] + (self->maxs[2] - self->mins[2])/2.0f;
  480. }
  481. G_RadiusDamage( org, self, self->splashDamage, self->splashRadius, self, MOD_UNKNOWN );
  482. if ( self->model && ( Q_stricmp( "models/map_objects/ships/tie_fighter.md3", self->model ) == 0 ||
  483. Q_stricmp( "models/map_objects/ships/tie_bomber.md3", self->model ) == 0 ) )
  484. {//TEMP HACK for Tie Fighters- they're HUGE
  485. G_PlayEffect( "explosions/fighter_explosion2", self->currentOrigin );
  486. G_Sound( self, G_SoundIndex( "sound/weapons/tie_fighter/TIEexplode.wav" ) );
  487. self->s.loopSound = 0;
  488. }
  489. else
  490. {
  491. CG_MiscModelExplosion( self->absmin, self->absmax, size, self->material );
  492. G_Sound( self, G_SoundIndex("sound/weapons/explosions/cargoexplode.wav") );
  493. self->s.loopSound = 0;
  494. }
  495. }
  496. else
  497. {//just break
  498. AddSightEvent( attacker, self->currentOrigin, 128, AEL_DISCOVERED );
  499. AddSoundEvent( attacker, self->currentOrigin, 64, AEL_SUSPICIOUS, qfalse, qtrue );//FIXME: am I on ground or not?
  500. // This is the default explosion
  501. CG_MiscModelExplosion( self->absmin, self->absmax, size, self->material );
  502. G_Sound(self, G_SoundIndex("sound/weapons/explosions/cargoexplode.wav"));
  503. }
  504. }
  505. self->e_ThinkFunc = thinkF_NULL;
  506. self->nextthink = -1;
  507. if(self->s.modelindex2 != -1 && !(self->spawnflags & 8))
  508. {//FIXME: modelindex doesn't get set to -1 if the damage model doesn't exist
  509. self->svFlags |= SVF_BROKEN;
  510. self->s.modelindex = self->s.modelindex2;
  511. G_ActivateBehavior( self, BSET_DEATH );
  512. }
  513. else
  514. {
  515. G_FreeEntity( self );
  516. }
  517. }
  518. void misc_model_throw_at_target4( gentity_t *self, gentity_t *activator )
  519. {
  520. vec3_t pushDir, kvel;
  521. float knockback = 200;
  522. float mass = self->mass;
  523. gentity_t *target = G_Find( NULL, FOFS(targetname), self->target4 );
  524. if ( !target )
  525. {//nothing to throw ourselves at...
  526. return;
  527. }
  528. VectorSubtract( target->currentOrigin, self->currentOrigin, pushDir );
  529. knockback -= VectorNormalize( pushDir );
  530. if ( knockback < 100 )
  531. {
  532. knockback = 100;
  533. }
  534. VectorCopy( self->currentOrigin, self->s.pos.trBase );
  535. self->s.pos.trTime = level.time; // move a bit on the very first frame
  536. if ( self->s.pos.trType != TR_INTERPOLATE )
  537. {//don't do this to rolling missiles
  538. self->s.pos.trType = TR_GRAVITY;
  539. }
  540. if ( mass < 50 )
  541. {//???
  542. mass = 50;
  543. }
  544. if ( g_gravity->value > 0 )
  545. {
  546. VectorScale( pushDir, g_knockback->value * knockback / mass * 0.8, kvel );
  547. kvel[2] = pushDir[2] * g_knockback->value * knockback / mass * 1.5;
  548. }
  549. else
  550. {
  551. VectorScale( pushDir, g_knockback->value * knockback / mass, kvel );
  552. }
  553. VectorAdd( self->s.pos.trDelta, kvel, self->s.pos.trDelta );
  554. if ( g_gravity->value > 0 )
  555. {
  556. if ( self->s.pos.trDelta[2] < knockback )
  557. {
  558. self->s.pos.trDelta[2] = knockback;
  559. }
  560. }
  561. //no trDuration?
  562. if ( self->e_ThinkFunc != thinkF_G_RunObject )
  563. {//objects spin themselves?
  564. //spin it
  565. //FIXME: messing with roll ruins the rotational center???
  566. self->s.apos.trTime = level.time;
  567. self->s.apos.trType = TR_LINEAR;
  568. VectorClear( self->s.apos.trDelta );
  569. self->s.apos.trDelta[1] = Q_irand( -800, 800 );
  570. }
  571. self->forcePushTime = level.time + 600; // let the push effect last for 600 ms
  572. if ( activator )
  573. {
  574. self->forcePuller = activator->s.number;//remember this regardless
  575. }
  576. else
  577. {
  578. self->forcePuller = NULL;
  579. }
  580. }
  581. void misc_model_use (gentity_t *self, gentity_t *other, gentity_t *activator)
  582. {
  583. if ( self->target4 )
  584. {//throw me at my target!
  585. misc_model_throw_at_target4( self, activator );
  586. return;
  587. }
  588. if ( self->health <= 0 && self->max_health > 0)
  589. {//used while broken fired target3
  590. G_UseTargets2( self, activator, self->target3 );
  591. return;
  592. }
  593. // Become solid again.
  594. if ( !self->count )
  595. {
  596. self->count = 1;
  597. self->activator = activator;
  598. self->svFlags &= ~SVF_NOCLIENT;
  599. self->s.eFlags &= ~EF_NODRAW;
  600. }
  601. G_ActivateBehavior( self, BSET_USE );
  602. //Don't explode if they've requested it to not
  603. if ( self->spawnflags & 64 )
  604. {//Usemodels toggling
  605. if ( self->spawnflags & 32 )
  606. {
  607. if( self->s.modelindex == self->sound1to2 )
  608. {
  609. self->s.modelindex = self->sound2to1;
  610. }
  611. else
  612. {
  613. self->s.modelindex = self->sound1to2;
  614. }
  615. }
  616. return;
  617. }
  618. self->e_DieFunc = dieF_misc_model_breakable_die;
  619. misc_model_breakable_die( self, other, activator, self->health, MOD_UNKNOWN );
  620. }
  621. #define MDL_OTHER 0
  622. #define MDL_ARMOR_HEALTH 1
  623. #define MDL_AMMO 2
  624. void misc_model_breakable_init( gentity_t *ent )
  625. {
  626. int type;
  627. type = MDL_OTHER;
  628. if (!ent->model) {
  629. G_Error("no model set on %s at (%.1f %.1f %.1f)\n", ent->classname, ent->s.origin[0],ent->s.origin[1],ent->s.origin[2]);
  630. }
  631. //Main model
  632. ent->s.modelindex = ent->sound2to1 = G_ModelIndex( ent->model );
  633. if ( ent->spawnflags & 1 )
  634. {//Blocks movement
  635. ent->contents = CONTENTS_SOLID|CONTENTS_OPAQUE|CONTENTS_BODY|CONTENTS_MONSTERCLIP|CONTENTS_BOTCLIP;//Was CONTENTS_SOLID, but only architecture should be this
  636. }
  637. else if ( ent->health )
  638. {//Can only be shot
  639. ent->contents = CONTENTS_SHOTCLIP;
  640. }
  641. if (type == MDL_OTHER)
  642. {
  643. ent->e_UseFunc = useF_misc_model_use;
  644. }
  645. else if ( type == MDL_ARMOR_HEALTH )
  646. {
  647. // G_SoundIndex("sound/player/suithealth.wav");
  648. ent->e_UseFunc = useF_health_use;
  649. if (!ent->count)
  650. {
  651. ent->count = 100;
  652. }
  653. ent->health = 60;
  654. }
  655. else if ( type == MDL_AMMO )
  656. {
  657. // G_SoundIndex("sound/player/suitenergy.wav");
  658. ent->e_UseFunc = useF_ammo_use;
  659. if (!ent->count)
  660. {
  661. ent->count = 100;
  662. }
  663. ent->health = 60;
  664. }
  665. if ( ent->health )
  666. {
  667. G_SoundIndex("sound/weapons/explosions/cargoexplode.wav");
  668. ent->max_health = ent->health;
  669. ent->takedamage = qtrue;
  670. ent->e_PainFunc = painF_misc_model_breakable_pain;
  671. ent->e_DieFunc = dieF_misc_model_breakable_die;
  672. }
  673. }
  674. void TieFighterThink ( gentity_t *self )
  675. {
  676. gentity_t *player = &g_entities[0];
  677. if ( self->health <= 0 )
  678. {
  679. return;
  680. }
  681. self->nextthink = level.time + FRAMETIME;
  682. if ( player )
  683. {
  684. vec3_t playerDir, fighterDir, fwd, rt;
  685. float playerDist, fighterSpeed;
  686. //use player eyepoint
  687. VectorSubtract( player->currentOrigin, self->currentOrigin, playerDir );
  688. playerDist = VectorNormalize( playerDir );
  689. VectorSubtract( self->currentOrigin, self->lastOrigin, fighterDir );
  690. VectorCopy( self->currentOrigin, self->lastOrigin );
  691. fighterSpeed = VectorNormalize( fighterDir )*1000;
  692. AngleVectors( self->currentAngles, fwd, rt, NULL );
  693. if ( fighterSpeed )
  694. {
  695. float side;
  696. // Magic number fun! Speed is used for banking, so modulate the speed by a sine wave
  697. fighterSpeed *= sin( ( 100 ) * 0.003 );
  698. // Clamp to prevent harsh rolling
  699. if ( fighterSpeed > 10 )
  700. fighterSpeed = 10;
  701. side = fighterSpeed * DotProduct( fighterDir, rt );
  702. self->s.apos.trBase[2] -= side;
  703. }
  704. //FIXME: bob up/down, strafe left/right some
  705. float dot = DotProduct( playerDir, fighterDir );
  706. if ( dot > 0 )
  707. {//heading toward the player
  708. if ( playerDist < 1024 )
  709. {
  710. if ( DotProduct( playerDir, fwd ) > 0.7 )
  711. {//facing the player
  712. if ( self->attackDebounceTime < level.time )
  713. {
  714. gentity_t *bolt;
  715. bolt = G_Spawn();
  716. bolt->classname = "tie_proj";
  717. bolt->nextthink = level.time + 10000;
  718. bolt->e_ThinkFunc = thinkF_G_FreeEntity;
  719. bolt->s.eType = ET_MISSILE;
  720. bolt->s.weapon = WP_BLASTER;
  721. bolt->owner = self;
  722. bolt->damage = 30;
  723. bolt->dflags = DAMAGE_NO_KNOCKBACK; // Don't push them around, or else we are constantly re-aiming
  724. bolt->splashDamage = 0;
  725. bolt->splashRadius = 0;
  726. bolt->methodOfDeath = MOD_ENERGY; // ?
  727. bolt->clipmask = MASK_SHOT;
  728. bolt->s.pos.trType = TR_LINEAR;
  729. bolt->s.pos.trTime = level.time; // move a bit on the very first frame
  730. VectorCopy( self->currentOrigin, bolt->s.pos.trBase );
  731. VectorScale( fwd, 8000, bolt->s.pos.trDelta );
  732. SnapVector( bolt->s.pos.trDelta ); // save net bandwidth
  733. VectorCopy( self->currentOrigin, bolt->currentOrigin);
  734. if ( !Q_irand( 0, 2 ) )
  735. {
  736. G_SoundOnEnt( bolt, CHAN_VOICE, "sound/weapons/tie_fighter/tie_fire.wav" );
  737. }
  738. else
  739. {
  740. G_SoundOnEnt( bolt, CHAN_VOICE, va( "sound/weapons/tie_fighter/tie_fire%d.wav", Q_irand( 2, 3 ) ) );
  741. }
  742. self->attackDebounceTime = level.time + Q_irand( 300, 2000 );
  743. }
  744. }
  745. }
  746. }
  747. if ( playerDist < 1024 )//512 )
  748. {//within range to start our sound
  749. if ( dot > 0 )
  750. {
  751. if ( !self->fly_sound_debounce_time )
  752. {//start sound
  753. G_SoundOnEnt( self, CHAN_VOICE, va( "sound/weapons/tie_fighter/tiepass%d.wav", Q_irand( 1, 5 ) ) );
  754. self->fly_sound_debounce_time = 2000;
  755. }
  756. else
  757. {//sound already started
  758. self->fly_sound_debounce_time = -1;
  759. }
  760. }
  761. }
  762. else if ( self->fly_sound_debounce_time < level.time )
  763. {
  764. self->fly_sound_debounce_time = 0;
  765. }
  766. }
  767. }
  768. void TieFighterUse( gentity_t *self, gentity_t *other, gentity_t *activator )
  769. {
  770. if ( !self || !other || !activator )
  771. return;
  772. vec3_t fwd, rt;
  773. AngleVectors( self->currentAngles, fwd, rt, NULL );
  774. gentity_t *bolt;
  775. bolt = G_Spawn();
  776. bolt->classname = "tie_proj";
  777. bolt->nextthink = level.time + 10000;
  778. bolt->e_ThinkFunc = thinkF_G_FreeEntity;
  779. bolt->s.eType = ET_MISSILE;
  780. bolt->s.weapon = WP_TIE_FIGHTER;
  781. bolt->owner = self;
  782. bolt->damage = 30;
  783. bolt->dflags = DAMAGE_NO_KNOCKBACK; // Don't push them around, or else we are constantly re-aiming
  784. bolt->splashDamage = 0;
  785. bolt->splashRadius = 0;
  786. bolt->methodOfDeath = MOD_ENERGY; // ?
  787. bolt->clipmask = MASK_SHOT;
  788. bolt->s.pos.trType = TR_LINEAR;
  789. bolt->s.pos.trTime = level.time; // move a bit on the very first frame
  790. VectorCopy( self->currentOrigin, bolt->s.pos.trBase );
  791. rt[2] += 2.0f;
  792. VectorMA( bolt->s.pos.trBase, -15.0, rt, bolt->s.pos.trBase );
  793. VectorScale( fwd, 3000, bolt->s.pos.trDelta );
  794. SnapVector( bolt->s.pos.trDelta ); // save net bandwidth
  795. VectorCopy( self->currentOrigin, bolt->currentOrigin);
  796. bolt = G_Spawn();
  797. bolt->classname = "tie_proj";
  798. bolt->nextthink = level.time + 10000;
  799. bolt->e_ThinkFunc = thinkF_G_FreeEntity;
  800. bolt->s.eType = ET_MISSILE;
  801. bolt->s.weapon = WP_TIE_FIGHTER;
  802. bolt->owner = self;
  803. bolt->damage = 30;
  804. bolt->dflags = DAMAGE_NO_KNOCKBACK; // Don't push them around, or else we are constantly re-aiming
  805. bolt->splashDamage = 0;
  806. bolt->splashRadius = 0;
  807. bolt->methodOfDeath = MOD_ENERGY; // ?
  808. bolt->clipmask = MASK_SHOT;
  809. bolt->s.pos.trType = TR_LINEAR;
  810. bolt->s.pos.trTime = level.time; // move a bit on the very first frame
  811. VectorCopy( self->currentOrigin, bolt->s.pos.trBase );
  812. rt[2] -= 4.0f;
  813. VectorMA( bolt->s.pos.trBase, 15.0, rt, bolt->s.pos.trBase );
  814. VectorScale( fwd, 3000, bolt->s.pos.trDelta );
  815. SnapVector( bolt->s.pos.trDelta ); // save net bandwidth
  816. VectorCopy( self->currentOrigin, bolt->currentOrigin);
  817. }
  818. void TouchTieBomb( gentity_t *self, gentity_t *other, trace_t *trace )
  819. {
  820. // Stop the effect.
  821. G_StopEffect( G_EffectIndex( "ships/tiebomber_bomb_falling" ), self->playerModel, gi.G2API_AddBolt( &self->ghoul2[0], "model_root" ), self->s.number );
  822. self->e_ThinkFunc = thinkF_G_FreeEntity;
  823. self->nextthink = level.time + FRAMETIME;
  824. G_PlayEffect( G_EffectIndex( "ships/tiebomber_explosion2" ), self->currentOrigin, self->currentAngles );
  825. G_RadiusDamage( self->currentOrigin, self, 900, 500, self, MOD_EXPLOSIVE_SPLASH );
  826. }
  827. #define MIN_PLAYER_DIST 1600
  828. void TieBomberThink( gentity_t *self )
  829. {
  830. // NOTE: Lerp2Angles will think this thinkfunc if the model is a misc_model_breakable. Watchout
  831. // for that in a script (try to just use ROFF's?).
  832. // Stop thinking, you're dead.
  833. if ( self->health <= 0 )
  834. {
  835. return;
  836. }
  837. // Needed every think...
  838. self->nextthink = level.time + FRAMETIME;
  839. gentity_t *player = &g_entities[0];
  840. vec3_t playerDir;
  841. float playerDist;
  842. //use player eyepoint
  843. VectorSubtract( player->currentOrigin, self->currentOrigin, playerDir );
  844. playerDist = VectorNormalize( playerDir );
  845. // Time to attack?
  846. if ( player->health > 0 && playerDist < MIN_PLAYER_DIST && self->attackDebounceTime < level.time )
  847. {
  848. char name1[200] = "models/players/remote/model.glm";
  849. gentity_t *bomb = G_CreateObject( self, self->s.pos.trBase, self->s.apos.trBase, 0, 0, TR_GRAVITY, 0 );
  850. bomb->s.modelindex = G_ModelIndex( name1 );
  851. gi.G2API_InitGhoul2Model( bomb->ghoul2, name1, bomb->s.modelindex);
  852. bomb->s.radius = 50;
  853. bomb->s.eFlags |= EF_NODRAW;
  854. // Make the bombs go forward in the bombers direction a little.
  855. vec3_t fwd, rt;
  856. AngleVectors( self->currentAngles, fwd, rt, NULL );
  857. rt[2] -= 0.5f;
  858. VectorMA( bomb->s.pos.trBase, -30.0, rt, bomb->s.pos.trBase );
  859. VectorScale( fwd, 300, bomb->s.pos.trDelta );
  860. SnapVector( bomb->s.pos.trDelta ); // save net bandwidth
  861. // Start the effect.
  862. G_PlayEffect( G_EffectIndex( "ships/tiebomber_bomb_falling" ), bomb->playerModel, gi.G2API_AddBolt( &bomb->ghoul2[0], "model_root" ), bomb->s.number, bomb->currentOrigin, 1000, qtrue );
  863. // Set the tie bomb to have a touch function, so when it hits the ground (or whatever), there's a nice 'boom'.
  864. bomb->e_TouchFunc = touchF_TouchTieBomb;
  865. self->attackDebounceTime = level.time + 1000;
  866. }
  867. }
  868. void misc_model_breakable_gravity_init( gentity_t *ent, qboolean dropToFloor )
  869. {
  870. //G_SoundIndex( "sound/movers/objects/objectHurt.wav" );
  871. G_EffectIndex( "melee/kick_impact" );
  872. G_EffectIndex( "melee/kick_impact_silent" );
  873. //G_SoundIndex( "sound/weapons/melee/punch1.mp3" );
  874. //G_SoundIndex( "sound/weapons/melee/punch2.mp3" );
  875. //G_SoundIndex( "sound/weapons/melee/punch3.mp3" );
  876. //G_SoundIndex( "sound/weapons/melee/punch4.mp3" );
  877. G_SoundIndex( "sound/movers/objects/objectHit.wav" );
  878. G_SoundIndex( "sound/movers/objects/objectHitHeavy.wav" );
  879. G_SoundIndex( "sound/movers/objects/objectBreak.wav" );
  880. //FIXME: dust impact effect when hits ground?
  881. ent->s.eType = ET_GENERAL;
  882. ent->s.eFlags |= EF_BOUNCE_HALF;
  883. ent->clipmask = MASK_SOLID|CONTENTS_BODY|CONTENTS_MONSTERCLIP|CONTENTS_BOTCLIP;//?
  884. if ( !ent->mass )
  885. {//not overridden by designer
  886. ent->mass = VectorLength( ent->maxs ) + VectorLength( ent->mins );
  887. }
  888. ent->physicsBounce = ent->mass;
  889. //drop to floor
  890. trace_t tr;
  891. vec3_t top, bottom;
  892. if ( dropToFloor )
  893. {
  894. VectorCopy( ent->currentOrigin, top );
  895. top[2] += 1;
  896. VectorCopy( ent->currentOrigin, bottom );
  897. bottom[2] = MIN_WORLD_COORD;
  898. gi.trace( &tr, top, ent->mins, ent->maxs, bottom, ent->s.number, MASK_NPCSOLID );
  899. if ( !tr.allsolid && !tr.startsolid && tr.fraction < 1.0 )
  900. {
  901. G_SetOrigin( ent, tr.endpos );
  902. gi.linkentity( ent );
  903. }
  904. }
  905. else
  906. {
  907. G_SetOrigin( ent, ent->currentOrigin );
  908. gi.linkentity( ent );
  909. }
  910. //set up for object thinking
  911. if ( VectorCompare( ent->s.pos.trDelta, vec3_origin ) )
  912. {//not moving
  913. ent->s.pos.trType = TR_STATIONARY;
  914. }
  915. else
  916. {
  917. ent->s.pos.trType = TR_GRAVITY;
  918. }
  919. VectorCopy( ent->currentOrigin, ent->s.pos.trBase );
  920. VectorClear( ent->s.pos.trDelta );
  921. ent->s.pos.trTime = level.time;
  922. if ( VectorCompare( ent->s.apos.trDelta, vec3_origin ) )
  923. {//not moving
  924. ent->s.apos.trType = TR_STATIONARY;
  925. }
  926. else
  927. {
  928. ent->s.apos.trType = TR_LINEAR;
  929. }
  930. VectorCopy( ent->currentAngles, ent->s.apos.trBase );
  931. VectorClear( ent->s.apos.trDelta );
  932. ent->s.apos.trTime = level.time;
  933. ent->nextthink = level.time + FRAMETIME;
  934. ent->e_ThinkFunc = thinkF_G_RunObject;
  935. }
  936. /*QUAKED misc_model_breakable (1 0 0) (-16 -16 -16) (16 16 16) SOLID AUTOANIMATE DEADSOLID NO_DMODEL NO_SMOKE USE_MODEL USE_NOT_BREAK PLAYER_USE NO_EXPLOSION START_OFF
  937. SOLID - Movement is blocked by it, if not set, can still be broken by explosions and shots if it has health
  938. AUTOANIMATE - Will cycle it's anim
  939. DEADSOLID - Stay solid even when destroyed (in case damage model is rather large).
  940. NO_DMODEL - Makes it NOT display a damage model when destroyed, even if one exists
  941. USE_MODEL - When used, will toggle to it's usemodel (model + "_u1.md3")... this obviously does nothing if USE_NOT_BREAK is not checked
  942. USE_NOT_BREAK - Using it, doesn't make it break, still can be destroyed by damage
  943. PLAYER_USE - Player can use it with the use button
  944. NO_EXPLOSION - By default, will explode when it dies...this is your override.
  945. START_OFF - Will start off and will not appear until used.
  946. "model" arbitrary .md3 file to display
  947. "modelscale" "x" uniform scale
  948. "modelscale_vec" "x y z" scale model in each axis - height, width and length - bbox will scale with it
  949. "health" how much health to have - default is zero (not breakable) If you don't set the SOLID flag, but give it health, it can be shot but will not block NPCs or players from moving
  950. "targetname" when used, dies and displays damagemodel (model + "_d1.md3"), if any (if not, removes itself)
  951. "target" What to use when it dies
  952. "target2" What to use when it's repaired
  953. "target3" What to use when it's used while it's broken
  954. "paintarget" target to fire when hit (but not destroyed)
  955. "count" the amount of armor/health/ammo given (default 50)
  956. "radius" Chunk code tries to pick a good volume of chunks, but you can alter this to scale the number of spawned chunks. (default 1) (.5) is half as many chunks, (2) is twice as many chunks
  957. "NPC_targetname" - Only the NPC with this name can damage this
  958. "forcevisible" - When you turn on force sight (any level), you can see these draw through the entire level...
  959. "redCrosshair" - crosshair turns red when you look at this
  960. "gravity" if set to 1, this will be affected by gravity
  961. "throwtarget" if set (along with gravity), this thing, when used, will throw itself at the entity whose targetname matches this string
  962. "mass" if gravity is on, this determines how much damage this thing does when it hits someone. Default is the size of the object from one corner to the other, that works very well. Only override if this is an object whose mass should be very high or low for it's size (density)
  963. Damage: default is none
  964. "splashDamage" - damage to do (will make it explode on death)
  965. "splashRadius" - radius for above damage
  966. "team" - This cannot take damage from members of this team:
  967. "player"
  968. "neutral"
  969. "enemy"
  970. "material" - default is "8 - MAT_NONE" - choose from this list:
  971. 0 = MAT_METAL (grey metal)
  972. 1 = MAT_GLASS
  973. 2 = MAT_ELECTRICAL (sparks only)
  974. 3 = MAT_ELEC_METAL (METAL chunks and sparks)
  975. 4 = MAT_DRK_STONE (brown stone chunks)
  976. 5 = MAT_LT_STONE (tan stone chunks)
  977. 6 = MAT_GLASS_METAL (glass and METAL chunks)
  978. 7 = MAT_METAL2 (blue/grey metal)
  979. 8 = MAT_NONE (no chunks-DEFAULT)
  980. 9 = MAT_GREY_STONE (grey colored stone)
  981. 10 = MAT_METAL3 (METAL and METAL2 chunk combo)
  982. 11 = MAT_CRATE1 (yellow multi-colored crate chunks)
  983. 12 = MAT_GRATE1 (grate chunks--looks horrible right now)
  984. 13 = MAT_ROPE (for yavin_trial, no chunks, just wispy bits )
  985. 14 = MAT_CRATE2 (red multi-colored crate chunks)
  986. 15 = MAT_WHITE_METAL (white angular chunks for Stu, NS_hideout )
  987. */
  988. void SP_misc_model_breakable( gentity_t *ent )
  989. {
  990. char damageModel[MAX_QPATH];
  991. char chunkModel[MAX_QPATH];
  992. char useModel[MAX_QPATH];
  993. int len;
  994. // Chris F. requested default for misc_model_breakable to be NONE...so don't arbitrarily change this.
  995. G_SpawnInt( "material", "8", (int*)&ent->material );
  996. G_SpawnFloat( "radius", "1", &ent->radius ); // used to scale chunk code if desired by a designer
  997. qboolean bHasScale = G_SpawnVector("modelscale_vec", "0 0 0", ent->s.modelScale);
  998. if (!bHasScale)
  999. {
  1000. float temp;
  1001. G_SpawnFloat( "modelscale", "0", &temp);
  1002. if (temp != 0.0f)
  1003. {
  1004. ent->s.modelScale[ 0 ] = ent->s.modelScale[ 1 ] = ent->s.modelScale[ 2 ] = temp;
  1005. bHasScale = qtrue;
  1006. }
  1007. }
  1008. CacheChunkEffects( ent->material );
  1009. misc_model_breakable_init( ent );
  1010. len = strlen( ent->model ) - 4;
  1011. assert(ent->model[len]=='.');//we're expecting ".md3"
  1012. strncpy( damageModel, ent->model, sizeof(damageModel) );
  1013. damageModel[len] = 0; //chop extension
  1014. strncpy( chunkModel, damageModel, sizeof(chunkModel));
  1015. strncpy( useModel, damageModel, sizeof(useModel));
  1016. if (ent->takedamage) {
  1017. //Dead/damaged model
  1018. if( !(ent->spawnflags & 8) ) { //no dmodel
  1019. strcat( damageModel, "_d1.md3" );
  1020. ent->s.modelindex2 = G_ModelIndex( damageModel );
  1021. }
  1022. //Chunk model
  1023. strcat( chunkModel, "_c1.md3" );
  1024. ent->s.modelindex3 = G_ModelIndex( chunkModel );
  1025. }
  1026. //Use model
  1027. if( ent->spawnflags & 32 ) { //has umodel
  1028. strcat( useModel, "_u1.md3" );
  1029. ent->sound1to2 = G_ModelIndex( useModel );
  1030. }
  1031. if ( !ent->mins[0] && !ent->mins[1] && !ent->mins[2] )
  1032. {
  1033. VectorSet (ent->mins, -16, -16, -16);
  1034. }
  1035. if ( !ent->maxs[0] && !ent->maxs[1] && !ent->maxs[2] )
  1036. {
  1037. VectorSet (ent->maxs, 16, 16, 16);
  1038. }
  1039. // Scale up the tie-bomber bbox a little.
  1040. if ( ent->model && Q_stricmp( "models/map_objects/ships/tie_bomber.md3", ent->model ) == 0 )
  1041. {
  1042. VectorSet (ent->mins, -80, -80, -80);
  1043. VectorSet (ent->maxs, 80, 80, 80);
  1044. //ent->s.modelScale[ 0 ] = ent->s.modelScale[ 1 ] = ent->s.modelScale[ 2 ] *= 2.0f;
  1045. //bHasScale = qtrue;
  1046. }
  1047. if (bHasScale)
  1048. {
  1049. //scale the x axis of the bbox up.
  1050. ent->maxs[0] *= ent->s.modelScale[0];//*scaleFactor;
  1051. ent->mins[0] *= ent->s.modelScale[0];//*scaleFactor;
  1052. //scale the y axis of the bbox up.
  1053. ent->maxs[1] *= ent->s.modelScale[1];//*scaleFactor;
  1054. ent->mins[1] *= ent->s.modelScale[1];//*scaleFactor;
  1055. //scale the z axis of the bbox up and adjust origin accordingly
  1056. ent->maxs[2] *= ent->s.modelScale[2];
  1057. float oldMins2 = ent->mins[2];
  1058. ent->mins[2] *= ent->s.modelScale[2];
  1059. ent->s.origin[2] += (oldMins2-ent->mins[2]);
  1060. }
  1061. if ( ent->spawnflags & 2 )
  1062. {
  1063. ent->s.eFlags |= EF_ANIM_ALLFAST;
  1064. }
  1065. G_SetOrigin( ent, ent->s.origin );
  1066. G_SetAngles( ent, ent->s.angles );
  1067. gi.linkentity (ent);
  1068. if ( ent->spawnflags & 128 )
  1069. {//Can be used by the player's BUTTON_USE
  1070. ent->svFlags |= SVF_PLAYER_USABLE;
  1071. }
  1072. if ( ent->team && ent->team[0] )
  1073. {
  1074. ent->noDamageTeam = (team_t)GetIDForString( TeamTable, ent->team );
  1075. if ( ent->noDamageTeam == TEAM_FREE )
  1076. {
  1077. G_Error("team name %s not recognized\n", ent->team);
  1078. }
  1079. }
  1080. ent->team = NULL;
  1081. //HACK
  1082. if ( ent->model && Q_stricmp( "models/map_objects/ships/x_wing_nogear.md3", ent->model ) == 0 )
  1083. {
  1084. if( ent->splashDamage > 0 && ent->splashRadius > 0 )
  1085. {
  1086. ent->s.loopSound = G_SoundIndex( "sound/vehicles/x-wing/loop.wav" );
  1087. ent->s.eFlags |= EF_LESS_ATTEN;
  1088. }
  1089. }
  1090. else if ( ent->model && Q_stricmp( "models/map_objects/ships/tie_fighter.md3", ent->model ) == 0 )
  1091. {//run a think
  1092. G_EffectIndex( "explosions/fighter_explosion2" );
  1093. G_SoundIndex( "sound/weapons/tie_fighter/tiepass1.wav" );
  1094. /* G_SoundIndex( "sound/weapons/tie_fighter/tiepass2.wav" );
  1095. G_SoundIndex( "sound/weapons/tie_fighter/tiepass3.wav" );
  1096. G_SoundIndex( "sound/weapons/tie_fighter/tiepass4.wav" );
  1097. G_SoundIndex( "sound/weapons/tie_fighter/tiepass5.wav" );*/
  1098. G_SoundIndex( "sound/weapons/tie_fighter/tie_fire.wav" );
  1099. /* G_SoundIndex( "sound/weapons/tie_fighter/tie_fire2.wav" );
  1100. G_SoundIndex( "sound/weapons/tie_fighter/tie_fire3.wav" );*/
  1101. G_SoundIndex( "sound/weapons/tie_fighter/TIEexplode.wav" );
  1102. RegisterItem( FindItemForWeapon( WP_TIE_FIGHTER ));
  1103. ent->s.eFlags |= EF_LESS_ATTEN;
  1104. if( ent->splashDamage > 0 && ent->splashRadius > 0 )
  1105. {
  1106. ent->s.loopSound = G_SoundIndex( "sound/vehicles/tie-bomber/loop.wav" );
  1107. //ent->e_ThinkFunc = thinkF_TieFighterThink;
  1108. //ent->e_UseFunc = thinkF_TieFighterThink;
  1109. //ent->nextthink = level.time + FRAMETIME;
  1110. ent->e_UseFunc = useF_TieFighterUse;
  1111. // Yeah, I could have just made this value changable from the editor, but I
  1112. // need it immediately!
  1113. float light;
  1114. vec3_t color;
  1115. qboolean lightSet, colorSet;
  1116. // if the "color" or "light" keys are set, setup constantLight
  1117. lightSet = qtrue;//G_SpawnFloat( "light", "100", &light );
  1118. light = 255;
  1119. //colorSet = "1 1 1"//G_SpawnVector( "color", "1 1 1", color );
  1120. colorSet = qtrue;
  1121. color[0] = 1; color[1] = 1; color[2] = 1;
  1122. if ( lightSet || colorSet )
  1123. {
  1124. int r, g, b, i;
  1125. r = color[0] * 255;
  1126. if ( r > 255 ) {
  1127. r = 255;
  1128. }
  1129. g = color[1] * 255;
  1130. if ( g > 255 ) {
  1131. g = 255;
  1132. }
  1133. b = color[2] * 255;
  1134. if ( b > 255 ) {
  1135. b = 255;
  1136. }
  1137. i = light / 4;
  1138. if ( i > 255 ) {
  1139. i = 255;
  1140. }
  1141. ent->s.constantLight = r | ( g << 8 ) | ( b << 16 ) | ( i << 24 );
  1142. }
  1143. }
  1144. }
  1145. else if ( ent->model && Q_stricmp( "models/map_objects/ships/tie_bomber.md3", ent->model ) == 0 )
  1146. {
  1147. G_EffectIndex( "ships/tiebomber_bomb_falling" );
  1148. G_EffectIndex( "ships/tiebomber_explosion2" );
  1149. G_EffectIndex( "explosions/fighter_explosion2" );
  1150. G_SoundIndex( "sound/weapons/tie_fighter/TIEexplode.wav" );
  1151. ent->e_ThinkFunc = thinkF_TieBomberThink;
  1152. ent->nextthink = level.time + FRAMETIME;
  1153. ent->attackDebounceTime = level.time + 1000;
  1154. // We only take damage from a heavy weapon class missiles.
  1155. ent->flags |= FL_DMG_BY_HEAVY_WEAP_ONLY;
  1156. ent->s.loopSound = G_SoundIndex( "sound/vehicles/tie-bomber/loop.wav" );
  1157. ent->s.eFlags |= EF_LESS_ATTEN;
  1158. }
  1159. float grav = 0;
  1160. G_SpawnFloat( "gravity", "0", &grav );
  1161. if ( grav )
  1162. {//affected by gravity
  1163. G_SetAngles( ent, ent->s.angles );
  1164. G_SetOrigin( ent, ent->currentOrigin );
  1165. G_SpawnString( "throwtarget", NULL, &ent->target4 ); // used to throw itself at something
  1166. misc_model_breakable_gravity_init( ent, qtrue );
  1167. }
  1168. // Start off.
  1169. if ( ent->spawnflags & 4096 )
  1170. {
  1171. ent->spawnContents = ent->contents; // It Navs can temporarly turn it "on"
  1172. ent->s.solid = 0;
  1173. ent->contents = 0;
  1174. ent->clipmask = 0;
  1175. ent->svFlags |= SVF_NOCLIENT;
  1176. ent->s.eFlags |= EF_NODRAW;
  1177. ent->count = 0;
  1178. }
  1179. int forceVisible = 0;
  1180. G_SpawnInt( "forcevisible", "0", &forceVisible );
  1181. if ( forceVisible )
  1182. {//can see these through walls with force sight, so must be broadcast
  1183. //ent->svFlags |= SVF_BROADCAST;
  1184. ent->s.eFlags |= EF_FORCE_VISIBLE;
  1185. }
  1186. int redCrosshair = 0;
  1187. G_SpawnInt( "redCrosshair", "0", &redCrosshair );
  1188. if ( redCrosshair )
  1189. {//can see these through walls with force sight, so must be broadcast
  1190. ent->flags |= FL_RED_CROSSHAIR;
  1191. }
  1192. }
  1193. //----------------------------------
  1194. //
  1195. // Breaking Glass Technology
  1196. //
  1197. //----------------------------------
  1198. // Really naughty cheating. Put in an EVENT at some point...
  1199. extern void cgi_R_GetBModelVerts(int bmodelIndex, vec3_t *verts, vec3_t normal );
  1200. extern void CG_DoGlass( vec3_t verts[4], vec3_t normal, vec3_t dmgPt, vec3_t dmgDir, float dmgRadius );
  1201. extern cgs_t cgs;
  1202. //-----------------------------------------------------
  1203. void funcGlassDie( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod,int dFlags,int hitLoc )
  1204. {
  1205. vec3_t verts[4], normal;
  1206. // if a missile is stuck to us, blow it up so we don't look dumb....we could, alternately, just let the missile drop off??
  1207. for ( int i = 0; i < MAX_GENTITIES; i++ )
  1208. {
  1209. if ( g_entities[i].s.groundEntityNum == self->s.number && ( g_entities[i].s.eFlags & EF_MISSILE_STICK ))
  1210. {
  1211. G_Damage( &g_entities[i], self, self, NULL, NULL, 99999, 0, MOD_CRUSH ); //?? MOD?
  1212. }
  1213. }
  1214. // Really naughty cheating. Put in an EVENT at some point...
  1215. cgi_R_GetBModelVerts( cgs.inlineDrawModel[self->s.modelindex], verts, normal );
  1216. CG_DoGlass( verts, normal, self->pos1, self->pos2, self->splashRadius );
  1217. self->takedamage = qfalse;//stop chain reaction runaway loops
  1218. G_SetEnemy( self, self->enemy );
  1219. //NOTE: MUST do this BEFORE clearing contents, or you may not open the area portal!!!
  1220. gi.AdjustAreaPortalState( self, qtrue );
  1221. //So chunks don't get stuck inside me
  1222. self->s.solid = 0;
  1223. self->contents = 0;
  1224. self->clipmask = 0;
  1225. gi.linkentity(self);
  1226. if ( self->target && attacker != NULL )
  1227. {
  1228. G_UseTargets( self, attacker );
  1229. }
  1230. G_FreeEntity( self );
  1231. }
  1232. //-----------------------------------------------------
  1233. void funcGlassUse( gentity_t *self, gentity_t *other, gentity_t *activator )
  1234. {
  1235. vec3_t temp1, temp2;
  1236. // For now, we just break on use
  1237. G_ActivateBehavior( self, BSET_USE );
  1238. VectorAdd( self->mins, self->maxs, temp1 );
  1239. VectorScale( temp1, 0.5f, temp1 );
  1240. VectorAdd( other->mins, other->maxs, temp2 );
  1241. VectorScale( temp2, 0.5f, temp2 );
  1242. VectorSubtract( temp1, temp2, self->pos2 );
  1243. VectorCopy( temp1, self->pos1 );
  1244. VectorNormalize( self->pos2 );
  1245. VectorScale( self->pos2, 390, self->pos2 );
  1246. self->splashRadius = 40; // ?? some random number, maybe it's ok?
  1247. funcGlassDie( self, other, activator, self->health, MOD_UNKNOWN );
  1248. }
  1249. //-----------------------------------------------------
  1250. /*QUAKED func_glass (0 .8 .5) ? INVINCIBLE
  1251. When destroyed, fires it's target, breaks into glass chunks and plays glass noise
  1252. For now, instantly breaks on impact
  1253. INVINCIBLE - can only be broken by being used
  1254. "targetname" entities with matching target will fire it
  1255. "target" all entities with a matching targetname will be used when this is destroyed
  1256. "health" default is 1
  1257. */
  1258. //-----------------------------------------------------
  1259. void SP_func_glass( gentity_t *self )
  1260. {
  1261. if ( !(self->spawnflags & 1 ))
  1262. {
  1263. if ( !self->health )
  1264. {
  1265. self->health = 1;
  1266. }
  1267. }
  1268. if ( self->health )
  1269. {
  1270. self->takedamage = qtrue;
  1271. }
  1272. self->e_UseFunc = useF_funcGlassUse;
  1273. self->e_DieFunc = dieF_funcGlassDie;
  1274. VectorCopy( self->s.origin, self->pos1 );
  1275. gi.SetBrushModel( self, self->model );
  1276. self->svFlags |= (SVF_GLASS_BRUSH|SVF_BBRUSH);
  1277. self->material = MAT_GLASS;
  1278. self->s.eType = ET_MOVER;
  1279. self->s.pos.trType = TR_STATIONARY;
  1280. VectorCopy( self->pos1, self->s.pos.trBase );
  1281. G_SoundIndex( "sound/effects/glassbreak1.wav" );
  1282. G_EffectIndex( "misc/glass_impact" );
  1283. gi.linkentity( self );
  1284. }
  1285. qboolean G_EntIsBreakable( int entityNum, gentity_t *breaker )
  1286. {//breakable brush/model that can actually be broken
  1287. if ( entityNum < 0 || entityNum >= ENTITYNUM_WORLD )
  1288. {
  1289. return qfalse;
  1290. }
  1291. gentity_t *ent = &g_entities[entityNum];
  1292. if ( !ent->takedamage )
  1293. {
  1294. return qfalse;
  1295. }
  1296. if ( ent->NPC_targetname )
  1297. {//only a specific entity can break this!
  1298. if ( !breaker
  1299. || !breaker->targetname
  1300. || Q_stricmp( ent->NPC_targetname, breaker->targetname ) != 0 )
  1301. {//I'm not the one who can break it
  1302. return qfalse;
  1303. }
  1304. }
  1305. if ( (ent->svFlags&SVF_GLASS_BRUSH) )
  1306. {
  1307. return qtrue;
  1308. }
  1309. if ( (ent->svFlags&SVF_BBRUSH) )
  1310. {
  1311. return qtrue;
  1312. }
  1313. if ( !Q_stricmp( "misc_model_breakable", ent->classname ) )
  1314. {
  1315. return qtrue;
  1316. }
  1317. if ( !Q_stricmp( "misc_maglock", ent->classname ) )
  1318. {
  1319. return qtrue;
  1320. }
  1321. return qfalse;
  1322. }