g_target.cpp 33 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240
  1. // leave this line at the top for all g_xxxx.cpp files...
  2. #include "g_headers.h"
  3. #include "Q3_Interface.h"
  4. #include "g_local.h"
  5. #include "g_functions.h"
  6. extern void G_SetEnemy( gentity_t *self, gentity_t *enemy );
  7. //==========================================================
  8. /*QUAKED target_give (1 0 0) (-8 -8 -8) (8 8 8)
  9. Gives the activator all the items pointed to.
  10. */
  11. void Use_Target_Give( gentity_t *ent, gentity_t *other, gentity_t *activator ) {
  12. gentity_t *t;
  13. trace_t trace;
  14. if ( !activator->client ) {
  15. return;
  16. }
  17. if ( !ent->target ) {
  18. return;
  19. }
  20. G_ActivateBehavior(ent,BSET_USE);
  21. memset( &trace, 0, sizeof( trace ) );
  22. t = NULL;
  23. while ( (t = G_Find (t, FOFS(targetname), ent->target)) != NULL ) {
  24. if ( !t->item ) {
  25. continue;
  26. }
  27. Touch_Item( t, activator, &trace );
  28. // make sure it isn't going to respawn or show any events
  29. t->nextthink = 0;
  30. gi.unlinkentity( t );
  31. }
  32. }
  33. void SP_target_give( gentity_t *ent ) {
  34. ent->e_UseFunc = useF_Use_Target_Give;
  35. }
  36. //==========================================================
  37. /*QUAKED target_delay (1 0 0) (-8 -8 -8) (8 8 8)
  38. "wait" seconds to pause before firing targets.
  39. "random" delay variance, total delay = delay +/- random seconds
  40. */
  41. void Think_Target_Delay( gentity_t *ent )
  42. {
  43. G_UseTargets( ent, ent->activator );
  44. }
  45. void Use_Target_Delay( gentity_t *ent, gentity_t *other, gentity_t *activator )
  46. {
  47. G_ActivateBehavior(ent,BSET_USE);
  48. ent->nextthink = level.time + ( ent->wait + ent->random * crandom() ) * 1000;
  49. ent->e_ThinkFunc = thinkF_Think_Target_Delay;
  50. ent->activator = activator;
  51. }
  52. void SP_target_delay( gentity_t *ent )
  53. {
  54. // check delay for backwards compatability
  55. if ( !G_SpawnFloat( "delay", "0", &ent->wait ) )
  56. {
  57. G_SpawnFloat( "wait", "1", &ent->wait );
  58. }
  59. if ( !ent->wait )
  60. {
  61. ent->wait = 1;
  62. }
  63. ent->e_UseFunc = useF_Use_Target_Delay;
  64. }
  65. //==========================================================
  66. /*QUAKED target_score (1 0 0) (-8 -8 -8) (8 8 8)
  67. "count" number of points to add, default 1
  68. The activator is given this many points.
  69. */
  70. void Use_Target_Score (gentity_t *ent, gentity_t *other, gentity_t *activator)
  71. {
  72. G_ActivateBehavior(ent,BSET_USE);
  73. AddScore( activator, ent->count );
  74. }
  75. void SP_target_score( gentity_t *ent ) {
  76. if ( !ent->count ) {
  77. ent->count = 1;
  78. }
  79. ent->e_UseFunc = useF_Use_Target_Score;
  80. }
  81. //==========================================================
  82. /*QUAKED target_print (1 0 0) (-8 -8 -8) (8 8 8)
  83. "message" text to print
  84. If "private", only the activator gets the message. If no checks, all clients get the message.
  85. */
  86. void Use_Target_Print (gentity_t *ent, gentity_t *other, gentity_t *activator)
  87. {
  88. G_ActivateBehavior(ent,BSET_USE);
  89. if ( activator->client ) {
  90. gi.SendServerCommand( activator-g_entities, "cp \"%s\"", ent->message );
  91. }
  92. }
  93. void SP_target_print( gentity_t *ent ) {
  94. ent->e_UseFunc = useF_Use_Target_Print;
  95. }
  96. //==========================================================
  97. /*QUAKED target_speaker (1 0 0) (-8 -8 -8) (8 8 8) looped-on looped-off global activator
  98. "noise" wav file to play
  99. "sounds" va() min max, so if your sound string is borgtalk%d.wav, and you set a "sounds" value of 4, it will randomly play borgtalk1.wav - borgtalk4.wav when triggered
  100. to use this, you must store the wav name in "soundGroup", NOT "noise"
  101. A global sound will play full volume throughout the level.
  102. Activator sounds will play on the player that activated the target.
  103. Global and activator sounds can't be combined with looping.
  104. Normal sounds play each time the target is used.
  105. Looped sounds will be toggled by use functions.
  106. Multiple identical looping sounds will just increase volume without any speed cost.
  107. "wait" : Seconds between triggerings, 0 = don't auto trigger
  108. "random" wait variance, default is 0
  109. */
  110. void Use_Target_Speaker (gentity_t *ent, gentity_t *other, gentity_t *activator) {
  111. if(ent->painDebounceTime > level.time)
  112. {
  113. return;
  114. }
  115. G_ActivateBehavior(ent,BSET_USE);
  116. if ( ent->sounds )
  117. {
  118. ent->noise_index = G_SoundIndex( va( ent->paintarget, Q_irand(1, ent->sounds ) ) );
  119. }
  120. if (ent->spawnflags & 3) { // looping sound toggles
  121. gentity_t *looper = ent;
  122. if ( ent->spawnflags & 8 ) {
  123. looper = activator;
  124. }
  125. if (looper->s.loopSound)
  126. looper->s.loopSound = 0; // turn it off
  127. else
  128. looper->s.loopSound = ent->noise_index; // start it
  129. }else { // normal sound
  130. if ( ent->spawnflags & 8 ) {
  131. G_AddEvent( activator, EV_GENERAL_SOUND, ent->noise_index );
  132. } else if (ent->spawnflags & 4) {
  133. G_AddEvent( ent, EV_GLOBAL_SOUND, ent->noise_index );
  134. } else {
  135. G_AddEvent( ent, EV_GENERAL_SOUND, ent->noise_index );
  136. }
  137. }
  138. if(ent->wait < 0)
  139. {//BYE!
  140. ent->e_UseFunc = useF_NULL;
  141. }
  142. else
  143. {
  144. ent->painDebounceTime = level.time + ent->wait;
  145. }
  146. }
  147. void SP_target_speaker( gentity_t *ent ) {
  148. char buffer[MAX_QPATH];
  149. char *s;
  150. int i;
  151. if ( VALIDSTRING( ent->soundSet ) )
  152. {
  153. VectorCopy( ent->s.origin, ent->s.pos.trBase );
  154. gi.linkentity (ent);
  155. return;
  156. }
  157. G_SpawnFloat( "wait", "0", &ent->wait );
  158. G_SpawnFloat( "random", "0", &ent->random );
  159. if(!ent->sounds)
  160. {
  161. if ( !G_SpawnString( "noise", "*NOSOUND*", &s ) ) {
  162. G_Error( "target_speaker without a noise key at %s", vtos( ent->s.origin ) );
  163. }
  164. Q_strncpyz( buffer, s, sizeof(buffer) );
  165. COM_DefaultExtension( buffer, sizeof(buffer), ".wav");
  166. ent->noise_index = G_SoundIndex(buffer);
  167. }
  168. else
  169. {//Precache all possible sounds
  170. for( i = 0; i < ent->sounds; i++ )
  171. {
  172. ent->noise_index = G_SoundIndex( va( ent->paintarget, i+1 ) );
  173. }
  174. }
  175. // a repeating speaker can be done completely client side
  176. ent->s.eType = ET_SPEAKER;
  177. ent->s.eventParm = ent->noise_index;
  178. ent->s.frame = ent->wait * 10;
  179. ent->s.clientNum = ent->random * 10;
  180. ent->wait *= 1000;
  181. // check for prestarted looping sound
  182. if ( ent->spawnflags & 1 ) {
  183. ent->s.loopSound = ent->noise_index;
  184. }
  185. ent->e_UseFunc = useF_Use_Target_Speaker;
  186. if (ent->spawnflags & 4) {
  187. ent->svFlags |= SVF_BROADCAST;
  188. }
  189. VectorCopy( ent->s.origin, ent->s.pos.trBase );
  190. // must link the entity so we get areas and clusters so
  191. // the server can determine who to send updates to
  192. gi.linkentity (ent);
  193. }
  194. //==========================================================
  195. /*QUAKED target_laser (0 .5 .8) (-8 -8 -8) (8 8 8) START_ON
  196. When triggered, fires a laser. You can either set a target or a direction.
  197. */
  198. void target_laser_think (gentity_t *self) {
  199. vec3_t end;
  200. trace_t tr;
  201. vec3_t point;
  202. // if pointed at another entity, set movedir to point at it
  203. if ( self->enemy ) {
  204. VectorMA (self->enemy->s.origin, 0.5, self->enemy->mins, point);
  205. VectorMA (point, 0.5, self->enemy->maxs, point);
  206. VectorSubtract (point, self->s.origin, self->movedir);
  207. VectorNormalize (self->movedir);
  208. }
  209. // fire forward and see what we hit
  210. VectorMA (self->s.origin, 2048, self->movedir, end);
  211. gi.trace( &tr, self->s.origin, NULL, NULL, end, self->s.number, CONTENTS_SOLID|CONTENTS_BODY|CONTENTS_CORPSE);
  212. if ( tr.entityNum ) {
  213. // hurt it if we can
  214. G_Damage ( &g_entities[tr.entityNum], self, self->activator, self->movedir,
  215. tr.endpos, self->damage, DAMAGE_NO_KNOCKBACK, MOD_ENERGY );
  216. }
  217. VectorCopy (tr.endpos, self->s.origin2);
  218. gi.linkentity( self );
  219. self->nextthink = level.time + FRAMETIME;
  220. }
  221. void target_laser_on (gentity_t *self)
  222. {
  223. if (!self->activator)
  224. self->activator = self;
  225. target_laser_think (self);
  226. }
  227. void target_laser_off (gentity_t *self)
  228. {
  229. gi.unlinkentity( self );
  230. self->nextthink = 0;
  231. }
  232. void target_laser_use (gentity_t *self, gentity_t *other, gentity_t *activator)
  233. {
  234. G_ActivateBehavior(self,BSET_USE);
  235. self->activator = activator;
  236. if ( self->nextthink > 0 )
  237. target_laser_off (self);
  238. else
  239. target_laser_on (self);
  240. }
  241. void target_laser_start (gentity_t *self)
  242. {
  243. gentity_t *ent;
  244. self->s.eType = ET_BEAM;
  245. if (self->target) {
  246. ent = G_Find (NULL, FOFS(targetname), self->target);
  247. if (!ent) {
  248. gi.Printf ("%s at %s: %s is a bad target\n", self->classname, vtos(self->s.origin), self->target);
  249. }
  250. G_SetEnemy( self, ent );
  251. } else {
  252. G_SetMovedir (self->s.angles, self->movedir);
  253. }
  254. self->e_UseFunc = useF_target_laser_use;
  255. self->e_ThinkFunc = thinkF_target_laser_think;
  256. if ( !self->damage ) {
  257. self->damage = 1;
  258. }
  259. if (self->spawnflags & 1)
  260. target_laser_on (self);
  261. else
  262. target_laser_off (self);
  263. }
  264. void SP_target_laser (gentity_t *self)
  265. {
  266. // let everything else get spawned before we start firing
  267. self->e_ThinkFunc = thinkF_target_laser_start;
  268. self->nextthink = level.time + START_TIME_LINK_ENTS;
  269. }
  270. //==========================================================
  271. void target_teleporter_use( gentity_t *self, gentity_t *other, gentity_t *activator ) {
  272. gentity_t *dest;
  273. if (!activator->client)
  274. return;
  275. G_ActivateBehavior(self,BSET_USE);
  276. dest = G_PickTarget( self->target );
  277. if (!dest) {
  278. gi.Printf ("Couldn't find teleporter destination\n");
  279. return;
  280. }
  281. TeleportPlayer( activator, dest->s.origin, dest->s.angles );
  282. }
  283. /*QUAK-ED target_teleporter (1 0 0) (-8 -8 -8) (8 8 8)
  284. The activator will be teleported away.
  285. */
  286. void SP_target_teleporter( gentity_t *self ) {
  287. if (!self->targetname)
  288. gi.Printf("untargeted %s at %s\n", self->classname, vtos(self->s.origin));
  289. self->e_UseFunc = useF_target_teleporter_use;
  290. }
  291. //==========================================================
  292. /*QUAKED target_relay (.5 .5 .5) (-8 -8 -8) (8 8 8) RED_ONLY BLUE_ONLY RANDOM x x x x INACTIVE
  293. This doesn't perform any actions except fire its targets.
  294. The activator can be forced to be from a certain team.
  295. if RANDOM is checked, only one of the targets will be fired, not all of them
  296. INACTIVE Can't be used until activated
  297. "delay" - Will actually fire this many seconds after being used
  298. "wait" - Cannot be fired again until this many seconds after the last time it was used
  299. */
  300. void target_relay_use_go (gentity_t *self )
  301. {
  302. G_ActivateBehavior( self, BSET_USE );
  303. if ( self->spawnflags & 4 )
  304. {
  305. gentity_t *ent;
  306. ent = G_PickTarget( self->target );
  307. if ( ent && (ent->e_UseFunc != useF_NULL) )
  308. { // e_UseFunc check can be omitted
  309. GEntity_UseFunc( ent, self, self->activator );
  310. }
  311. return;
  312. }
  313. G_UseTargets( self, self->activator );
  314. }
  315. void target_relay_use (gentity_t *self, gentity_t *other, gentity_t *activator)
  316. {
  317. if ( ( self->spawnflags & 1 ) && activator->client )
  318. {//&& activator->client->ps.persistant[PERS_TEAM] != TEAM_RED ) {
  319. return;
  320. }
  321. if ( ( self->spawnflags & 2 ) && activator->client )
  322. {//&& activator->client->ps.persistant[PERS_TEAM] != TEAM_BLUE ) {
  323. return;
  324. }
  325. if ( self->svFlags & SVF_INACTIVE )
  326. {//set by target_deactivate
  327. return;
  328. }
  329. if ( self->painDebounceTime > level.time )
  330. {
  331. return;
  332. }
  333. G_SetEnemy( self, other );
  334. self->activator = activator;
  335. if ( self->delay )
  336. {
  337. self->e_ThinkFunc = thinkF_target_relay_use_go;
  338. self->nextthink = level.time + self->delay;
  339. return;
  340. }
  341. target_relay_use_go( self );
  342. if ( self->wait < 0 )
  343. {
  344. self->e_UseFunc = useF_NULL;
  345. }
  346. else
  347. {
  348. self->painDebounceTime = level.time + self->wait;
  349. }
  350. }
  351. void SP_target_relay (gentity_t *self)
  352. {
  353. self->e_UseFunc = useF_target_relay_use;
  354. self->wait *= 1000;
  355. self->delay *= 1000;
  356. if ( self->spawnflags&128 )
  357. {
  358. self->svFlags |= SVF_INACTIVE;
  359. }
  360. }
  361. //==========================================================
  362. /*QUAKED target_kill (.5 .5 .5) (-8 -8 -8) (8 8 8) FALLING ELECTRICAL
  363. Kills the activator.
  364. */
  365. void target_kill_use( gentity_t *self, gentity_t *other, gentity_t *activator ) {
  366. G_ActivateBehavior(self,BSET_USE);
  367. if ( self->spawnflags & 1 )
  368. {//falling death
  369. G_Damage ( activator, NULL, NULL, NULL, NULL, 100000, DAMAGE_NO_PROTECTION, MOD_FALLING );
  370. if ( !activator->s.number && activator->health <= 0 && 1 )
  371. {
  372. extern void CGCam_Fade( vec4_t source, vec4_t dest, float duration );
  373. float src[4] = {0,0,0,0},dst[4]={0,0,0,1};
  374. CGCam_Fade( src, dst, 10000 );
  375. }
  376. }
  377. else if ( self->spawnflags & 2 ) // electrical
  378. {
  379. G_Damage ( activator, NULL, NULL, NULL, NULL, 100000, DAMAGE_NO_PROTECTION, MOD_ELECTROCUTE );
  380. if ( activator->client )
  381. {
  382. activator->s.powerups |= ( 1 << PW_SHOCKED );
  383. activator->client->ps.powerups[PW_SHOCKED] = level.time + 4000;
  384. }
  385. }
  386. else
  387. {
  388. G_Damage ( activator, NULL, NULL, NULL, NULL, 100000, DAMAGE_NO_PROTECTION, MOD_UNKNOWN);
  389. }
  390. }
  391. void SP_target_kill( gentity_t *self )
  392. {
  393. self->e_UseFunc = useF_target_kill_use;
  394. }
  395. /*QUAKED target_position (0 0.5 0) (-4 -4 -4) (4 4 4)
  396. Used as a positional target for in-game calculation, like jumppad targets.
  397. info_notnull does the same thing
  398. */
  399. void SP_target_position( gentity_t *self ){
  400. G_SetOrigin( self, self->s.origin );
  401. }
  402. //static -slc
  403. void target_location_linkup(gentity_t *ent)
  404. {
  405. int i;
  406. if (level.locationLinked)
  407. return;
  408. level.locationLinked = qtrue;
  409. level.locationHead = NULL;
  410. for (i = 0, ent = g_entities; i < globals.num_entities; i++, ent++) {
  411. if (ent->classname && !Q_stricmp(ent->classname, "target_location")) {
  412. ent->nextTrain = level.locationHead;
  413. level.locationHead = ent;
  414. }
  415. }
  416. // All linked together now
  417. }
  418. /*QUAKED target_location (0 0.5 0) (-8 -8 -8) (8 8 8)
  419. Set "message" to the name of this location.
  420. Set "count" to 0-7 for color.
  421. 0:white 1:red 2:green 3:yellow 4:blue 5:cyan 6:magenta 7:white
  422. Closest target_location in sight used for the location, if none
  423. in site, closest in distance
  424. */
  425. void SP_target_location( gentity_t *self ){
  426. self->e_ThinkFunc = thinkF_target_location_linkup;
  427. self->nextthink = level.time + 1000; // Let them all spawn first
  428. G_SetOrigin( self, self->s.origin );
  429. }
  430. //===NEW===================================================================
  431. /*QUAKED target_counter (1.0 0 0) (-4 -4 -4) (4 4 4) x x x x x x x INACTIVE
  432. Acts as an intermediary for an action that takes multiple inputs.
  433. INACTIVE cannot be used until used by a target_activate
  434. target2 - what the counter should fire each time it's incremented and does NOT reach it's count
  435. After the counter has been triggered "count" times (default 2), it will fire all of it's targets and remove itself.
  436. bounceCount - number of times the counter should reset to it's full count when it's done
  437. */
  438. void target_counter_use( gentity_t *self, gentity_t *other, gentity_t *activator )
  439. {
  440. if ( self->count == 0 )
  441. {
  442. return;
  443. }
  444. //gi.Printf("target_counter %s used by %s, entnum %d\n", self->targetname, activator->targetname, activator->s.number );
  445. self->count--;
  446. if ( activator )
  447. {
  448. Quake3Game()->DebugPrint( IGameInterface::WL_VERBOSE, "target_counter %s used by %s (%d/%d)\n", self->targetname, activator->targetname, (self->max_health-self->count), self->max_health );
  449. }
  450. if ( self->count )
  451. {
  452. if ( self->target2 )
  453. {
  454. //gi.Printf("target_counter %s firing target2 from %s, entnum %d\n", self->targetname, activator->targetname, activator->s.number );
  455. G_UseTargets2( self, activator, self->target2 );
  456. }
  457. return;
  458. }
  459. G_ActivateBehavior( self,BSET_USE );
  460. if ( self->spawnflags & 128 )
  461. {
  462. self->svFlags |= SVF_INACTIVE;
  463. }
  464. self->activator = activator;
  465. G_UseTargets( self, activator );
  466. if ( self->count == 0 )
  467. {
  468. if ( self->bounceCount == 0 )
  469. {
  470. return;
  471. }
  472. self->count = self->max_health;
  473. if ( self->bounceCount > 0 )
  474. {//-1 means bounce back forever
  475. self->bounceCount--;
  476. }
  477. }
  478. }
  479. void SP_target_counter (gentity_t *self)
  480. {
  481. self->wait = -1;
  482. if (!self->count)
  483. {
  484. self->count = 2;
  485. }
  486. //if ( self->bounceCount > 0 )//let's always set this anyway
  487. {//we will reset when we use up our count, remember our initial count
  488. self->max_health = self->count;
  489. }
  490. self->e_UseFunc = useF_target_counter_use;
  491. }
  492. /*QUAKED target_random (.5 .5 .5) (-4 -4 -4) (4 4 4) USEONCE
  493. Randomly fires off only one of it's targets each time used
  494. USEONCE set to never fire again
  495. */
  496. void target_random_use(gentity_t *self, gentity_t *other, gentity_t *activator)
  497. {
  498. int t_count = 0, pick;
  499. gentity_t *t = NULL;
  500. //gi.Printf("target_random %s used by %s (entnum %d)\n", self->targetname, activator->targetname, activator->s.number );
  501. G_ActivateBehavior(self,BSET_USE);
  502. if(self->spawnflags & 1)
  503. {
  504. self->e_UseFunc = useF_NULL;
  505. }
  506. while ( (t = G_Find (t, FOFS(targetname), self->target)) != NULL )
  507. {
  508. if (t != self)
  509. {
  510. t_count++;
  511. }
  512. }
  513. if(!t_count)
  514. {
  515. return;
  516. }
  517. if(t_count == 1)
  518. {
  519. G_UseTargets (self, activator);
  520. return;
  521. }
  522. //FIXME: need a seed
  523. pick = Q_irand(1, t_count);
  524. t_count = 0;
  525. while ( (t = G_Find (t, FOFS(targetname), self->target)) != NULL )
  526. {
  527. if (t != self)
  528. {
  529. t_count++;
  530. }
  531. else
  532. {
  533. continue;
  534. }
  535. if (t == self)
  536. {
  537. // gi.Printf ("WARNING: Entity used itself.\n");
  538. }
  539. else if(t_count == pick)
  540. {
  541. if (t->e_UseFunc != useF_NULL) // check can be omitted
  542. {
  543. GEntity_UseFunc(t, self, activator);
  544. return;
  545. }
  546. }
  547. if (!self->inuse)
  548. {
  549. gi.Printf("entity was removed while using targets\n");
  550. return;
  551. }
  552. }
  553. }
  554. void SP_target_random (gentity_t *self)
  555. {
  556. self->e_UseFunc = useF_target_random_use;
  557. }
  558. int numNewICARUSEnts = 0;
  559. void scriptrunner_run (gentity_t *self)
  560. {
  561. /*
  562. if (self->behaviorSet[BSET_USE])
  563. {
  564. char newname[MAX_FILENAME_LENGTH];
  565. sprintf((char *) &newname, "%s/%s", Q3_SCRIPT_DIR, self->behaviorSet[BSET_USE] );
  566. ICARUS_RunScript( self, newname );
  567. }
  568. */
  569. if ( self->count != -1 )
  570. {
  571. if ( self->count <= 0 )
  572. {
  573. self->e_UseFunc = useF_NULL;
  574. self->behaviorSet[BSET_USE] = NULL;
  575. return;
  576. }
  577. else
  578. {
  579. --self->count;
  580. }
  581. }
  582. if (self->behaviorSet[BSET_USE])
  583. {
  584. if ( self->spawnflags & 1 )
  585. {
  586. if ( !self->activator )
  587. {
  588. Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "target_scriptrunner tried to run on invalid entity!\n");
  589. return;
  590. }
  591. if ( self->activator->m_iIcarusID == IIcarusInterface::ICARUS_INVALID )
  592. {//Need to be initialized through ICARUS
  593. if ( !self->activator->script_targetname || !self->activator->script_targetname[0] )
  594. {
  595. //We don't have a script_targetname, so create a new one
  596. self->activator->script_targetname = va( "newICARUSEnt%d", numNewICARUSEnts++ );
  597. }
  598. if ( Quake3Game()->ValidEntity( self->activator ) )
  599. {
  600. Quake3Game()->InitEntity( self->activator );
  601. }
  602. else
  603. {
  604. Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "target_scriptrunner tried to run on invalid ICARUS activator!\n");
  605. return;
  606. }
  607. }
  608. Quake3Game()->DebugPrint( IGameInterface::WL_VERBOSE, "target_scriptrunner running %s on activator %s\n", self->behaviorSet[BSET_USE], self->activator->targetname );
  609. Quake3Game()->RunScript( self->activator, self->behaviorSet[BSET_USE] );
  610. }
  611. else
  612. {
  613. if ( self->activator )
  614. {
  615. Quake3Game()->DebugPrint( IGameInterface::WL_VERBOSE, "target_scriptrunner %s used by %s\n", self->targetname, self->activator->targetname );
  616. }
  617. G_ActivateBehavior( self, BSET_USE );
  618. }
  619. }
  620. if ( self->wait )
  621. {
  622. self->nextthink = level.time + self->wait;
  623. }
  624. }
  625. void target_scriptrunner_use(gentity_t *self, gentity_t *other, gentity_t *activator)
  626. {
  627. if ( self->nextthink > level.time )
  628. {
  629. return;
  630. }
  631. self->activator = activator;
  632. G_SetEnemy( self, other );
  633. if ( self->delay )
  634. {//delay before firing scriptrunner
  635. self->e_ThinkFunc = thinkF_scriptrunner_run;
  636. self->nextthink = level.time + self->delay;
  637. }
  638. else
  639. {
  640. scriptrunner_run (self);
  641. }
  642. }
  643. /*QUAKED target_scriptrunner (1 0 0) (-4 -4 -4) (4 4 4) runonactivator x x x x x x INACTIVE
  644. --- SPAWNFLAGS ---
  645. runonactivator - Will run the script on the entity that used this or tripped the trigger that used this
  646. INACTIVE - start off
  647. ----- KEYS ------
  648. Usescript - Script to run when used
  649. count - how many times to run, -1 = infinite. Default is once
  650. wait - can't be used again in this amount of seconds (Default is 1 second if it's multiple-use)
  651. delay - how long to wait after use to run script
  652. */
  653. void SP_target_scriptrunner( gentity_t *self )
  654. {
  655. if (!self->behaviorSet[BSET_USE])
  656. {
  657. gi.Printf(S_COLOR_RED "SP_target_scriptrunner %s has no USESCRIPT\n", self->targetname );
  658. }
  659. if ( self->spawnflags & 128 )
  660. {
  661. self->svFlags |= SVF_INACTIVE;
  662. }
  663. if ( !self->count )
  664. {
  665. self->count = 1;//default 1 use only
  666. }
  667. /*
  668. else if ( !self->wait )
  669. {
  670. self->wait = 1;//default wait of 1 sec
  671. }
  672. */
  673. // FIXME: this is a hack... because delay is read in as an int, so I'm bypassing that because it's too late in the project to change it and I want to be able to set less than a second delays
  674. // no one should be setting a radius on a scriptrunner, if they are this would be bad, take this out for the next project
  675. self->radius = 0.0f;
  676. G_SpawnFloat( "delay", "0", &self->radius );
  677. self->delay = self->radius * 1000;//sec to ms
  678. self->wait *= 1000;//sec to ms
  679. G_SetOrigin( self, self->s.origin );
  680. self->e_UseFunc = useF_target_scriptrunner_use;
  681. }
  682. void target_gravity_change_use(gentity_t *self, gentity_t *other, gentity_t *activator)
  683. {
  684. G_ActivateBehavior(self,BSET_USE);
  685. if ( self->spawnflags & 1 )
  686. {
  687. gi.cvar_set("g_gravity", va("%f", self->speed));
  688. }
  689. else if ( activator->client )
  690. {
  691. int grav = floor(self->speed);
  692. /*
  693. if ( activator->client->ps.gravity != grav )
  694. {
  695. gi.Printf("%s gravity changed to %d\n", activator->targetname, grav );
  696. }
  697. */
  698. activator->client->ps.gravity = grav;
  699. activator->svFlags |= SVF_CUSTOM_GRAVITY;
  700. //FIXME: need a way to set this back to normal?
  701. }
  702. }
  703. /*QUAKED target_gravity_change (1 0 0) (-4 -4 -4) (4 4 4) GLOBAL
  704. "gravity" - Normal = 800, Valid range: any
  705. GLOBAL - Apply to entire world, not just the activator
  706. */
  707. void SP_target_gravity_change( gentity_t *self )
  708. {
  709. G_SetOrigin( self, self->s.origin );
  710. G_SpawnFloat( "gravity", "0", &self->speed );
  711. self->e_UseFunc = useF_target_gravity_change_use;
  712. }
  713. void target_friction_change_use(gentity_t *self, gentity_t *other, gentity_t *activator)
  714. {
  715. G_ActivateBehavior(self,BSET_USE);
  716. if(self->spawnflags & 1)
  717. {//FIXME - make a global?
  718. //gi.Cvar_Set("g_friction", va("%d", self->health));
  719. }
  720. else if(activator->client)
  721. {
  722. activator->client->ps.friction = self->health;
  723. }
  724. }
  725. /*QUAKED target_friction_change (1 0 0) (-4 -4 -4) (4 4 4)
  726. "friction" Normal = 6, Valid range 0 - 10
  727. */
  728. void SP_target_friction_change( gentity_t *self )
  729. {
  730. G_SetOrigin( self, self->s.origin );
  731. self->e_UseFunc = useF_target_friction_change_use;
  732. }
  733. void set_mission_stats_cvars( void )
  734. {
  735. char text[1024]={0};
  736. //we'll assume that the activator is the player
  737. gclient_t* const client = &level.clients[0];
  738. if (!client)
  739. {
  740. return;
  741. }
  742. cg_entities[0].gent->client->sess.missionStats.enemiesKilled;
  743. gi.cvar_set("ui_stats_enemieskilled", va("%d",client->sess.missionStats.enemiesKilled)); //pass this on to the menu
  744. if (cg_entities[0].gent->client->sess.missionStats.totalSecrets)
  745. {
  746. cgi_SP_GetStringTextString( "SP_INGAME_SECRETAREAS_OF", text, sizeof(text) );
  747. gi.cvar_set("ui_stats_secretsfound", va("%d %s %d",
  748. cg_entities[0].gent->client->sess.missionStats.secretsFound,
  749. text,
  750. cg_entities[0].gent->client->sess.missionStats.totalSecrets));
  751. }
  752. else // Setting ui_stats_secretsfound to 0 will hide the text on screen
  753. {
  754. gi.cvar_set("ui_stats_secretsfound", "0");
  755. }
  756. // Find the favorite weapon
  757. int wpn=0,i;
  758. int max_wpn = cg_entities[0].gent->client->sess.missionStats.weaponUsed[0];
  759. for (i = 1; i<WP_NUM_WEAPONS; i++)
  760. {
  761. if (cg_entities[0].gent->client->sess.missionStats.weaponUsed[i] > max_wpn)
  762. {
  763. max_wpn = cg_entities[0].gent->client->sess.missionStats.weaponUsed[i];
  764. wpn = i;
  765. }
  766. }
  767. if ( wpn )
  768. {
  769. gitem_t *wItem= FindItemForWeapon( (weapon_t)wpn);
  770. cgi_SP_GetStringTextString( va("SP_INGAME_%s",wItem->classname ), text, sizeof( text ));
  771. gi.cvar_set("ui_stats_fave", va("%s",text)); //pass this on to the menu
  772. }
  773. gi.cvar_set("ui_stats_shots", va("%d",client->sess.missionStats.shotsFired)); //pass this on to the menu
  774. gi.cvar_set("ui_stats_hits", va("%d",client->sess.missionStats.hits)); //pass this on to the menu
  775. const float percent = cg_entities[0].gent->client->sess.missionStats.shotsFired? 100.0f * (float)cg_entities[0].gent->client->sess.missionStats.hits / cg_entities[0].gent->client->sess.missionStats.shotsFired : 0;
  776. gi.cvar_set("ui_stats_accuracy", va("%.2f%%",percent)); //pass this on to the menu
  777. gi.cvar_set("ui_stats_thrown", va("%d",client->sess.missionStats.saberThrownCnt)); //pass this on to the menu
  778. gi.cvar_set("ui_stats_blocks", va("%d",client->sess.missionStats.saberBlocksCnt));
  779. gi.cvar_set("ui_stats_legattacks", va("%d",client->sess.missionStats.legAttacksCnt));
  780. gi.cvar_set("ui_stats_armattacks", va("%d",client->sess.missionStats.armAttacksCnt));
  781. gi.cvar_set("ui_stats_bodyattacks", va("%d",client->sess.missionStats.torsoAttacksCnt));
  782. gi.cvar_set("ui_stats_absorb", va("%d",client->sess.missionStats.forceUsed[FP_ABSORB]));
  783. gi.cvar_set("ui_stats_heal", va("%d",client->sess.missionStats.forceUsed[FP_HEAL]));
  784. gi.cvar_set("ui_stats_mindtrick", va("%d",client->sess.missionStats.forceUsed[FP_TELEPATHY]));
  785. gi.cvar_set("ui_stats_protect", va("%d",client->sess.missionStats.forceUsed[FP_PROTECT]));
  786. gi.cvar_set("ui_stats_jump", va("%d",client->sess.missionStats.forceUsed[FP_LEVITATION]));
  787. gi.cvar_set("ui_stats_pull", va("%d",client->sess.missionStats.forceUsed[FP_PULL]));
  788. gi.cvar_set("ui_stats_push", va("%d",client->sess.missionStats.forceUsed[FP_PUSH]));
  789. gi.cvar_set("ui_stats_sense", va("%d",client->sess.missionStats.forceUsed[FP_SEE]));
  790. gi.cvar_set("ui_stats_speed", va("%d",client->sess.missionStats.forceUsed[FP_SPEED]));
  791. gi.cvar_set("ui_stats_defense", va("%d",client->sess.missionStats.forceUsed[FP_SABER_DEFENSE]));
  792. gi.cvar_set("ui_stats_offense", va("%d",client->sess.missionStats.forceUsed[FP_SABER_OFFENSE]));
  793. gi.cvar_set("ui_stats_throw", va("%d",client->sess.missionStats.forceUsed[FP_SABERTHROW]));
  794. gi.cvar_set("ui_stats_drain", va("%d",client->sess.missionStats.forceUsed[FP_DRAIN]));
  795. gi.cvar_set("ui_stats_grip", va("%d",client->sess.missionStats.forceUsed[FP_GRIP]));
  796. gi.cvar_set("ui_stats_lightning", va("%d",client->sess.missionStats.forceUsed[FP_LIGHTNING]));
  797. gi.cvar_set("ui_stats_rage", va("%d",client->sess.missionStats.forceUsed[FP_RAGE]));
  798. }
  799. #include "..\cgame\cg_media.h" //access to cgs
  800. extern void G_ChangeMap (const char *mapname, const char *spawntarget, qboolean hub); //g_utils
  801. void target_level_change_use(gentity_t *self, gentity_t *other, gentity_t *activator)
  802. {
  803. G_ActivateBehavior(self,BSET_USE);
  804. if( self->message && !Q_stricmp( "disconnect", self->message ) )
  805. {
  806. gi.SendConsoleCommand( "disconnect\n");
  807. }
  808. else
  809. {
  810. G_ChangeMap( self->message, self->target, (self->spawnflags&1) );
  811. }
  812. if (self->count>=0)
  813. {
  814. gi.cvar_set("tier_storyinfo", va("%i",self->count));
  815. if (level.mapname[0] == 't' && level.mapname[2] == '_'
  816. && ( level.mapname[1] == '1' || level.mapname[1] == '2' || level.mapname[1] == '3' )
  817. )
  818. {
  819. char s[2048];
  820. gi.Cvar_VariableStringBuffer("tiers_complete", s, sizeof(s)); //get the current list
  821. if (*s)
  822. {
  823. gi.cvar_set("tiers_complete", va("%s %s", s, level.mapname)); //strcat this level into the existing list
  824. }
  825. else
  826. {
  827. gi.cvar_set("tiers_complete", level.mapname); //set this level into the list
  828. }
  829. }
  830. if (self->noise_index)
  831. {
  832. cgi_S_StopSounds();
  833. cgi_S_StartSound( NULL, 0, CHAN_VOICE, cgs.sound_precache[ self->noise_index ] );
  834. }
  835. }
  836. set_mission_stats_cvars();
  837. }
  838. /*QUAKED target_level_change (1 0 0) (-4 -4 -4) (4 4 4) HUB NO_STORYSOUND
  839. HUB - Will save the current map's status and load the next map with any saved status it may have
  840. NO_STORYSOUND - will not play storyinfo wav file, even if you '++' or set tier_storyinfo
  841. "mapname" - Name of map to change to or "+menuname" to launch a menu instead
  842. "target" - Name of spawnpoint to start at in the new map
  843. "tier_storyinfo" - integer to set cvar or "++" to just increment
  844. "storyhead" - which head to show on menu [luke, kyle, or prot]
  845. "saber_menu" - integer to set cvar for menu
  846. "weapon_menu" - integer to set cvar for ingame weapon menu
  847. */
  848. void SP_target_level_change( gentity_t *self )
  849. {
  850. if ( !self->message )
  851. {
  852. G_Error( "target_level_change with no mapname!\n");
  853. return;
  854. }
  855. char *s;
  856. if (G_SpawnString( "tier_storyinfo", "", &s ))
  857. {
  858. if (*s == '+')
  859. {
  860. self->noise_index = G_SoundIndex(va("sound/chars/tiervictory/%s.mp3",level.mapname) );
  861. self->count = gi.Cvar_VariableIntegerValue("tier_storyinfo")+1;
  862. G_SoundIndex(va("sound/chars/storyinfo/%d.mp3",self->count)); //cache for menu
  863. }
  864. else
  865. {
  866. self->count = atoi(s);
  867. if( !(self->spawnflags & 2) )
  868. {
  869. self->noise_index = G_SoundIndex(va("sound/chars/storyinfo/%d.mp3",self->count) );
  870. }
  871. }
  872. if (G_SpawnString( "storyhead", "", &s ))
  873. { //[luke, kyle, or prot]
  874. gi.cvar_set("storyhead", s); //pass this on to the menu
  875. }
  876. else
  877. { //show head based on mapname
  878. gi.cvar_set("storyhead", level.mapname); //pass this on to the menu
  879. }
  880. }
  881. if (G_SpawnString( "saber_menu", "", &s ))
  882. {
  883. gi.cvar_set("saber_menu", s); //pass this on to the menu
  884. }
  885. if (G_SpawnString( "weapon_menu", "1", &s ))
  886. {
  887. gi.cvar_set("weapon_menu", s); //pass this on to the menu
  888. }
  889. else
  890. {
  891. gi.cvar_set("weapon_menu", "0"); //pass this on to the menu
  892. }
  893. G_SetOrigin( self, self->s.origin );
  894. self->e_UseFunc = useF_target_level_change_use;
  895. }
  896. /*QUAKED target_change_parm (1 0 0) (-4 -4 -4) (4 4 4)
  897. Copies any parms set on this ent to the entity that fired the trigger/button/whatever that uses this
  898. parm1
  899. parm2
  900. parm3
  901. parm4
  902. parm5
  903. parm6
  904. parm7
  905. parm8
  906. parm9
  907. parm10
  908. parm11
  909. parm12
  910. parm13
  911. parm14
  912. parm15
  913. parm16
  914. */
  915. void Q3_SetParm (int entID, int parmNum, const char *parmValue);
  916. void target_change_parm_use(gentity_t *self, gentity_t *other, gentity_t *activator)
  917. {
  918. if ( !activator || !self )
  919. {
  920. return;
  921. }
  922. //FIXME: call capyparms
  923. if ( self->parms )
  924. {
  925. for ( int parmNum = 0; parmNum < MAX_PARMS; parmNum++ )
  926. {
  927. if ( self->parms->parm[parmNum] && self->parms->parm[parmNum][0] )
  928. {
  929. Q3_SetParm( activator->s.number, parmNum, self->parms->parm[parmNum] );
  930. }
  931. }
  932. }
  933. }
  934. void SP_target_change_parm( gentity_t *self )
  935. {
  936. if ( !self->parms )
  937. {//ERROR!
  938. return;
  939. }
  940. G_SetOrigin( self, self->s.origin );
  941. self->e_UseFunc = useF_target_change_parm_use;
  942. }
  943. void target_play_music_use(gentity_t *self, gentity_t *other, gentity_t *activator)
  944. {
  945. G_ActivateBehavior(self,BSET_USE);
  946. gi.SetConfigstring( CS_MUSIC, self->message );
  947. }
  948. /*QUAKED target_play_music (1 0 0) (-4 -4 -4) (4 4 4)
  949. target_play_music
  950. Plays the requested music files when this target is used.
  951. "targetname"
  952. "music" music WAV or MP3 file ( music/introfile.mp3 [optional] music/loopfile.mp3 )
  953. If an intro file and loop file are specified, the intro plays first, then the looping
  954. portion will start and loop indefinetly. If no introfile is entered, only the loopfile
  955. will play.
  956. */
  957. void SP_target_play_music( gentity_t *self )
  958. {
  959. char *s;
  960. G_SetOrigin( self, self->s.origin );
  961. if (!G_SpawnString( "music", "", &s )) {
  962. G_Error( "target_play_music without a music key at %s", vtos( self->s.origin ) );
  963. }
  964. self->message = G_NewString (s);
  965. self->e_UseFunc = useF_target_play_music_use;
  966. extern cvar_t *com_buildScript;
  967. //Precache!
  968. if (com_buildScript->integer) {//copy this puppy over
  969. char buffer[MAX_QPATH];
  970. fileHandle_t hFile;
  971. Q_strncpyz( buffer, s, sizeof(buffer) );
  972. COM_DefaultExtension( buffer, sizeof(buffer), ".mp3");
  973. gi.FS_FOpenFile(buffer, &hFile, FS_READ);
  974. if (hFile) {
  975. gi.FS_FCloseFile( hFile );
  976. }
  977. }
  978. }
  979. extern bool allowNormalAutosave;
  980. void target_autosave_use(gentity_t *self, gentity_t *other, gentity_t *activator)
  981. {
  982. if(!allowNormalAutosave)
  983. return;
  984. G_ActivateBehavior(self,BSET_USE);
  985. //gi.SendServerCommand( NULL, "cp @SP_INGAME_CHECKPOINT" );
  986. CG_CenterPrint( "@SP_INGAME_CHECKPOINT", SCREEN_HEIGHT * 0.25 ); //jump the network
  987. gi.SendConsoleCommand( "wait 2;save auto\n" );
  988. }
  989. /*QUAKED target_autosave (1 0 0) (-4 -4 -4) (4 4 4)
  990. Auto save the game in two frames.
  991. Make sure it won't trigger during dialogue or cinematic or it will stutter!
  992. */
  993. void SP_target_autosave( gentity_t *self )
  994. {
  995. G_SetOrigin( self, self->s.origin );
  996. self->e_UseFunc = useF_target_autosave_use;
  997. }
  998. void target_secret_use(gentity_t *self, gentity_t *other, gentity_t *activator)
  999. {
  1000. //we'll assume that the activator is the player
  1001. gclient_t* const client = &level.clients[0];
  1002. client->sess.missionStats.secretsFound++;
  1003. if ( activator )
  1004. {
  1005. G_Sound( activator, self->noise_index );
  1006. }
  1007. else
  1008. {
  1009. G_Sound( self, self->noise_index );
  1010. }
  1011. gi.SendServerCommand( NULL, "cp @SP_INGAME_SECRET_AREA" );
  1012. assert(client->sess.missionStats.totalSecrets);
  1013. }
  1014. /*QUAKED target_secret (1 0 1) (-4 -4 -4) (4 4 4)
  1015. You found a Secret!
  1016. "count" - how many secrets on this level,
  1017. if more than one on a level, be sure they all have the same count!
  1018. */
  1019. void SP_target_secret( gentity_t *self )
  1020. {
  1021. G_SetOrigin( self, self->s.origin );
  1022. self->e_UseFunc = useF_target_secret_use;
  1023. self->noise_index = G_SoundIndex("sound/interface/secret_area");
  1024. if (self->count)
  1025. {
  1026. gi.cvar_set("newTotalSecrets", va("%i",self->count));
  1027. }
  1028. }