1 /*
2 ===========================================================================
3 Copyright (C) 1999 - 2005, Id Software, Inc.
4 Copyright (C) 2000 - 2013, Raven Software, Inc.
5 Copyright (C) 2001 - 2013, Activision, Inc.
6 Copyright (C) 2013 - 2015, OpenJK contributors
7 
8 This file is part of the OpenJK source code.
9 
10 OpenJK is free software; you can redistribute it and/or modify it
11 under the terms of the GNU General Public License version 2 as
12 published by the Free Software Foundation.
13 
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 GNU General Public License for more details.
18 
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, see <http://www.gnu.org/licenses/>.
21 ===========================================================================
22 */
23 
24 #include "g_local.h"
25 #include "bg_saga.h"
26 
27 int gTrigFallSound;
28 
InitTrigger(gentity_t * self)29 void InitTrigger( gentity_t *self ) {
30 	if (!VectorCompare (self->s.angles, vec3_origin))
31 		G_SetMovedir (self->s.angles, self->movedir);
32 
33 	trap->SetBrushModel( (sharedEntity_t *)self, self->model );
34 	self->r.contents = CONTENTS_TRIGGER;		// replaces the -1 from trap->SetBrushModel
35 	self->r.svFlags = SVF_NOCLIENT;
36 
37 	if(self->spawnflags & 128)
38 	{
39 		self->flags |= FL_INACTIVE;
40 	}
41 }
42 
43 // the wait time has passed, so set back up for another activation
multi_wait(gentity_t * ent)44 void multi_wait( gentity_t *ent ) {
45 	ent->nextthink = 0;
46 }
47 
48 void trigger_cleared_fire (gentity_t *self);
49 
50 // the trigger was just activated
51 // ent->activator should be set to the activator so it can be held through a delay
52 // so wait for the delay time before firing
multi_trigger_run(gentity_t * ent)53 void multi_trigger_run( gentity_t *ent )
54 {
55 	ent->think = 0;
56 
57 	G_ActivateBehavior( ent, BSET_USE );
58 
59 	if ( ent->soundSet && ent->soundSet[0] )
60 	{
61 		trap->SetConfigstring( CS_GLOBAL_AMBIENT_SET, ent->soundSet );
62 	}
63 
64 	if (ent->genericValue4)
65 	{ //we want to activate target3 for team1 or target4 for team2
66 		if (ent->genericValue4 == SIEGETEAM_TEAM1 &&
67 			ent->target3 && ent->target3[0])
68 		{
69 			G_UseTargets2(ent, ent->activator, ent->target3);
70 		}
71 		else if (ent->genericValue4 == SIEGETEAM_TEAM2 &&
72 			ent->target4 && ent->target4[0])
73 		{
74 			G_UseTargets2(ent, ent->activator, ent->target4);
75 		}
76 
77 		ent->genericValue4 = 0;
78 	}
79 
80 	G_UseTargets (ent, ent->activator);
81 	if ( ent->noise_index )
82 	{
83 		G_Sound( ent->activator, CHAN_AUTO, ent->noise_index );
84 	}
85 
86 	if ( ent->target2 && ent->target2[0] && ent->wait >= 0 )
87 	{
88 		ent->think = trigger_cleared_fire;
89 		ent->nextthink = level.time + ent->speed;
90 	}
91 	else if ( ent->wait > 0 )
92 	{
93 		if ( ent->painDebounceTime != level.time )
94 		{//first ent to touch it this frame
95 			//ent->e_ThinkFunc = thinkF_multi_wait;
96 			ent->nextthink = level.time + ( ent->wait + ent->random * Q_flrand(-1.0f, 1.0f) ) * 1000;
97 			ent->painDebounceTime = level.time;
98 		}
99 	}
100 	else if ( ent->wait < 0 )
101 	{
102 		// we can't just remove (self) here, because this is a touch function
103 		// called while looping through area links...
104 		ent->r.contents &= ~CONTENTS_TRIGGER;//so the EntityContact trace doesn't have to be done against me
105 		ent->think = 0;
106 		ent->use = 0;
107 		//Don't remove, Icarus may barf?
108 		//ent->nextthink = level.time + FRAMETIME;
109 		//ent->think = G_FreeEntity;
110 	}
111 	if( ent->activator && ent->activator->client )
112 	{	// mark the trigger as being touched by the player
113 		ent->aimDebounceTime = level.time;
114 	}
115 }
116 
117 //determine if the class given is listed in the string using the | formatting
G_NameInTriggerClassList(char * list,char * str)118 qboolean G_NameInTriggerClassList(char *list, char *str)
119 {
120 	char cmp[MAX_STRING_CHARS];
121 	int i = 0;
122 	int j;
123 
124 	while (list[i])
125 	{
126         j = 0;
127         while (list[i] && list[i] != '|')
128 		{
129 			cmp[j] = list[i];
130 			i++;
131 			j++;
132 		}
133 		cmp[j] = 0;
134 
135 		if (!Q_stricmp(str, cmp))
136 		{ //found it
137 			return qtrue;
138 		}
139 		if (list[i] != '|')
140 		{ //reached the end and never found it
141 			return qfalse;
142 		}
143 		i++;
144 	}
145 
146 	return qfalse;
147 }
148 
149 extern qboolean gSiegeRoundBegun;
150 void SiegeItemRemoveOwner(gentity_t *ent, gentity_t *carrier);
multi_trigger(gentity_t * ent,gentity_t * activator)151 void multi_trigger( gentity_t *ent, gentity_t *activator )
152 {
153 	qboolean haltTrigger = qfalse;
154 
155 	if ( ent->think == multi_trigger_run )
156 	{//already triggered, just waiting to run
157 		return;
158 	}
159 
160 	if (level.gametype == GT_SIEGE &&
161 		!gSiegeRoundBegun)
162 	{ //nothing can be used til the round starts.
163 		return;
164 	}
165 
166 	if (level.gametype == GT_SIEGE &&
167 		activator && activator->client &&
168 		ent->alliedTeam &&
169 		activator->client->sess.sessionTeam != ent->alliedTeam)
170 	{ //this team can't activate this trigger.
171 		return;
172 	}
173 
174 	if (level.gametype == GT_SIEGE &&
175 		ent->idealclass && ent->idealclass[0])
176 	{ //only certain classes can activate it
177 		if (!activator ||
178 			!activator->client ||
179 			activator->client->siegeClass < 0)
180 		{ //no class
181 			return;
182 		}
183 
184 		if (!G_NameInTriggerClassList(bgSiegeClasses[activator->client->siegeClass].name, ent->idealclass))
185 		{ //wasn't in the list
186 			return;
187 		}
188 	}
189 
190 	if (level.gametype == GT_SIEGE && ent->genericValue1)
191 	{
192 		haltTrigger = qtrue;
193 
194 		if (activator && activator->client &&
195 			activator->client->holdingObjectiveItem &&
196 			ent->targetname && ent->targetname[0])
197 		{
198 			gentity_t *objItem = &g_entities[activator->client->holdingObjectiveItem];
199 
200 			if (objItem && objItem->inuse)
201 			{
202 				if (objItem->goaltarget && objItem->goaltarget[0] &&
203 					!Q_stricmp(ent->targetname, objItem->goaltarget))
204 				{
205 					if (objItem->genericValue7 != activator->client->sess.sessionTeam)
206 					{ //The carrier of the item is not on the team which disallows objective scoring for it
207 						if (objItem->target3 && objItem->target3[0])
208 						{ //if it has a target3, fire it off instead of using the trigger
209 							G_UseTargets2(objItem, objItem, objItem->target3);
210 
211                             //3-24-03 - want to fire off the target too I guess, if we have one.
212 							if (ent->targetname && ent->targetname[0])
213 							{
214 								haltTrigger = qfalse;
215 							}
216 						}
217 						else
218 						{
219 							haltTrigger = qfalse;
220 						}
221 
222 						//now that the item has been delivered, it can go away.
223 						SiegeItemRemoveOwner(objItem, activator);
224 						objItem->nextthink = 0;
225 						objItem->neverFree = qfalse;
226 						G_FreeEntity(objItem);
227 					}
228 				}
229 			}
230 		}
231 	}
232 	else if (ent->genericValue1)
233 	{ //Never activate in non-siege gametype I guess.
234 		return;
235 	}
236 
237 	if (ent->genericValue2)
238 	{ //has "teambalance" property
239 		int i = 0;
240 		int team1ClNum = 0;
241 		int team2ClNum = 0;
242 		int owningTeam = ent->genericValue3;
243 		int newOwningTeam = 0;
244 		int numEnts = 0;
245 		int entityList[MAX_GENTITIES];
246 		gentity_t *cl;
247 
248 		if (level.gametype != GT_SIEGE)
249 		{
250 			return;
251 		}
252 
253 		if (!activator->client ||
254 			(activator->client->sess.sessionTeam != SIEGETEAM_TEAM1 && activator->client->sess.sessionTeam != SIEGETEAM_TEAM2))
255 		{ //activator must be a valid client to begin with
256 			return;
257 		}
258 
259 		//Count up the number of clients standing within the bounds of the trigger and the number of them on each team
260 		numEnts = trap->EntitiesInBox( ent->r.absmin, ent->r.absmax, entityList, MAX_GENTITIES );
261 		while (i < numEnts)
262 		{
263 			if (entityList[i] < MAX_CLIENTS)
264 			{ //only care about clients
265 				cl = &g_entities[entityList[i]];
266 
267 				//the client is valid
268 				if (cl->inuse && cl->client &&
269 					(cl->client->sess.sessionTeam == SIEGETEAM_TEAM1 || cl->client->sess.sessionTeam == SIEGETEAM_TEAM2) &&
270 					cl->health > 0 &&
271 					!(cl->client->ps.eFlags & EF_DEAD))
272 				{
273 					//See which team he's on
274 					if (cl->client->sess.sessionTeam == SIEGETEAM_TEAM1)
275 					{
276 						team1ClNum++;
277 					}
278 					else
279 					{
280 						team2ClNum++;
281 					}
282 				}
283 			}
284 			i++;
285 		}
286 
287 		if (!team1ClNum && !team2ClNum)
288 		{ //no one in the box? How did we get activated? Oh well.
289 			return;
290 		}
291 
292 		if (team1ClNum == team2ClNum)
293 		{ //if equal numbers the ownership will remain the same as it is now
294 			return;
295 		}
296 
297 		//decide who owns it now
298 		if (team1ClNum > team2ClNum)
299 		{
300 			newOwningTeam = SIEGETEAM_TEAM1;
301 		}
302 		else
303 		{
304 			newOwningTeam = SIEGETEAM_TEAM2;
305 		}
306 
307 		if (owningTeam == newOwningTeam)
308 		{ //it's the same one it already was, don't care then.
309 			return;
310 		}
311 
312 		//Set the new owner and set the variable which will tell us to activate a team-specific target
313 		ent->genericValue3 = newOwningTeam;
314 		ent->genericValue4 = newOwningTeam;
315 	}
316 
317 	if (haltTrigger)
318 	{ //This is an objective trigger and the activator is not carrying an objective item that matches the targetname.
319 		return;
320 	}
321 
322 	if ( ent->nextthink > level.time )
323 	{
324 		if( ent->spawnflags & 2048 ) // MULTIPLE - allow multiple entities to touch this trigger in a single frame
325 		{
326 			if ( ent->painDebounceTime && ent->painDebounceTime != level.time )
327 			{//this should still allow subsequent ents to fire this trigger in the current frame
328 				return;		// can't retrigger until the wait is over
329 			}
330 		}
331 		else
332 		{
333 			return;
334 		}
335 
336 	}
337 
338 	// if the player has already activated this trigger this frame
339 	if( activator && activator->s.number < MAX_CLIENTS && ent->aimDebounceTime == level.time )
340 	{
341 		return;
342 	}
343 
344 	if ( ent->flags & FL_INACTIVE )
345 	{//Not active at this time
346 		return;
347 	}
348 
349 	ent->activator = activator;
350 
351 	if(ent->delay && ent->painDebounceTime < (level.time + ent->delay) )
352 	{//delay before firing trigger
353 		ent->think = multi_trigger_run;
354 		ent->nextthink = level.time + ent->delay;
355 		ent->painDebounceTime = level.time;
356 
357 	}
358 	else
359 	{
360 		multi_trigger_run (ent);
361 	}
362 }
363 
Use_Multi(gentity_t * ent,gentity_t * other,gentity_t * activator)364 void Use_Multi( gentity_t *ent, gentity_t *other, gentity_t *activator )
365 {
366 	multi_trigger( ent, activator );
367 }
368 
369 qboolean G_PointInBounds( vec3_t point, vec3_t mins, vec3_t maxs );
370 
Touch_Multi(gentity_t * self,gentity_t * other,trace_t * trace)371 void Touch_Multi( gentity_t *self, gentity_t *other, trace_t *trace )
372 {
373 	if( !other->client )
374 	{
375 		return;
376 	}
377 
378 	if ( self->flags & FL_INACTIVE )
379 	{//set by target_deactivate
380 		return;
381 	}
382 
383 	if( self->alliedTeam )
384 	{
385 		if ( other->client->sess.sessionTeam != self->alliedTeam )
386 		{
387 			return;
388 		}
389 	}
390 
391 // moved to just above multi_trigger because up here it just checks if the trigger is not being touched
392 // we want it to check any conditions set on the trigger, if one of those isn't met, the trigger is considered to be "cleared"
393 //	if ( self->e_ThinkFunc == thinkF_trigger_cleared_fire )
394 //	{//We're waiting to fire our target2 first
395 //		self->nextthink = level.time + self->speed;
396 //		return;
397 //	}
398 
399 	if ( self->spawnflags & 1 )
400 	{
401 		if ( other->s.eType == ET_NPC )
402 		{
403 			return;
404 		}
405 	}
406 	else
407 	{
408 		if ( self->spawnflags & 16 )
409 		{//NPCONLY
410 			if ( other->NPC == NULL )
411 			{
412 				return;
413 			}
414 		}
415 
416 		if ( self->NPC_targetname && self->NPC_targetname[0] )
417 		{
418 			if ( other->script_targetname && other->script_targetname[0] )
419 			{
420 				if ( Q_stricmp( self->NPC_targetname, other->script_targetname ) != 0 )
421 				{//not the right guy to fire me off
422 					return;
423 				}
424 			}
425 			else
426 			{
427 				return;
428 			}
429 		}
430 	}
431 
432 	if ( self->spawnflags & 2 )
433 	{//FACING
434 		vec3_t	forward;
435 
436 		AngleVectors( other->client->ps.viewangles, forward, NULL, NULL );
437 
438 		if ( DotProduct( self->movedir, forward ) < 0.5 )
439 		{//Not Within 45 degrees
440 			return;
441 		}
442 	}
443 
444 	if ( self->spawnflags & 4 )
445 	{//USE_BUTTON
446 		if( !( other->client->pers.cmd.buttons & BUTTON_USE ) )
447 		{//not pressing use button
448 			return;
449 		}
450 
451 		if ((other->client->ps.weaponTime > 0 && other->client->ps.torsoAnim != BOTH_BUTTON_HOLD && other->client->ps.torsoAnim != BOTH_CONSOLE1) || other->health < 1 ||
452 			(other->client->ps.pm_flags & PMF_FOLLOW) || other->client->sess.sessionTeam == TEAM_SPECTATOR ||
453 			other->client->ps.forceHandExtend != HANDEXTEND_NONE)
454 		{ //player has to be free of other things to use.
455 			return;
456 		}
457 
458 		if (self->genericValue7)
459 		{ //we have to be holding the use key in this trigger for x milliseconds before firing
460 			if (level.gametype == GT_SIEGE &&
461 				self->idealclass && self->idealclass[0])
462 			{ //only certain classes can activate it
463 				if (!other ||
464 					!other->client ||
465 					other->client->siegeClass < 0)
466 				{ //no class
467 					return;
468 				}
469 
470 				if (!G_NameInTriggerClassList(bgSiegeClasses[other->client->siegeClass].name, self->idealclass))
471 				{ //wasn't in the list
472 					return;
473 				}
474 			}
475 
476 			if (!G_PointInBounds( other->client->ps.origin, self->r.absmin, self->r.absmax ))
477 			{
478 				return;
479 			}
480 			else if (other->client->isHacking != self->s.number && other->s.number < MAX_CLIENTS )
481 			{ //start the hack
482 				other->client->isHacking = self->s.number;
483 				VectorCopy(other->client->ps.viewangles, other->client->hackingAngles);
484 				other->client->ps.hackingTime = level.time + self->genericValue7;
485 				other->client->ps.hackingBaseTime = self->genericValue7;
486 				if (other->client->ps.hackingBaseTime > 60000)
487 				{ //don't allow a bit overflow
488 					other->client->ps.hackingTime = level.time + 60000;
489 					other->client->ps.hackingBaseTime = 60000;
490 				}
491 				return;
492 			}
493 			else if (other->client->ps.hackingTime < level.time)
494 			{ //finished with the hack, reset the hacking values and let it fall through
495 				other->client->isHacking = 0; //can't hack a client
496 				other->client->ps.hackingTime = 0;
497 			}
498 			else
499 			{ //hack in progress
500 				return;
501 			}
502 		}
503 	}
504 
505 	if ( self->spawnflags & 8 )
506 	{//FIRE_BUTTON
507 		if( !( other->client->pers.cmd.buttons & BUTTON_ATTACK ) &&
508 			!( other->client->pers.cmd.buttons & BUTTON_ALT_ATTACK ) )
509 		{//not pressing fire button or altfire button
510 			return;
511 		}
512 	}
513 
514 	if ( self->radius )
515 	{
516 		vec3_t	eyeSpot;
517 
518 		//Only works if your head is in it, but we allow leaning out
519 		//NOTE: We don't use CalcEntitySpot SPOT_HEAD because we don't want this
520 		//to be reliant on the physical model the player uses.
521 		VectorCopy(other->client->ps.origin, eyeSpot);
522 		eyeSpot[2] += other->client->ps.viewheight;
523 
524 		if ( G_PointInBounds( eyeSpot, self->r.absmin, self->r.absmax ) )
525 		{
526 			if( !( other->client->pers.cmd.buttons & BUTTON_ATTACK ) &&
527 				!( other->client->pers.cmd.buttons & BUTTON_ALT_ATTACK ) )
528 			{//not attacking, so hiding bonus
529 				/*
530 				//FIXME:  should really have sound events clear the hiddenDist
531 				other->client->hiddenDist = self->radius;
532 				//NOTE: movedir HAS to be normalized!
533 				if ( VectorLength( self->movedir ) )
534 				{//They can only be hidden from enemies looking in this direction
535 					VectorCopy( self->movedir, other->client->hiddenDir );
536 				}
537 				else
538 				{
539 					VectorClear( other->client->hiddenDir );
540 				}
541 				*/
542 				//Not using this, at least not yet.
543 			}
544 		}
545 	}
546 
547 	if ( self->spawnflags & 4 )
548 	{//USE_BUTTON
549 		if (other->client->ps.torsoAnim != BOTH_BUTTON_HOLD &&
550 			other->client->ps.torsoAnim != BOTH_CONSOLE1)
551 		{
552 			G_SetAnim( other, NULL, SETANIM_TORSO, BOTH_BUTTON_HOLD, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0 );
553 		}
554 		else
555 		{
556 			other->client->ps.torsoTimer = 500;
557 		}
558 		other->client->ps.weaponTime = other->client->ps.torsoTimer;
559 	}
560 
561 	if ( self->think == trigger_cleared_fire )
562 	{//We're waiting to fire our target2 first
563 		self->nextthink = level.time + self->speed;
564 		return;
565 	}
566 
567 	multi_trigger( self, other );
568 }
569 
trigger_cleared_fire(gentity_t * self)570 void trigger_cleared_fire (gentity_t *self)
571 {
572 	G_UseTargets2( self, self->activator, self->target2 );
573 	self->think = 0;
574 	// should start the wait timer now, because the trigger's just been cleared, so we must "wait" from this point
575 	if ( self->wait > 0 )
576 	{
577 		self->nextthink = level.time + ( self->wait + self->random * Q_flrand(-1.0f, 1.0f) ) * 1000;
578 	}
579 }
580 
581 /*QUAKED trigger_multiple (.1 .5 .1) ? CLIENTONLY FACING USE_BUTTON FIRE_BUTTON NPCONLY x x INACTIVE MULTIPLE
582 CLIENTONLY - only a player can trigger this by touch
583 FACING - Won't fire unless triggering ent's view angles are within 45 degrees of trigger's angles (in addition to any other conditions)
584 USE_BUTTON - Won't fire unless player is in it and pressing use button (in addition to any other conditions)
585 FIRE_BUTTON - Won't fire unless player/NPC is in it and pressing fire button (in addition to any other conditions)
586 NPCONLY - only non-player NPCs can trigger this by touch
587 INACTIVE - Start off, has to be activated to be touchable/usable
588 MULTIPLE - multiple entities can touch this trigger in a single frame *and* if needed, the trigger can have a wait of > 0
589 
590 "wait"		Seconds between triggerings, 0 default, number < 0 means one time only.
591 "random"	wait variance, default is 0
592 "delay"		how many seconds to wait to fire targets after tripped
593 "hiderange" As long as NPC's head is in this trigger, NPCs out of this hiderange cannot see him.  If you set an angle on the trigger, they're only hidden from enemies looking in that direction.  the player's crouch viewheight is 36, his standing viewheight is 54.  So a trigger thast should hide you when crouched but not standing should be 48 tall.
594 "target2"	The trigger will fire this only when the trigger has been activated and subsequently 'cleared'( once any of the conditions on the trigger have not been satisfied).  This will not fire the "target" more than once until the "target2" is fired (trigger field is 'cleared')
595 "speed"		How many seconds to wait to fire the target2, default is 1
596 "noise"		Sound to play when the trigger fires (plays at activator's origin)
597 "NPC_targetname"  Only the NPC with this NPC_targetname fires this trigger
598 
599 Variable sized repeatable trigger.  Must be targeted at one or more entities.
600 so, the basic time between firing is a random time between
601 (wait - random) and (wait + random)
602 
603 "team" - If set, only this team can trip this trigger
604 	0 - any
605 	1 - red
606 	2 - blue
607 
608 "soundSet"	Ambient sound set to play when this trigger is activated
609 
610 usetime		-	If specified (in milliseconds) along with the USE_BUTTON flag, will
611 				require a client to hold the use key for x amount of ms before firing.
612 
613 Applicable only during Siege gametype:
614 teamuser	-	if 1, team 2 can't use this. If 2, team 1 can't use this.
615 siegetrig	-	if non-0, can only be activated by players carrying a misc_siege_item
616 				which is associated with this trigger by the item's goaltarget value.
617 teambalance	-	if non-0, is "owned" by the last team that activated. Can only be activated
618 				by the other team if the number of players on the other team inside	the
619 				trigger outnumber the number of players on the owning team inside the
620 				trigger.
621 target3		-	fire when activated by team1
622 target4		-	fire when activated by team2
623 
624 idealclass	-	Can only be used by this class/these classes. You can specify use by
625 				multiple classes with the use of |, e.g.:
626 				"Imperial Medic|Imperial Assassin|Imperial Demolitionist"
627 */
SP_trigger_multiple(gentity_t * ent)628 void SP_trigger_multiple( gentity_t *ent )
629 {
630 	char	*s;
631 	if ( G_SpawnString( "noise", "", &s ) )
632 	{
633 		if (s && s[0])
634 		{
635 			ent->noise_index = G_SoundIndex(s);
636 		}
637 		else
638 		{
639 			ent->noise_index = 0;
640 		}
641 	}
642 
643 	G_SpawnInt("usetime", "0", &ent->genericValue7);
644 
645 	//For siege gametype
646 	G_SpawnInt("siegetrig", "0", &ent->genericValue1);
647     G_SpawnInt("teambalance", "0", &ent->genericValue2);
648 
649 	G_SpawnInt("delay", "0", &ent->delay);
650 
651 	if ( (ent->wait > 0) && (ent->random >= ent->wait) ) {
652 		ent->random = ent->wait - FRAMETIME;
653 		Com_Printf(S_COLOR_YELLOW"trigger_multiple has random >= wait\n");
654 	}
655 
656 	ent->delay *= 1000;//1 = 1 msec, 1000 = 1 sec
657 	if ( !ent->speed && ent->target2 && ent->target2[0] )
658 	{
659 		ent->speed = 1000;
660 	}
661 	else
662 	{
663 		ent->speed *= 1000;
664 	}
665 
666 	ent->touch = Touch_Multi;
667 	ent->use   = Use_Multi;
668 
669 	if ( ent->team && ent->team[0] )
670 	{
671 		ent->alliedTeam = atoi(ent->team);
672 		ent->team = NULL;
673 	}
674 
675 	InitTrigger( ent );
676 	trap->LinkEntity ((sharedEntity_t *)ent);
677 }
678 
679 
680 /*QUAKED trigger_once (.5 1 .5) ? CLIENTONLY FACING USE_BUTTON FIRE_BUTTON x x x INACTIVE MULTIPLE
681 CLIENTONLY - only a player can trigger this by touch
682 FACING - Won't fire unless triggering ent's view angles are within 45 degrees of trigger's angles (in addition to any other conditions)
683 USE_BUTTON - Won't fire unless player is in it and pressing use button (in addition to any other conditions)
684 FIRE_BUTTON - Won't fire unless player/NPC is in it and pressing fire button (in addition to any other conditions)
685 INACTIVE - Start off, has to be activated to be touchable/usable
686 MULTIPLE - multiple entities can touch this trigger in a single frame *and* if needed, the trigger can have a wait of > 0
687 
688 "random"	wait variance, default is 0
689 "delay"		how many seconds to wait to fire targets after tripped
690 Variable sized repeatable trigger.  Must be targeted at one or more entities.
691 so, the basic time between firing is a random time between
692 (wait - random) and (wait + random)
693 "noise"		Sound to play when the trigger fires (plays at activator's origin)
694 "NPC_targetname"  Only the NPC with this NPC_targetname fires this trigger
695 
696 "team" - If set, only this team can trip this trigger
697 	0 - any
698 	1 - red
699 	2 - blue
700 
701 "soundSet"	Ambient sound set to play when this trigger is activated
702 
703 usetime		-	If specified (in milliseconds) along with the USE_BUTTON flag, will
704 				require a client to hold the use key for x amount of ms before firing.
705 
706 Applicable only during Siege gametype:
707 teamuser - if 1, team 2 can't use this. If 2, team 1 can't use this.
708 siegetrig - if non-0, can only be activated by players carrying a misc_siege_item
709 			which is associated with this trigger by the item's goaltarget value.
710 
711 idealclass	-	Can only be used by this class/these classes. You can specify use by
712 				multiple classes with the use of |, e.g.:
713 				"Imperial Medic|Imperial Assassin|Imperial Demolitionist"
714 */
SP_trigger_once(gentity_t * ent)715 void SP_trigger_once( gentity_t *ent )
716 {
717 	char	*s;
718 	if ( G_SpawnString( "noise", "", &s ) )
719 	{
720 		if (s && s[0])
721 		{
722 			ent->noise_index = G_SoundIndex(s);
723 		}
724 		else
725 		{
726 			ent->noise_index = 0;
727 		}
728 	}
729 
730 	G_SpawnInt("usetime", "0", &ent->genericValue7);
731 
732 	//For siege gametype
733 	G_SpawnInt("siegetrig", "0", &ent->genericValue1);
734 
735 	G_SpawnInt("delay", "0", &ent->delay);
736 
737 	ent->wait = -1;
738 
739 	ent->touch = Touch_Multi;
740 	ent->use   = Use_Multi;
741 
742 	if ( ent->team && ent->team[0] )
743 	{
744 		ent->alliedTeam = atoi(ent->team);
745 		ent->team = NULL;
746 	}
747 
748 	ent->delay *= 1000;//1 = 1 msec, 1000 = 1 sec
749 
750 	InitTrigger( ent );
751 	trap->LinkEntity ((sharedEntity_t *)ent);
752 }
753 
754 /*
755 ======================================================================
756 trigger_lightningstrike -rww
757 ======================================================================
758 */
759 //lightning strike trigger lightning strike event
Do_Strike(gentity_t * ent)760 void Do_Strike(gentity_t *ent)
761 {
762 	trace_t localTrace;
763 	vec3_t strikeFrom;
764 	vec3_t strikePoint;
765 	vec3_t fxAng;
766 
767 	//maybe allow custom fx direction at some point?
768 	VectorSet(fxAng, 90.0f, 0.0f, 0.0f);
769 
770 	//choose a random point to strike within the bounds of the trigger
771 	strikePoint[0] = flrand(ent->r.absmin[0], ent->r.absmax[0]);
772 	strikePoint[1] = flrand(ent->r.absmin[1], ent->r.absmax[1]);
773 
774 	//consider the bottom mins the ground level
775 	strikePoint[2] = ent->r.absmin[2];
776 
777 	//set the from point
778 	strikeFrom[0] = strikePoint[0];
779 	strikeFrom[1] = strikePoint[1];
780 	strikeFrom[2] = ent->r.absmax[2]-4.0f;
781 
782 	//now trace for damaging stuff, and do the effect
783 	trap->Trace(&localTrace, strikeFrom, NULL, NULL, strikePoint, ent->s.number, MASK_PLAYERSOLID, qfalse, 0, 0);
784 	VectorCopy(localTrace.endpos, strikePoint);
785 
786 	if (localTrace.startsolid || localTrace.allsolid)
787 	{ //got a bad spot, think again next frame to try another strike
788 		ent->nextthink = level.time;
789 		return;
790 	}
791 
792 	if (ent->radius)
793 	{ //do a radius damage at the end pos
794 		G_RadiusDamage(strikePoint, ent, ent->damage, ent->radius, ent, NULL, MOD_SUICIDE);
795 	}
796 	else
797 	{ //only damage individuals
798 		gentity_t *trHit = &g_entities[localTrace.entityNum];
799 
800 		if (trHit->inuse && trHit->takedamage)
801 		{ //damage it then
802 			G_Damage(trHit, ent, ent, NULL, trHit->r.currentOrigin, ent->damage, 0, MOD_SUICIDE);
803 		}
804 	}
805 
806 	G_PlayEffectID(ent->genericValue2, strikeFrom, fxAng);
807 }
808 
809 //lightning strike trigger think loop
Think_Strike(gentity_t * ent)810 void Think_Strike(gentity_t *ent)
811 {
812 	if (ent->genericValue1)
813 	{ //turned off currently
814 		return;
815 	}
816 
817 	ent->nextthink = level.time + ent->wait + Q_irand(0, ent->random);
818 	Do_Strike(ent);
819 }
820 
821 //lightning strike trigger use event function
Use_Strike(gentity_t * ent,gentity_t * other,gentity_t * activator)822 void Use_Strike( gentity_t *ent, gentity_t *other, gentity_t *activator )
823 {
824 	ent->genericValue1 = !ent->genericValue1;
825 
826 	if (!ent->genericValue1)
827 	{ //turn it back on
828 		ent->nextthink = level.time;
829 	}
830 }
831 
832 /*QUAKED trigger_lightningstrike (.1 .5 .1) ? START_OFF
833 START_OFF - start trigger disabled
834 
835 "lightningfx"	effect to use for lightning, MUST be specified
836 "wait"			Seconds between strikes, 1000 default
837 "random"		wait variance, default is 2000
838 "dmg"			damage on strike (default 50)
839 "radius"		if non-0, does a radius damage at the lightning strike
840 				impact point (using this value as the radius). otherwise
841 				will only do line trace damage. default 0.
842 
843 use to toggle on and off
844 */
SP_trigger_lightningstrike(gentity_t * ent)845 void SP_trigger_lightningstrike( gentity_t *ent )
846 {
847 	char *s;
848 
849 	ent->use = Use_Strike;
850 	ent->think = Think_Strike;
851 	ent->nextthink = level.time + 500;
852 
853 	G_SpawnString("lightningfx", "", &s);
854 	if (!s || !s[0])
855 	{
856 		Com_Error(ERR_DROP, "trigger_lightningstrike with no lightningfx");
857 	}
858 
859 	//get a configstring index for it
860 	ent->genericValue2 = G_EffectIndex(s);
861 
862 	if (ent->spawnflags & 1)
863 	{ //START_OFF
864 		ent->genericValue1 = 1;
865 	}
866 
867 	if (!ent->wait)
868 	{ //default 1000
869 		ent->wait = 1000;
870 	}
871 	if (!ent->random)
872 	{ //default 2000
873 		ent->random = 2000;
874 	}
875 	if (!ent->damage)
876 	{ //default 50
877 		ent->damage = 50;
878 	}
879 
880 	InitTrigger( ent );
881 	trap->LinkEntity ((sharedEntity_t *)ent);
882 }
883 
884 
885 /*
886 ==============================================================================
887 
888 trigger_always
889 
890 ==============================================================================
891 */
892 
trigger_always_think(gentity_t * ent)893 void trigger_always_think( gentity_t *ent ) {
894 	G_UseTargets(ent, ent);
895 	G_FreeEntity( ent );
896 }
897 
898 /*QUAKED trigger_always (.5 .5 .5) (-8 -8 -8) (8 8 8)
899 This trigger will always fire.  It is activated by the world.
900 */
SP_trigger_always(gentity_t * ent)901 void SP_trigger_always (gentity_t *ent) {
902 	// we must have some delay to make sure our use targets are present
903 	ent->nextthink = level.time + 300;
904 	ent->think = trigger_always_think;
905 }
906 
907 
908 /*
909 ==============================================================================
910 
911 trigger_push
912 
913 ==============================================================================
914 */
915 //trigger_push
916 #define PUSH_LINEAR		4
917 #define PUSH_RELATIVE	16
918 #define PUSH_MULTIPLE	2048
919 //target_push
920 #define PUSH_CONSTANT	2
921 
trigger_push_touch(gentity_t * self,gentity_t * other,trace_t * trace)922 void trigger_push_touch (gentity_t *self, gentity_t *other, trace_t *trace ) {
923 	if ( self->flags & FL_INACTIVE )
924 	{//set by target_deactivate
925 		return;
926 	}
927 
928 	if ( !(self->spawnflags&PUSH_LINEAR) )
929 	{//normal throw
930 		if ( !other->client ) {
931 			return;
932 		}
933 		BG_TouchJumpPad( &other->client->ps, &self->s );
934 		return;
935 	}
936 
937 	//linear
938 	if( level.time < self->painDebounceTime + self->wait  ) // normal 'wait' check
939 	{
940 		if( self->spawnflags & PUSH_MULTIPLE ) // MULTIPLE - allow multiple entities to touch this trigger in one frame
941 		{
942 			if ( self->painDebounceTime && level.time > self->painDebounceTime ) // if we haven't reached the next frame continue to let ents touch the trigger
943 			{
944 				return;
945 			}
946 		}
947 		else // only allowing one ent per frame to touch trigger
948 		{
949 			return;
950 		}
951 	}
952 
953 	/*
954 	//???
955 	// if the player has already activated this trigger this frame
956 	if( other && !other->s.number && self->aimDebounceTime == level.time )
957 	{
958 		return;
959 	}
960 	*/
961 
962 	/*
963 	if( self->spawnflags & PUSH_CONVEYOR )
964 	{   // only push player if he's on the ground
965 		if( other->s.groundEntityNum == ENTITYNUM_NONE )
966 		{
967 			return;
968 		}
969 	}
970 	*/
971 
972 	/*
973 	if ( self->spawnflags & 1 )
974 	{//PLAYERONLY
975 		if ( other->s.number >= MAX_CLIENTS )
976 		{
977 			return;
978 		}
979 	}
980 	else
981 	{
982 		if ( self->spawnflags & 8 )
983 		{//NPCONLY
984 			if ( other->NPC == NULL )
985 			{
986 				return;
987 			}
988 		}
989 	}
990 	*/
991 
992 	if ( !other->client ) {
993 		if ( other->s.pos.trType != TR_STATIONARY && other->s.pos.trType != TR_LINEAR_STOP && other->s.pos.trType != TR_NONLINEAR_STOP && VectorLengthSquared( other->s.pos.trDelta ) )
994 		{//already moving
995 			VectorCopy( other->r.currentOrigin, other->s.pos.trBase );
996 			VectorCopy( self->s.origin2, other->s.pos.trDelta );
997 			other->s.pos.trTime = level.time;
998 		}
999 		return;
1000 	}
1001 
1002 	if ( other->client->ps.pm_type != PM_NORMAL
1003 		&& other->client->ps.pm_type != PM_DEAD
1004 		&& other->client->ps.pm_type != PM_FREEZE )
1005 	{
1006 		return;
1007 	}
1008 
1009 	if ( (self->spawnflags&PUSH_RELATIVE) )
1010 	{//relative, dir to it * speed
1011 		vec3_t dir;
1012 		VectorSubtract( self->s.origin2, other->r.currentOrigin, dir );
1013 		if ( self->speed )
1014 		{
1015 			VectorNormalize( dir );
1016 			VectorScale( dir, self->speed, dir );
1017 		}
1018 		VectorCopy( dir, other->client->ps.velocity );
1019 	}
1020 	else if ( (self->spawnflags&PUSH_LINEAR) )
1021 	{//linear dir * speed
1022 		VectorScale( self->s.origin2, self->speed, other->client->ps.velocity );
1023 	}
1024 	else
1025 	{
1026 		VectorCopy( self->s.origin2, other->client->ps.velocity );
1027 	}
1028 	//so we don't take damage unless we land lower than we start here...
1029 	/*
1030 	other->client->ps.forceJumpZStart = 0;
1031 	other->client->ps.pm_flags |= PMF_TRIGGER_PUSHED;//pushed by a trigger
1032 	other->client->ps.jumpZStart = other->client->ps.origin[2];
1033 	*/
1034 
1035 	if ( self->wait == -1 )
1036 	{
1037 		self->touch = NULL;
1038 	}
1039 	else if ( self->wait > 0 )
1040 	{
1041 		self->painDebounceTime = level.time;
1042 
1043 	}
1044 	/*
1045 	if( other && !other->s.number )
1046 	{	// mark that the player has activated this trigger this frame
1047 		self->aimDebounceTime =level.time;
1048 	}
1049 	*/
1050 }
1051 
1052 
1053 /*
1054 =================
1055 AimAtTarget
1056 
1057 Calculate origin2 so the target apogee will be hit
1058 =================
1059 */
AimAtTarget(gentity_t * self)1060 void AimAtTarget( gentity_t *self ) {
1061 	gentity_t	*ent;
1062 	vec3_t		origin;
1063 	float		height, gravity, time, forward;
1064 	float		dist;
1065 
1066 	VectorAdd( self->r.absmin, self->r.absmax, origin );
1067 	VectorScale ( origin, 0.5f, origin );
1068 
1069 	ent = G_PickTarget( self->target );
1070 	if ( !ent ) {
1071 		G_FreeEntity( self );
1072 		return;
1073 	}
1074 
1075 	if ( self->classname && !Q_stricmp( "trigger_push", self->classname ) )
1076 	{
1077 		if ( (self->spawnflags&PUSH_RELATIVE) )
1078 		{//relative, not an arc or linear
1079 			VectorCopy( ent->r.currentOrigin, self->s.origin2 );
1080 			return;
1081 		}
1082 		else if ( (self->spawnflags&PUSH_LINEAR) )
1083 		{//linear, not an arc
1084 			VectorSubtract( ent->r.currentOrigin, origin, self->s.origin2 );
1085 			VectorNormalize( self->s.origin2 );
1086 			return;
1087 		}
1088 	}
1089 
1090 	if ( self->classname && !Q_stricmp( "target_push", self->classname ) )
1091 	{
1092 		if( self->spawnflags & PUSH_CONSTANT )
1093 		{
1094 			VectorSubtract ( ent->s.origin, self->s.origin, self->s.origin2 );
1095 			VectorNormalize( self->s.origin2);
1096 			VectorScale (self->s.origin2, self->speed, self->s.origin2);
1097 			return;
1098 		}
1099 	}
1100 
1101 	height = ent->s.origin[2] - origin[2];
1102 	gravity = g_gravity.value;
1103 	time = sqrt( height / ( .5 * gravity ) );
1104 	if ( !time ) {
1105 		G_FreeEntity( self );
1106 		return;
1107 	}
1108 
1109 	// set s.origin2 to the push velocity
1110 	VectorSubtract ( ent->s.origin, origin, self->s.origin2 );
1111 	self->s.origin2[2] = 0;
1112 	dist = VectorNormalize( self->s.origin2);
1113 
1114 	forward = dist / time;
1115 	VectorScale( self->s.origin2, forward, self->s.origin2 );
1116 
1117 	self->s.origin2[2] = time * gravity;
1118 }
1119 
1120 
1121 /*QUAKED trigger_push (.5 .5 .5) ? x x LINEAR x RELATIVE x x INACTIVE MULTIPLE
1122 Must point at a target_position, which will be the apex of the leap.
1123 This will be client side predicted, unlike target_push
1124 
1125 LINEAR - Instead of tossing the client at the target_position, it will push them towards it.  Must set a "speed" (see below)
1126 RELATIVE - instead of pushing you in a direction that is always from the center of the trigger to the target_position, it pushes *you* toward the target position, relative to your current location (can use with "speed"... if don't set a speed, it will use the distance from you to the target_position)
1127 INACTIVE - not active until targeted by a target_activate
1128 MULTIPLE - multiple entities can touch this trigger in a single frame *and* if needed, the trigger can have a wait of > 0
1129 
1130 wait - how long to wait between pushes: -1 = push only once
1131 speed - when used with the LINEAR spawnflag, pushes the client toward the position at a constant speed (default is 1000)
1132 */
SP_trigger_push(gentity_t * self)1133 void SP_trigger_push( gentity_t *self ) {
1134 	InitTrigger (self);
1135 
1136 	// unlike other triggers, we need to send this one to the client
1137 	self->r.svFlags &= ~SVF_NOCLIENT;
1138 
1139 	// make sure the client precaches this sound
1140 	G_SoundIndex("sound/weapons/force/jump.wav");
1141 
1142 	self->s.eType = ET_PUSH_TRIGGER;
1143 
1144 	if ( !(self->spawnflags&2) )
1145 	{//start on
1146 		self->touch = trigger_push_touch;
1147 	}
1148 
1149 	if ( self->spawnflags & 4 )
1150 	{//linear
1151 		self->speed = 1000;
1152 	}
1153 
1154 	self->think = AimAtTarget;
1155 	self->nextthink = level.time + FRAMETIME;
1156 	trap->LinkEntity ((sharedEntity_t *)self);
1157 }
1158 
Use_target_push(gentity_t * self,gentity_t * other,gentity_t * activator)1159 void Use_target_push( gentity_t *self, gentity_t *other, gentity_t *activator ) {
1160 	if ( !activator->client ) {
1161 		return;
1162 	}
1163 
1164 	if ( activator->client->ps.pm_type != PM_NORMAL && activator->client->ps.pm_type != PM_FLOAT ) {
1165 		return;
1166 	}
1167 
1168 	G_ActivateBehavior(self,BSET_USE);
1169 
1170 	VectorCopy (self->s.origin2, activator->client->ps.velocity);
1171 
1172 	// play fly sound every 1.5 seconds
1173 	if ( activator->fly_sound_debounce_time < level.time ) {
1174 		activator->fly_sound_debounce_time = level.time + 1500;
1175 		if (self->noise_index)
1176 		{
1177 			G_Sound( activator, CHAN_AUTO, self->noise_index );
1178 		}
1179 	}
1180 }
1181 
1182 /*QUAKED target_push (.5 .5 .5) (-8 -8 -8) (8 8 8) bouncepad CONSTANT
1183 CONSTANT will push activator in direction of 'target' at constant 'speed'
1184 
1185 Pushes the activator in the direction.of angle, or towards a target apex.
1186 "speed"		defaults to 1000
1187 if "bouncepad", play bounce noise instead of none
1188 */
SP_target_push(gentity_t * self)1189 void SP_target_push( gentity_t *self ) {
1190 	if (!self->speed) {
1191 		self->speed = 1000;
1192 	}
1193 	G_SetMovedir (self->s.angles, self->s.origin2);
1194 	VectorScale (self->s.origin2, self->speed, self->s.origin2);
1195 
1196 	if ( self->spawnflags & 1 ) {
1197 		self->noise_index = G_SoundIndex("sound/weapons/force/jump.wav");
1198 	} else {
1199 		self->noise_index = 0;	//G_SoundIndex("sound/misc/windfly.wav");
1200 	}
1201 	if ( self->target ) {
1202 		VectorCopy( self->s.origin, self->r.absmin );
1203 		VectorCopy( self->s.origin, self->r.absmax );
1204 		self->think = AimAtTarget;
1205 		self->nextthink = level.time + FRAMETIME;
1206 	}
1207 	self->use = Use_target_push;
1208 }
1209 
1210 /*
1211 ==============================================================================
1212 
1213 trigger_teleport
1214 
1215 ==============================================================================
1216 */
1217 
trigger_teleporter_touch(gentity_t * self,gentity_t * other,trace_t * trace)1218 void trigger_teleporter_touch (gentity_t *self, gentity_t *other, trace_t *trace ) {
1219 	gentity_t	*dest;
1220 
1221 	if ( self->flags & FL_INACTIVE )
1222 	{//set by target_deactivate
1223 		return;
1224 	}
1225 
1226 	if ( !other->client ) {
1227 		return;
1228 	}
1229 	if ( other->client->ps.pm_type == PM_DEAD ) {
1230 		return;
1231 	}
1232 	// Spectators only?
1233 	if ( ( self->spawnflags & 1 ) &&
1234 		other->client->sess.sessionTeam != TEAM_SPECTATOR ) {
1235 		return;
1236 	}
1237 
1238 
1239 	dest = 	G_PickTarget( self->target );
1240 	if (!dest) {
1241 		trap->Print ("Couldn't find teleporter destination\n");
1242 		return;
1243 	}
1244 
1245 	TeleportPlayer( other, dest->s.origin, dest->s.angles );
1246 }
1247 
1248 
1249 /*QUAKED trigger_teleport (.5 .5 .5) ? SPECTATOR
1250 Allows client side prediction of teleportation events.
1251 Must point at a target_position, which will be the teleport destination.
1252 
1253 If spectator is set, only spectators can use this teleport
1254 Spectator teleporters are not normally placed in the editor, but are created
1255 automatically near doors to allow spectators to move through them
1256 */
SP_trigger_teleport(gentity_t * self)1257 void SP_trigger_teleport( gentity_t *self ) {
1258 	InitTrigger (self);
1259 
1260 	// unlike other triggers, we need to send this one to the client
1261 	// unless is a spectator trigger
1262 	if ( self->spawnflags & 1 ) {
1263 		self->r.svFlags |= SVF_NOCLIENT;
1264 	} else {
1265 		self->r.svFlags &= ~SVF_NOCLIENT;
1266 	}
1267 
1268 	// make sure the client precaches this sound
1269 	G_SoundIndex("sound/weapons/force/speed.wav");
1270 
1271 	self->s.eType = ET_TELEPORT_TRIGGER;
1272 	self->touch = trigger_teleporter_touch;
1273 
1274 	trap->LinkEntity ((sharedEntity_t *)self);
1275 }
1276 
1277 
1278 /*
1279 ==============================================================================
1280 
1281 trigger_hurt
1282 
1283 ==============================================================================
1284 */
1285 
1286 /*QUAKED trigger_hurt (.5 .5 .5) ? START_OFF CAN_TARGET SILENT NO_PROTECTION SLOW
1287 Any entity that touches this will be hurt.
1288 It does dmg points of damage each server frame
1289 Targeting the trigger will toggle its on / off state.
1290 
1291 CAN_TARGET		if you target it, it will toggle on and off
1292 SILENT			supresses playing the sound
1293 SLOW			changes the damage rate to once per second
1294 NO_PROTECTION	*nothing* stops the damage
1295 
1296 "team"			team (1 or 2) to allow hurting (if none then hurt anyone) only applicable for siege
1297 "dmg"			default 5 (whole numbers only)
1298 If dmg is set to -1 this brush will use the fade-kill method
1299 
1300 */
hurt_use(gentity_t * self,gentity_t * other,gentity_t * activator)1301 void hurt_use( gentity_t *self, gentity_t *other, gentity_t *activator ) {
1302 	if (activator && activator->inuse && activator->client)
1303 	{
1304 		self->activator = activator;
1305 	}
1306 	else
1307 	{
1308 		self->activator = NULL;
1309 	}
1310 
1311 	G_ActivateBehavior(self,BSET_USE);
1312 
1313 	if ( self->r.linked ) {
1314 		trap->UnlinkEntity( (sharedEntity_t *)self );
1315 	} else {
1316 		trap->LinkEntity( (sharedEntity_t *)self );
1317 	}
1318 }
1319 
hurt_touch(gentity_t * self,gentity_t * other,trace_t * trace)1320 void hurt_touch( gentity_t *self, gentity_t *other, trace_t *trace ) {
1321 	int		dflags;
1322 
1323 	if (level.gametype == GT_SIEGE && self->team && self->team[0])
1324 	{
1325 		int team = atoi(self->team);
1326 
1327 		if (other->inuse && other->s.number < MAX_CLIENTS && other->client &&
1328 			other->client->sess.sessionTeam != team)
1329 		{ //real client don't hurt
1330 			return;
1331 		}
1332 		else if (other->inuse && other->client && other->s.eType == ET_NPC &&
1333 			other->s.NPC_class == CLASS_VEHICLE && other->s.teamowner != team)
1334 		{ //vehicle owned by team don't hurt
1335 			return;
1336 		}
1337 	}
1338 
1339 	if ( self->flags & FL_INACTIVE )
1340 	{//set by target_deactivate
1341 		return;
1342 	}
1343 
1344 	if ( !other->takedamage ) {
1345 		return;
1346 	}
1347 
1348 	if ( self->timestamp > level.time ) {
1349 		return;
1350 	}
1351 
1352 	if (self->damage == -1 && other && other->client && other->health < 1)
1353 	{
1354 		other->client->ps.fallingToDeath = 0;
1355 		ClientRespawn(other);
1356 		return;
1357 	}
1358 
1359 	if (self->damage == -1 && other && other->client && other->client->ps.fallingToDeath)
1360 	{
1361 		return;
1362 	}
1363 
1364 	if ( self->spawnflags & 16 ) {
1365 		self->timestamp = level.time + 1000;
1366 	} else {
1367 		self->timestamp = level.time + FRAMETIME;
1368 	}
1369 
1370 	// play sound
1371 	/*
1372 	if ( !(self->spawnflags & 4) && self->damage != -1 ) {
1373 		G_Sound( other, CHAN_AUTO, self->noise_index );
1374 	}
1375 	*/
1376 
1377 	if (self->spawnflags & 8)
1378 		dflags = DAMAGE_NO_PROTECTION;
1379 	else
1380 		dflags = 0;
1381 
1382 	if (self->damage == -1 && other && other->client)
1383 	{
1384 		if (other->client->ps.otherKillerTime > level.time)
1385 		{ //we're as good as dead, so if someone pushed us into this then remember them
1386 			other->client->ps.otherKillerTime = level.time + 20000;
1387 			other->client->ps.otherKillerDebounceTime = level.time + 10000;
1388 		}
1389 		other->client->ps.fallingToDeath = level.time;
1390 
1391 		//rag on the way down, this flag will automatically be cleared for us on respawn
1392 		other->client->ps.eFlags |= EF_RAG;
1393 
1394 		//make sure his jetpack is off
1395 		Jetpack_Off(other);
1396 
1397 		if (other->NPC)
1398 		{ //kill it now
1399 			vec3_t vDir;
1400 
1401 			VectorSet(vDir, 0, 1, 0);
1402 			G_Damage(other, other, other, vDir, other->client->ps.origin, Q3_INFINITE, 0, MOD_FALLING);
1403 		}
1404 		else
1405 		{
1406 			G_EntitySound(other, CHAN_VOICE, G_SoundIndex("*falling1.wav"));
1407 		}
1408 
1409 		self->timestamp = 0; //do not ignore others
1410 	}
1411 	else
1412 	{
1413 		int dmg = self->damage;
1414 
1415 		if (dmg == -1)
1416 		{ //so fall-to-blackness triggers destroy evertyhing
1417 			dmg = 99999;
1418 			self->timestamp = 0;
1419 		}
1420 		if (self->activator && self->activator->inuse && self->activator->client)
1421 		{
1422 			G_Damage (other, self->activator, self->activator, NULL, NULL, dmg, dflags|DAMAGE_NO_PROTECTION, MOD_TRIGGER_HURT);
1423 		}
1424 		else
1425 		{
1426 			G_Damage (other, self, self, NULL, NULL, dmg, dflags|DAMAGE_NO_PROTECTION, MOD_TRIGGER_HURT);
1427 		}
1428 	}
1429 }
1430 
SP_trigger_hurt(gentity_t * self)1431 void SP_trigger_hurt( gentity_t *self ) {
1432 	InitTrigger (self);
1433 
1434 	gTrigFallSound = G_SoundIndex("*falling1.wav");
1435 
1436 	self->noise_index = G_SoundIndex( "sound/weapons/force/speed.wav" );
1437 	self->touch = hurt_touch;
1438 
1439 	if ( !self->damage ) {
1440 		self->damage = 5;
1441 	}
1442 
1443 	self->r.contents = CONTENTS_TRIGGER;
1444 
1445 	if ( self->spawnflags & 2 ) {
1446 		self->use = hurt_use;
1447 	}
1448 
1449 	// link in to the world if starting active
1450 	if ( ! (self->spawnflags & 1) ) {
1451 		trap->LinkEntity ((sharedEntity_t *)self);
1452 	}
1453 	else if (self->r.linked)
1454 	{
1455 		trap->UnlinkEntity((sharedEntity_t *)self);
1456 	}
1457 }
1458 
1459 #define	INITIAL_SUFFOCATION_DELAY	500 //.5 seconds
space_touch(gentity_t * self,gentity_t * other,trace_t * trace)1460 void space_touch( gentity_t *self, gentity_t *other, trace_t *trace )
1461 {
1462 	if (!other || !other->inuse || !other->client )
1463 		//NOTE: we need vehicles to know this, too...
1464 		//|| other->s.number >= MAX_CLIENTS)
1465 	{
1466 		return;
1467 	}
1468 
1469 	if ( other->s.number < MAX_CLIENTS//player
1470 		&& other->client->ps.m_iVehicleNum//in a vehicle
1471 		&& other->client->ps.m_iVehicleNum >= MAX_CLIENTS )
1472 	{//a player client inside a vehicle
1473 		gentity_t *veh = &g_entities[other->client->ps.m_iVehicleNum];
1474 
1475 		if (veh->inuse && veh->client && veh->m_pVehicle &&
1476 			veh->m_pVehicle->m_pVehicleInfo->hideRider)
1477 		{ //if they are "inside" a vehicle, then let that protect them from THE HORRORS OF SPACE.
1478 			other->client->inSpaceSuffocation = 0;
1479 			other->client->inSpaceIndex = ENTITYNUM_NONE;
1480 			return;
1481 		}
1482 	}
1483 
1484 	if (!G_PointInBounds(other->client->ps.origin, self->r.absmin, self->r.absmax))
1485 	{ //his origin must be inside the trigger
1486 		return;
1487 	}
1488 
1489 	if (!other->client->inSpaceIndex ||
1490 		other->client->inSpaceIndex == ENTITYNUM_NONE)
1491 	{ //freshly entering space
1492 		other->client->inSpaceSuffocation = level.time + INITIAL_SUFFOCATION_DELAY;
1493 	}
1494 
1495 	other->client->inSpaceIndex = self->s.number;
1496 }
1497 
1498 /*QUAKED trigger_space (.5 .5 .5) ?
1499 causes human clients to suffocate and have no gravity.
1500 
1501 */
SP_trigger_space(gentity_t * self)1502 void SP_trigger_space(gentity_t *self)
1503 {
1504 	InitTrigger(self);
1505 	self->r.contents = CONTENTS_TRIGGER;
1506 
1507 	self->touch = space_touch;
1508 
1509     trap->LinkEntity((sharedEntity_t *)self);
1510 }
1511 
shipboundary_touch(gentity_t * self,gentity_t * other,trace_t * trace)1512 void shipboundary_touch( gentity_t *self, gentity_t *other, trace_t *trace )
1513 {
1514 	gentity_t *ent;
1515 
1516 	if (!other || !other->inuse || !other->client ||
1517 		other->s.number < MAX_CLIENTS ||
1518 		!other->m_pVehicle)
1519 	{ //only let vehicles touch
1520 		return;
1521 	}
1522 
1523 	if ( other->client->ps.hyperSpaceTime && level.time - other->client->ps.hyperSpaceTime < HYPERSPACE_TIME )
1524 	{//don't interfere with hyperspacing ships
1525 		return;
1526 	}
1527 
1528 	ent = G_Find (NULL, FOFS(targetname), self->target);
1529 	if (!ent || !ent->inuse)
1530 	{ //this is bad
1531 		trap->Error( ERR_DROP, "trigger_shipboundary has invalid target '%s'\n", self->target );
1532 		return;
1533 	}
1534 
1535 	if (!other->client->ps.m_iVehicleNum || other->m_pVehicle->m_iRemovedSurfaces)
1536 	{ //if a vehicle touches a boundary without a pilot in it or with parts missing, just blow the thing up
1537 		G_Damage(other, other, other, NULL, other->client->ps.origin, 99999, DAMAGE_NO_PROTECTION, MOD_SUICIDE);
1538 		return;
1539 	}
1540 
1541 	//make sure this sucker is linked so the prediction knows where to go
1542 	trap->LinkEntity((sharedEntity_t *)ent);
1543 
1544 	other->client->ps.vehTurnaroundIndex = ent->s.number;
1545 	other->client->ps.vehTurnaroundTime = level.time + (self->genericValue1*2);
1546 
1547 	//keep up the detailed checks for another 2 seconds
1548 	self->genericValue7 = level.time + 2000;
1549 }
1550 
shipboundary_think(gentity_t * ent)1551 void shipboundary_think(gentity_t *ent)
1552 {
1553 	int			iEntityList[MAX_GENTITIES];
1554 	int			numListedEntities;
1555 	int			i = 0;
1556 	gentity_t	*listedEnt;
1557 
1558 	ent->nextthink = level.time + 100;
1559 
1560 	if (ent->genericValue7 < level.time)
1561 	{ //don't need to be doing this check, no one has touched recently
1562 		return;
1563 	}
1564 
1565 	numListedEntities = trap->EntitiesInBox( ent->r.absmin, ent->r.absmax, iEntityList, MAX_GENTITIES );
1566 	while (i < numListedEntities)
1567 	{
1568 		listedEnt = &g_entities[iEntityList[i]];
1569 		if (listedEnt->inuse && listedEnt->client && listedEnt->client->ps.m_iVehicleNum)
1570 		{
1571             if (listedEnt->s.eType == ET_NPC &&
1572 				listedEnt->s.NPC_class == CLASS_VEHICLE)
1573 			{
1574 				Vehicle_t *pVeh = listedEnt->m_pVehicle;
1575 				if (pVeh && pVeh->m_pVehicleInfo->type == VH_FIGHTER)
1576 				{
1577                     shipboundary_touch(ent, listedEnt, NULL);
1578 				}
1579 			}
1580 		}
1581 		i++;
1582 	}
1583 }
1584 
1585 /*QUAKED trigger_shipboundary (.5 .5 .5) ?
1586 causes vehicle to turn toward target and travel in that direction for a set time when hit.
1587 
1588 "target"		name of entity to turn toward (can be info_notnull, or whatever).
1589 "traveltime"	time to travel in this direction
1590 
1591 */
SP_trigger_shipboundary(gentity_t * self)1592 void SP_trigger_shipboundary(gentity_t *self)
1593 {
1594 	InitTrigger(self);
1595 	self->r.contents = CONTENTS_TRIGGER;
1596 
1597 	if (!self->target || !self->target[0])
1598 	{
1599 		trap->Error( ERR_DROP, "trigger_shipboundary without a target." );
1600 	}
1601 	G_SpawnInt("traveltime", "0", &self->genericValue1);
1602 
1603 	if (!self->genericValue1)
1604 	{
1605 		trap->Error( ERR_DROP, "trigger_shipboundary without traveltime." );
1606 	}
1607 
1608 	self->think = shipboundary_think;
1609 	self->nextthink = level.time + 500;
1610 	self->touch = shipboundary_touch;
1611 
1612     trap->LinkEntity((sharedEntity_t *)self);
1613 }
1614 
hyperspace_touch(gentity_t * self,gentity_t * other,trace_t * trace)1615 void hyperspace_touch( gentity_t *self, gentity_t *other, trace_t *trace )
1616 {
1617 	gentity_t *ent;
1618 
1619 	if (!other || !other->inuse || !other->client ||
1620 		other->s.number < MAX_CLIENTS ||
1621 		!other->m_pVehicle)
1622 	{ //only let vehicles touch
1623 		return;
1624 	}
1625 
1626 	if ( other->client->ps.hyperSpaceTime && level.time - other->client->ps.hyperSpaceTime < HYPERSPACE_TIME )
1627 	{//already hyperspacing, just keep us moving
1628 		if ( (other->client->ps.eFlags2&EF2_HYPERSPACE) )
1629 		{//they've started the hyperspace but haven't been teleported yet
1630 			float timeFrac = ((float)(level.time-other->client->ps.hyperSpaceTime))/HYPERSPACE_TIME;
1631 			if ( timeFrac >= HYPERSPACE_TELEPORT_FRAC )
1632 			{//half-way, now teleport them!
1633 				vec3_t	diff, fwd, right, up, newOrg;
1634 				float	fDiff, rDiff, uDiff;
1635 				//take off the flag so we only do this once
1636 				other->client->ps.eFlags2 &= ~EF2_HYPERSPACE;
1637 				//Get the offset from the local position
1638 				ent = G_Find (NULL, FOFS(targetname), self->target);
1639 				if (!ent || !ent->inuse)
1640 				{ //this is bad
1641 					trap->Error( ERR_DROP, "trigger_hyperspace has invalid target '%s'\n", self->target );
1642 					return;
1643 				}
1644 				VectorSubtract( other->client->ps.origin, ent->s.origin, diff );
1645 				AngleVectors( ent->s.angles, fwd, right, up );
1646 				fDiff = DotProduct( fwd, diff );
1647 				rDiff = DotProduct( right, diff );
1648 				uDiff = DotProduct( up, diff );
1649 				//Now get the base position of the destination
1650 				ent = G_Find (NULL, FOFS(targetname), self->target2);
1651 				if (!ent || !ent->inuse)
1652 				{ //this is bad
1653 					trap->Error( ERR_DROP, "trigger_hyperspace has invalid target2 '%s'\n", self->target2 );
1654 					return;
1655 				}
1656 				VectorCopy( ent->s.origin, newOrg );
1657 				//finally, add the offset into the new origin
1658 				AngleVectors( ent->s.angles, fwd, right, up );
1659 				VectorMA( newOrg, fDiff, fwd, newOrg );
1660 				VectorMA( newOrg, rDiff, right, newOrg );
1661 				VectorMA( newOrg, uDiff, up, newOrg );
1662 				//trap->Print("hyperspace from %s to %s\n", vtos(other->client->ps.origin), vtos(newOrg) );
1663 				//now put them in the offset position, facing the angles that position wants them to be facing
1664 				TeleportPlayer( other, newOrg, ent->s.angles );
1665 				if ( other->m_pVehicle && other->m_pVehicle->m_pPilot )
1666 				{//teleport the pilot, too
1667 					TeleportPlayer( (gentity_t*)other->m_pVehicle->m_pPilot, newOrg, ent->s.angles );
1668 					//FIXME: and the passengers?
1669 				}
1670 				//make them face the new angle
1671 				//other->client->ps.hyperSpaceIndex = ent->s.number;
1672 				VectorCopy( ent->s.angles, other->client->ps.hyperSpaceAngles );
1673 				//sound
1674 				G_Sound( other, CHAN_LOCAL, G_SoundIndex( "sound/vehicles/common/hyperend.wav" ) );
1675 			}
1676 		}
1677 		return;
1678 	}
1679 	else
1680 	{
1681 		ent = G_Find (NULL, FOFS(targetname), self->target);
1682 		if (!ent || !ent->inuse)
1683 		{ //this is bad
1684 			trap->Error( ERR_DROP, "trigger_hyperspace has invalid target '%s'\n", self->target );
1685 			return;
1686 		}
1687 
1688 		if (!other->client->ps.m_iVehicleNum || other->m_pVehicle->m_iRemovedSurfaces)
1689 		{ //if a vehicle touches a boundary without a pilot in it or with parts missing, just blow the thing up
1690 			G_Damage(other, other, other, NULL, other->client->ps.origin, 99999, DAMAGE_NO_PROTECTION, MOD_SUICIDE);
1691 			return;
1692 		}
1693 		//other->client->ps.hyperSpaceIndex = ent->s.number;
1694 		VectorCopy( ent->s.angles, other->client->ps.hyperSpaceAngles );
1695 		other->client->ps.hyperSpaceTime = level.time;
1696 	}
1697 }
1698 
1699 /*
1700 void trigger_hyperspace_find_targets( gentity_t *self )
1701 {
1702 	gentity_t *targEnt = NULL;
1703 	targEnt = G_Find (NULL, FOFS(targetname), self->target);
1704 	if (!targEnt || !targEnt->inuse)
1705 	{ //this is bad
1706 		trap->Error( ERR_DROP, "trigger_hyperspace has invalid target '%s'\n", self->target );
1707 		return;
1708 	}
1709 	targEnt->r.svFlags |= SVF_BROADCAST;//crap, need to tell the cgame about the target_position
1710 	targEnt = G_Find (NULL, FOFS(targetname), self->target2);
1711 	if (!targEnt || !targEnt->inuse)
1712 	{ //this is bad
1713 		trap->Error( ERR_DROP, "trigger_hyperspace has invalid target2 '%s'\n", self->target2 );
1714 		return;
1715 	}
1716 	targEnt->r.svFlags |= SVF_BROADCAST;//crap, need to tell the cgame about the target_position
1717 }
1718 */
1719 /*QUAKED trigger_hyperspace (.5 .5 .5) ?
1720 Ship will turn to face the angles of the first target_position then fly forward, playing the hyperspace effect, then pop out at a relative point around the target
1721 
1722 "target"		whatever position the ship teleports from in relation to the target_position specified here, that's the relative position the ship will spawn at around the target2 target_position
1723 "target2"		name of target_position to teleport the ship to (will be relative to it's origin)
1724 */
SP_trigger_hyperspace(gentity_t * self)1725 void SP_trigger_hyperspace(gentity_t *self)
1726 {
1727 	//register the hyperspace end sound (start sounds are customized)
1728 	G_SoundIndex( "sound/vehicles/common/hyperend.wav" );
1729 
1730 	InitTrigger(self);
1731 	self->r.contents = CONTENTS_TRIGGER;
1732 
1733 	if (!self->target || !self->target[0])
1734 	{
1735 		trap->Error( ERR_DROP, "trigger_hyperspace without a target." );
1736 	}
1737 	if (!self->target2 || !self->target2[0])
1738 	{
1739 		trap->Error( ERR_DROP, "trigger_hyperspace without a target2." );
1740 	}
1741 
1742 	self->delay = Distance( self->r.absmax, self->r.absmin );//my size
1743 
1744 	self->touch = hyperspace_touch;
1745 
1746     trap->LinkEntity((sharedEntity_t *)self);
1747 
1748 	//self->think = trigger_hyperspace_find_targets;
1749 	//self->nextthink = level.time + FRAMETIME;
1750 }
1751 /*
1752 ==============================================================================
1753 
1754 timer
1755 
1756 ==============================================================================
1757 */
1758 
1759 
1760 /*QUAKED func_timer (0.3 0.1 0.6) (-8 -8 -8) (8 8 8) START_ON
1761 This should be renamed trigger_timer...
1762 Repeatedly fires its targets.
1763 Can be turned on or off by using.
1764 
1765 "wait"			base time between triggering all targets, default is 1
1766 "random"		wait variance, default is 0
1767 so, the basic time between firing is a random time between
1768 (wait - random) and (wait + random)
1769 
1770 */
func_timer_think(gentity_t * self)1771 void func_timer_think( gentity_t *self ) {
1772 	G_UseTargets (self, self->activator);
1773 	// set time before next firing
1774 	self->nextthink = level.time + 1000 * ( self->wait + Q_flrand(-1.0f, 1.0f) * self->random );
1775 }
1776 
func_timer_use(gentity_t * self,gentity_t * other,gentity_t * activator)1777 void func_timer_use( gentity_t *self, gentity_t *other, gentity_t *activator ) {
1778 	self->activator = activator;
1779 
1780 	G_ActivateBehavior(self,BSET_USE);
1781 
1782 	// if on, turn it off
1783 	if ( self->nextthink ) {
1784 		self->nextthink = 0;
1785 		return;
1786 	}
1787 
1788 	// turn it on
1789 	func_timer_think (self);
1790 }
1791 
SP_func_timer(gentity_t * self)1792 void SP_func_timer( gentity_t *self ) {
1793 	G_SpawnFloat( "random", "1", &self->random);
1794 	G_SpawnFloat( "wait", "1", &self->wait );
1795 
1796 	self->use = func_timer_use;
1797 	self->think = func_timer_think;
1798 
1799 	if ( self->random >= self->wait ) {
1800 		self->random = self->wait - 1;//NOTE: was - FRAMETIME, but FRAMETIME is in msec (100) and these numbers are in *seconds*!
1801 		trap->Print( "func_timer at %s has random >= wait\n", vtos( self->s.origin ) );
1802 	}
1803 
1804 	if ( self->spawnflags & 1 ) {
1805 		self->nextthink = level.time + FRAMETIME;
1806 		self->activator = self;
1807 	}
1808 
1809 	self->r.svFlags = SVF_NOCLIENT;
1810 }
1811 
asteroid_pick_random_asteroid(gentity_t * self)1812 gentity_t *asteroid_pick_random_asteroid( gentity_t *self )
1813 {
1814 	int			t_count = 0, pick;
1815 	gentity_t	*t = NULL;
1816 
1817 	while ( (t = G_Find (t, FOFS(targetname), self->target)) != NULL )
1818 	{
1819 		if (t != self)
1820 		{
1821 			t_count++;
1822 		}
1823 	}
1824 
1825 	if(!t_count)
1826 	{
1827 		return NULL;
1828 	}
1829 
1830 	if(t_count == 1)
1831 	{
1832 		return t;
1833 	}
1834 
1835 	//FIXME: need a seed
1836 	pick = Q_irand(1, t_count);
1837 	t_count = 0;
1838 	while ( (t = G_Find (t, FOFS(targetname), self->target)) != NULL )
1839 	{
1840 		if (t != self)
1841 		{
1842 			t_count++;
1843 		}
1844 		else
1845 		{
1846 			continue;
1847 		}
1848 
1849 		if(t_count == pick)
1850 		{
1851 			return t;
1852 		}
1853 	}
1854 	return NULL;
1855 }
1856 
asteroid_count_num_asteroids(gentity_t * self)1857 int asteroid_count_num_asteroids( gentity_t *self )
1858 {
1859 	int	i, count = 0;
1860 
1861 	for ( i = MAX_CLIENTS; i < ENTITYNUM_WORLD; i++ )
1862 	{
1863 		if ( !g_entities[i].inuse )
1864 		{
1865 			continue;
1866 		}
1867 		if ( g_entities[i].r.ownerNum == self->s.number )
1868 		{
1869 			count++;
1870 		}
1871 	}
1872 	return count;
1873 }
1874 
1875 extern void SP_func_rotating (gentity_t *ent);
1876 extern void Q3_Lerp2Origin( int taskID, int entID, vec3_t origin, float duration );
asteroid_field_think(gentity_t * self)1877 void asteroid_field_think(gentity_t *self)
1878 {
1879 	int numAsteroids = asteroid_count_num_asteroids( self );
1880 
1881 	self->nextthink = level.time + 500;
1882 
1883 	if ( numAsteroids < self->count )
1884 	{
1885 		//need to spawn a new asteroid
1886 		gentity_t *newAsteroid = G_Spawn();
1887 		if ( newAsteroid )
1888 		{
1889 			vec3_t startSpot, endSpot, startAngles;
1890 			float dist, speed = flrand( self->speed * 0.25f, self->speed * 2.0f );
1891 			int	capAxis, axis, time = 0;
1892 			gentity_t *copyAsteroid = asteroid_pick_random_asteroid( self );
1893 			if ( copyAsteroid )
1894 			{
1895 				newAsteroid->model = copyAsteroid->model;
1896 				newAsteroid->model2 = copyAsteroid->model2;
1897 				newAsteroid->health = copyAsteroid->health;
1898 				newAsteroid->spawnflags = copyAsteroid->spawnflags;
1899 				newAsteroid->mass = copyAsteroid->mass;
1900 				newAsteroid->damage = copyAsteroid->damage;
1901 				newAsteroid->speed = copyAsteroid->speed;
1902 
1903 				G_SetOrigin( newAsteroid, copyAsteroid->s.origin );
1904 				G_SetAngles( newAsteroid, copyAsteroid->s.angles );
1905 				newAsteroid->classname = "func_rotating";
1906 
1907 				SP_func_rotating( newAsteroid );
1908 
1909 				newAsteroid->genericValue15 = copyAsteroid->genericValue15;
1910 				newAsteroid->s.iModelScale = copyAsteroid->s.iModelScale;
1911 				newAsteroid->maxHealth = newAsteroid->health;
1912 				G_ScaleNetHealth(newAsteroid);
1913 				newAsteroid->radius = copyAsteroid->radius;
1914 				newAsteroid->material = copyAsteroid->material;
1915 				//CacheChunkEffects( self->material );
1916 
1917 				//keep track of it
1918 				newAsteroid->r.ownerNum = self->s.number;
1919 
1920 				//move it
1921 				capAxis = Q_irand( 0, 2 );
1922 				for ( axis = 0; axis < 3; axis++ )
1923 				{
1924 					if ( axis == capAxis )
1925 					{
1926 						if ( Q_irand( 0, 1 ) )
1927 						{
1928 							startSpot[axis] = self->r.mins[axis];
1929 							endSpot[axis] = self->r.maxs[axis];
1930 						}
1931 						else
1932 						{
1933 							startSpot[axis] = self->r.maxs[axis];
1934 							endSpot[axis] = self->r.mins[axis];
1935 						}
1936 					}
1937 					else
1938 					{
1939 						startSpot[axis] = self->r.mins[axis]+(flrand(0,1.0f)*(self->r.maxs[axis]-self->r.mins[axis]));
1940 						endSpot[axis] = self->r.mins[axis]+(flrand(0,1.0f)*(self->r.maxs[axis]-self->r.mins[axis]));
1941 					}
1942 				}
1943 				//FIXME: maybe trace from start to end to make sure nothing is in the way?  How big of a trace?
1944 
1945 				G_SetOrigin( newAsteroid, startSpot );
1946 				dist = Distance( endSpot, startSpot );
1947 				time = ceil(dist/speed)*1000;
1948 				Q3_Lerp2Origin( -1, newAsteroid->s.number, endSpot, time );
1949 
1950 				//spin it
1951 				startAngles[0] = flrand( -360, 360 );
1952 				startAngles[1] = flrand( -360, 360 );
1953 				startAngles[2] = flrand( -360, 360 );
1954 				G_SetAngles( newAsteroid, startAngles );
1955 				newAsteroid->s.apos.trDelta[0] = flrand( -100, 100 );
1956 				newAsteroid->s.apos.trDelta[1] = flrand( -100, 100 );
1957 				newAsteroid->s.apos.trDelta[2] = flrand( -100, 100 );
1958 				newAsteroid->s.apos.trTime = level.time;
1959 				newAsteroid->s.apos.trType = TR_LINEAR;
1960 
1961 				//remove itself when done
1962 				newAsteroid->think = G_FreeEntity;
1963 				newAsteroid->nextthink = level.time+time;
1964 
1965 				//think again sooner if need even more
1966 				if ( numAsteroids+1 < self->count )
1967 				{//still need at least one more
1968 					//spawn it in 100ms
1969 					self->nextthink = level.time + 100;
1970 				}
1971 			}
1972 		}
1973 	}
1974 }
1975 
1976 /*QUAKED trigger_asteroid_field (.5 .5 .5) ?
1977 speed - how fast, on average, the asteroid moves
1978 count - how many asteroids, max, to have at one time
1979 target - target this at func_rotating asteroids
1980 */
SP_trigger_asteroid_field(gentity_t * self)1981 void SP_trigger_asteroid_field(gentity_t *self)
1982 {
1983 	trap->SetBrushModel( (sharedEntity_t *)self, self->model );
1984 	self->r.contents = 0;
1985 
1986 	if ( !self->count )
1987 	{
1988 		self->health = 20;
1989 	}
1990 
1991 	if ( !self->speed )
1992 	{
1993 		self->speed = 10000;
1994 	}
1995 
1996 	self->think = asteroid_field_think;
1997 	self->nextthink = level.time + 100;
1998 
1999     trap->LinkEntity((sharedEntity_t *)self);
2000 }
2001