1 /*
2 ===========================================================================
3 
4 Return to Castle Wolfenstein single player GPL Source Code
5 Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company.
6 
7 This file is part of the Return to Castle Wolfenstein single player GPL Source Code (“RTCW SP Source Code”).
8 
9 RTCW SP Source Code is free software: you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation, either version 3 of the License, or
12 (at your option) any later version.
13 
14 RTCW SP Source Code 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 RTCW SP Source Code.  If not, see <http://www.gnu.org/licenses/>.
21 
22 In addition, the RTCW SP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW SP Source Code.  If not, please request a copy in writing from id Software at the address below.
23 
24 If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
25 
26 ===========================================================================
27 */
28 
29 #include "g_local.h"
30 
31 
InitTrigger(gentity_t * self)32 void InitTrigger( gentity_t *self ) {
33 	if ( !VectorCompare( self->s.angles, vec3_origin ) ) {
34 		G_SetMovedir( self->s.angles, self->movedir );
35 	}
36 
37 	trap_SetBrushModel( self, self->model );
38 
39 	self->r.contents = CONTENTS_TRIGGER;        // replaces the -1 from trap_SetBrushModel
40 	self->r.svFlags = SVF_NOCLIENT;
41 }
42 
43 
44 // the wait time has passed, so set back up for another activation
multi_wait(gentity_t * ent)45 void multi_wait( gentity_t *ent ) {
46 	ent->nextthink = 0;
47 }
48 
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(gentity_t * ent,gentity_t * activator)53 void multi_trigger( gentity_t *ent, gentity_t *activator ) {
54 	ent->activator = activator;
55 	if ( ent->nextthink ) {
56 		return;     // can't retrigger until the wait is over
57 	}
58 
59 	G_UseTargets( ent, ent->activator );
60 
61 	if ( ent->wait > 0 ) {
62 		ent->think = multi_wait;
63 		ent->nextthink = level.time + ( ent->wait + ent->random * crandom() ) * 1000;
64 	} else {
65 		// we can't just remove (self) here, because this is a touch function
66 		// called while looping through area links...
67 		ent->touch = 0;
68 		ent->nextthink = level.time + FRAMETIME;
69 		ent->think = G_FreeEntity;
70 	}
71 }
72 
Use_Multi(gentity_t * ent,gentity_t * other,gentity_t * activator)73 void Use_Multi( gentity_t *ent, gentity_t *other, gentity_t *activator ) {
74 	multi_trigger( ent, activator );
75 }
76 
Touch_Multi(gentity_t * self,gentity_t * other,trace_t * trace)77 void Touch_Multi( gentity_t *self, gentity_t *other, trace_t *trace ) {
78 	if ( !other->client ) {
79 		return;
80 	}
81 
82 	if ( !( self->spawnflags & 1 ) ) { // denotes AI_Touch flag
83 		if ( other->aiCharacter ) {
84 			return;
85 		}
86 	}
87 	multi_trigger( self, other );
88 }
89 
Enable_Trigger_Touch(gentity_t * ent)90 void Enable_Trigger_Touch( gentity_t *ent ) {
91 	gentity_t *targ;
92 	gentity_t *daent;
93 	trace_t tr;
94 	int mask = MASK_SHOT;
95 	int targTemp1, targTemp2;
96 	int entTemp1, entTemp2;
97 	vec3_t dir, forward, kvel;
98 	float angle;
99 	qboolean thisone = qfalse;
100 
101 
102 	// ent->touch = Touch_Multi;
103 
104 
105 	// find the client number that uses this entity
106 	targ = AICast_FindEntityForName( ent->aiName );
107 	if ( !targ ) {
108 		return;
109 	} else
110 	{
111 		// bail if GIBFLAG and targ has been jibbed
112 		if ( targ->health <= GIB_HEALTH  && ( ent->spawnflags & 2 ) ) {
113 			return;
114 		}
115 
116 		// need to make the ent solid since it is a trigger
117 
118 		entTemp1 = ent->clipmask;
119 		entTemp2 = ent->r.contents;
120 
121 		ent->clipmask   = CONTENTS_SOLID;
122 		ent->r.contents = CONTENTS_SOLID;
123 
124 		trap_LinkEntity( ent );
125 
126 		// same with targ cause targ is dead
127 
128 		targTemp1 = targ->clipmask;
129 		targTemp2 = targ->r.contents;
130 
131 		targ->clipmask   = CONTENTS_SOLID;
132 		targ->r.contents = CONTENTS_SOLID;
133 
134 		trap_LinkEntity( targ );
135 
136 		trap_Trace( &tr, targ->client->ps.origin, targ->r.mins, targ->r.maxs, targ->client->ps.origin, targ->s.number, mask );
137 
138 		if ( tr.startsolid ) {
139 			daent = &g_entities[ tr.entityNum ];
140 
141 			if ( daent == ent ) { // wooo hooo
142 				multi_trigger( ent, targ );
143 				thisone = qtrue;
144 			}
145 		}
146 
147 		// ok were done set it contents back
148 
149 		ent->clipmask = entTemp1;
150 		ent->r.contents = entTemp2;
151 
152 		trap_LinkEntity( ent );
153 
154 		targ->clipmask = targTemp1;
155 		targ->r.contents = targTemp2;
156 
157 		trap_LinkEntity( targ );
158 
159 		if ( ent->s.angles2[YAW] && thisone ) {
160 			angle = ent->s.angles2[YAW];
161 
162 			VectorClear( dir );
163 			VectorClear( targ->client->ps.velocity );
164 
165 			dir[YAW] = angle;
166 			AngleVectors( dir, forward, NULL, NULL );
167 
168 			VectorScale( forward, 32, kvel );
169 			VectorAdd( targ->client->ps.velocity, kvel, targ->client->ps.velocity );
170 		}
171 	}
172 
173 }
174 
175 /*QUAKED trigger_multiple (.5 .5 .5) ? AI_Touch
176 "wait" : Seconds between triggerings, 0.5 default, -1 = one time only.
177 "random"	wait variance, default is 0
178 Variable sized repeatable trigger.  Must be targeted at one or more entities.
179 so, the basic time between firing is a random time between
180 (wait - random) and (wait + random)
181 */
SP_trigger_multiple(gentity_t * ent)182 void SP_trigger_multiple( gentity_t *ent ) {
183 	G_SpawnFloat( "wait", "0.5", &ent->wait );
184 	G_SpawnFloat( "random", "0", &ent->random );
185 
186 	if ( ent->random >= ent->wait && ent->wait >= 0 ) {
187 		ent->random = ent->wait - FRAMETIME;
188 		G_Printf( "trigger_multiple has random >= wait\n" );
189 	}
190 
191 	ent->touch = Touch_Multi;
192 	ent->use = Use_Multi;
193 
194 	InitTrigger( ent );
195 	trap_LinkEntity( ent );
196 }
197 
198 
199 
200 /*
201 ==============================================================================
202 
203 trigger_always
204 
205 ==============================================================================
206 */
207 
trigger_always_think(gentity_t * ent)208 void trigger_always_think( gentity_t *ent ) {
209 	G_UseTargets( ent, ent );
210 	G_FreeEntity( ent );
211 }
212 
213 /*QUAKED trigger_always (.5 .5 .5) (-8 -8 -8) (8 8 8)
214 This trigger will always fire.  It is activated by the world.
215 */
SP_trigger_always(gentity_t * ent)216 void SP_trigger_always( gentity_t *ent ) {
217 	// we must have some delay to make sure our use targets are present
218 	ent->nextthink = level.time + 300;
219 	ent->think = trigger_always_think;
220 }
221 
222 
223 /*
224 ==============================================================================
225 
226 trigger_push
227 
228 ==============================================================================
229 */
230 
trigger_push_touch(gentity_t * self,gentity_t * other,trace_t * trace)231 void trigger_push_touch( gentity_t *self, gentity_t *other, trace_t *trace ) {
232 
233 	if ( ( self->spawnflags & 4 ) && other->r.svFlags & SVF_CASTAI ) {
234 		return;
235 	}
236 
237 	if ( !other->client ) {
238 		return;
239 	}
240 
241 	if ( other->client->ps.pm_type != PM_NORMAL ) {
242 		return;
243 	}
244 	if ( other->client->ps.powerups[PW_FLIGHT] ) {
245 		return;
246 	}
247 
248 //----(SA) commented out as we have no hook
249 //	if (other->client && other->client->hook)
250 //		Weapon_HookFree(other->client->hook);
251 
252 	if ( other->client->ps.velocity[2] < 100 ) {
253 		// don't play the event sound again if we are in a fat trigger
254 		G_AddPredictableEvent( other, EV_JUMP_PAD, 0 );
255 	}
256 	VectorCopy( self->s.origin2, other->client->ps.velocity );
257 
258 	if ( self->spawnflags & 2 ) {
259 		G_FreeEntity( self );
260 	}
261 }
262 
263 
264 /*
265 =================
266 AimAtTarget
267 
268 Calculate origin2 so the target apogee will be hit
269 =================
270 */
AimAtTarget(gentity_t * self)271 void AimAtTarget( gentity_t *self ) {
272 	gentity_t   *ent;
273 	vec3_t origin;
274 	float height, gravity, time, forward;
275 	float dist;
276 
277 	VectorAdd( self->r.absmin, self->r.absmax, origin );
278 	VectorScale( origin, 0.5, origin );
279 
280 	ent = G_PickTarget( self->target );
281 	if ( !ent ) {
282 		G_FreeEntity( self );
283 		return;
284 	}
285 
286 	height = ent->s.origin[2] - origin[2];
287 	gravity = g_gravity.value;
288 	time = sqrt( fabs( height / ( 0.5f * gravity ) ) );
289 	if ( !time ) {
290 		G_FreeEntity( self );
291 		return;
292 	}
293 
294 	// set s.origin2 to the push velocity
295 	VectorSubtract( ent->s.origin, origin, self->s.origin2 );
296 	self->s.origin2[2] = 0;
297 	dist = VectorNormalize( self->s.origin2 );
298 
299 	forward = dist / time;
300 	VectorScale( self->s.origin2, forward, self->s.origin2 );
301 
302 	self->s.origin2[2] = time * gravity;
303 }
304 
trigger_push_use(gentity_t * self,gentity_t * other,gentity_t * activator)305 void trigger_push_use( gentity_t *self, gentity_t *other, gentity_t *activator ) {
306 	self->touch = trigger_push_touch;
307 	trap_LinkEntity( self );
308 }
309 
310 /*QUAKED trigger_push (.5 .5 .5) ? TOGGLE REMOVEAFTERTOUCH PUSHPLAYERONLY
311 Must point at a target_position, which will be the apex of the leap.
312 This will be client side predicted, unlike target_push
313 */
SP_trigger_push(gentity_t * self)314 void SP_trigger_push( gentity_t *self ) {
315 //	InitTrigger (self);
316 
317 // init trigger
318 	if ( !VectorCompare( self->s.angles, vec3_origin ) ) {
319 		G_SetMovedir( self->s.angles, self->movedir );
320 	}
321 
322 	trap_SetBrushModel( self, self->model );
323 
324 	self->r.contents = CONTENTS_TRIGGER;        // replaces the -1 from trap_SetBrushModel
325 	self->r.svFlags = SVF_NOCLIENT;
326 //----(SA)	end
327 
328 	// unlike other triggers, we need to send this one to the client
329 //	self->r.svFlags &= ~SVF_NOCLIENT;
330 
331 	// make sure the client precaches this sound
332 	//G_SoundIndex("sound/world/jumppad.wav");
333 
334 	if ( !( self->spawnflags & 1 ) ) { // toggle
335 		self->s.eType = ET_PUSH_TRIGGER;
336 	}
337 
338 	self->touch = trigger_push_touch;
339 	self->think = AimAtTarget;
340 
341 	if ( self->spawnflags & 1 ) { // toggle
342 		self->use = trigger_push_use;
343 		self->touch = 0;
344 		trap_UnlinkEntity( self );
345 	} else {
346 		trap_LinkEntity( self );
347 	}
348 
349 	self->nextthink = level.time + FRAMETIME;
350 //	trap_LinkEntity (self);
351 }
352 
353 
Use_target_push(gentity_t * self,gentity_t * other,gentity_t * activator)354 void Use_target_push( gentity_t *self, gentity_t *other, gentity_t *activator ) {
355 	if ( !activator->client ) {
356 		return;
357 	}
358 
359 	if ( activator->client->ps.pm_type != PM_NORMAL ) {
360 		return;
361 	}
362 	if ( activator->client->ps.powerups[PW_FLIGHT] ) {
363 		return;
364 	}
365 
366 	VectorCopy( self->s.origin2, activator->client->ps.velocity );
367 
368 	// play fly sound every 1.5 seconds
369 	if ( activator->fly_sound_debounce_time < level.time ) {
370 		activator->fly_sound_debounce_time = level.time + 1500;
371 		G_Sound( activator, self->noise_index );
372 	}
373 }
374 
375 /*QUAKED target_push (.5 .5 .5) (-8 -8 -8) (8 8 8) bouncepad
376 Pushes the activator in the direction.of angle, or towards a target apex.
377 "speed"		defaults to 1000
378 if "bouncepad", play bounce noise instead of windfly
379 */
SP_target_push(gentity_t * self)380 void SP_target_push( gentity_t *self ) {
381 	if ( !self->speed ) {
382 		self->speed = 1000;
383 	}
384 	G_SetMovedir( self->s.angles, self->s.origin2 );
385 	VectorScale( self->s.origin2, self->speed, self->s.origin2 );
386 
387 	if ( self->spawnflags & 1 ) {
388 		//self->noise_index = G_SoundIndex("sound/world/jumppad.wav");
389 	} else {
390 		self->noise_index = G_SoundIndex( "sound/misc/windfly.wav" );
391 	}
392 	if ( self->target ) {
393 		VectorCopy( self->s.origin, self->r.absmin );
394 		VectorCopy( self->s.origin, self->r.absmax );
395 		self->think = AimAtTarget;
396 		self->nextthink = level.time + FRAMETIME;
397 	}
398 	self->use = Use_target_push;
399 }
400 
401 /*
402 ==============================================================================
403 
404 trigger_teleport
405 
406 ==============================================================================
407 */
408 
trigger_teleporter_touch(gentity_t * self,gentity_t * other,trace_t * trace)409 void trigger_teleporter_touch( gentity_t *self, gentity_t *other, trace_t *trace ) {
410 	gentity_t   *dest;
411 
412 	if ( !other->client ) {
413 		return;
414 	}
415 	if ( other->client->ps.pm_type == PM_DEAD ) {
416 		return;
417 	}
418 
419 	dest =  G_PickTarget( self->target );
420 	if ( !dest ) {
421 		G_Printf( "Couldn't find teleporter destination\n" );
422 		return;
423 	}
424 
425 	TeleportPlayer( other, dest->s.origin, dest->s.angles );
426 }
427 
428 
429 /*QUAKED trigger_teleport (.5 .5 .5) ?
430 Allows client side prediction of teleportation events.
431 Must point at a target_position, which will be the teleport destination.
432 */
SP_trigger_teleport(gentity_t * self)433 void SP_trigger_teleport( gentity_t *self ) {
434 	InitTrigger( self );
435 
436 	// unlike other triggers, we need to send this one to the client
437 	self->r.svFlags &= ~SVF_NOCLIENT;
438 
439 	// make sure the client precaches this sound
440 	//G_SoundIndex("sound/world/jumppad.wav");
441 
442 	self->s.eType = ET_TELEPORT_TRIGGER;
443 	self->touch = trigger_teleporter_touch;
444 
445 	trap_LinkEntity( self );
446 }
447 
448 
449 
450 /*
451 ==============================================================================
452 
453 trigger_hurt
454 
455 ==============================================================================
456 */
457 
458 /*QUAKED trigger_hurt (.5 .5 .5) ? START_OFF PLAYER_ONLY SILENT NO_PROTECTION SLOW ONCE
459 Any entity that touches this will be hurt.
460 It does dmg points of damage each server frame
461 Targeting the trigger will toggle its on / off state.
462 
463 PLAYER_ONLY   - only damages the player
464 SILENT        - supresses playing the sound
465 NO_PROTECTION - *nothing* stops the damage
466 SLOW          - changes the damage rate to once per second
467 
468 "dmg"			default 5 (whole numbers only)
469 
470 "life"	time this brush will exist if value is zero will live for ever ei 0.5 sec 2.sec
471 default is zero
472 
473 the entity must be used first before it will count down its life
474 */
hurt_touch(gentity_t * self,gentity_t * other,trace_t * trace)475 void hurt_touch( gentity_t *self, gentity_t *other, trace_t *trace ) {
476 	int dflags;
477 
478 	if ( !other->takedamage ) {
479 		return;
480 	}
481 
482 //----(SA)	player damage only
483 	if ( self->spawnflags & 2 ) {
484 		if ( other->aiCharacter ) {
485 			return;
486 		}
487 	}
488 //----(SA)	end
489 
490 	if ( self->timestamp > level.time ) {
491 		return;
492 	}
493 
494 	if ( self->spawnflags & 16 ) {
495 		self->timestamp = level.time + 1000;
496 	} else {
497 		self->timestamp = level.time + FRAMETIME;
498 	}
499 
500 	// play sound
501 	if ( !( self->spawnflags & 4 ) ) {
502 		G_Sound( other, self->noise_index );
503 	}
504 
505 	if ( self->spawnflags & 8 ) {
506 		dflags = DAMAGE_NO_PROTECTION;
507 	} else {
508 		dflags = 0;
509 	}
510 	G_Damage( other, self, self, NULL, NULL, self->damage, dflags, MOD_TRIGGER_HURT );
511 
512 	if ( self->spawnflags & 32 ) {
513 		self->touch = 0;
514 	}
515 }
516 
hurt_think(gentity_t * ent)517 void hurt_think( gentity_t *ent ) {
518 	ent->nextthink = level.time + FRAMETIME;
519 
520 	if ( ent->wait < level.time ) {
521 		G_FreeEntity( ent );
522 	}
523 
524 }
525 
hurt_use(gentity_t * self,gentity_t * other,gentity_t * activator)526 void hurt_use( gentity_t *self, gentity_t *other, gentity_t *activator ) {
527 	if ( self->touch ) {
528 		self->touch = 0;
529 	} else {
530 		self->touch = hurt_touch;
531 	}
532 
533 	if ( self->delay ) {
534 		self->nextthink = level.time + 50;
535 		self->think = hurt_think;
536 		self->wait = level.time + ( self->delay * 1000 );
537 	}
538 }
539 
540 /*
541 ==============
542 SP_trigger_hurt
543 ==============
544 */
SP_trigger_hurt(gentity_t * self)545 void SP_trigger_hurt( gentity_t *self ) {
546 
547 	char    *life;
548 	float dalife;
549 
550 	InitTrigger( self );
551 
552 	self->noise_index = G_SoundIndex( "sound/world/hurt_me.wav" );
553 
554 	if ( !self->damage ) {
555 		self->damage = 5;
556 	}
557 
558 	self->r.contents = CONTENTS_TRIGGER;
559 
560 	self->use = hurt_use;
561 
562 	// link in to the world if starting active
563 	if ( !( self->spawnflags & 1 ) ) {
564 		//----(SA)	any reason this needs to be linked? (predicted?)
565 //		trap_LinkEntity (self);
566 		self->touch = hurt_touch;
567 	}
568 
569 	G_SpawnString( "life", "0", &life );
570 	dalife = atof( life );
571 	self->delay = dalife;
572 
573 }
574 
575 
576 /*
577 ==============================================================================
578 
579 timer
580 
581 ==============================================================================
582 */
583 
584 
585 /*QUAKED func_timer (0.3 0.1 0.6) (-8 -8 -8) (8 8 8) START_ON
586 This should be renamed trigger_timer...
587 Repeatedly fires its targets.
588 Can be turned on or off by using.
589 
590 "wait"			base time between triggering all targets, default is 1
591 "random"		wait variance, default is 0
592 so, the basic time between firing is a random time between
593 (wait - random) and (wait + random)
594 
595 */
func_timer_think(gentity_t * self)596 void func_timer_think( gentity_t *self ) {
597 	G_UseTargets( self, self->activator );
598 	// set time before next firing
599 	self->nextthink = level.time + 1000 * ( self->wait + crandom() * self->random );
600 }
601 
func_timer_use(gentity_t * self,gentity_t * other,gentity_t * activator)602 void func_timer_use( gentity_t *self, gentity_t *other, gentity_t *activator ) {
603 	self->activator = activator;
604 
605 	// if on, turn it off
606 	if ( self->nextthink ) {
607 		self->nextthink = 0;
608 		return;
609 	}
610 
611 	// turn it on
612 	func_timer_think( self );
613 }
614 
SP_func_timer(gentity_t * self)615 void SP_func_timer( gentity_t *self ) {
616 	G_SpawnFloat( "random", "1", &self->random );
617 	G_SpawnFloat( "wait", "1", &self->wait );
618 
619 	self->use = func_timer_use;
620 	self->think = func_timer_think;
621 
622 	if ( self->random >= self->wait ) {
623 		self->random = self->wait - FRAMETIME;
624 		G_Printf( "func_timer at %s has random >= wait\n", vtos( self->s.origin ) );
625 	}
626 
627 	if ( self->spawnflags & 1 ) {
628 		self->nextthink = level.time + FRAMETIME;
629 		self->activator = self;
630 	}
631 
632 	self->r.svFlags = SVF_NOCLIENT;
633 }
634 
635 
636 
637 
638 //---- (SA) Wolf triggers
639 
640 
641 /*QUAKED trigger_once (.5 .5 .5) ? AI_Touch
642 Must be targeted at one or more entities.
643 Once triggered, this entity is destroyed
644 (you can actually do the same thing with trigger_multiple with a wait of -1)
645 */
SP_trigger_once(gentity_t * ent)646 void SP_trigger_once( gentity_t *ent ) {
647 	ent->wait   = -1;           // this will remove itself after one use
648 	ent->touch  = Touch_Multi;
649 	ent->use    = Use_Multi;
650 
651 	InitTrigger( ent );
652 	trap_LinkEntity( ent );
653 }
654 
655 //---- end
656 
657 /*QUAKED trigger_deathCheck (.5 .5 .5) ? - GIBFLAG
658 GIBFLAG entity will never fire its target(s) if aiName entity was gibbed
659 aiName entity making alertentity call
660 
661 this entity will test if aiName is in its volume
662 
663 Must be targeted at one or more entities.
664 Once triggered, this entity is destroyed
665 */
SP_trigger_deathCheck(gentity_t * ent)666 void SP_trigger_deathCheck( gentity_t *ent ) {
667 	VectorCopy( ent->s.angles, ent->s.angles2 );
668 
669 	if ( !( ent->aiName ) ) {
670 		G_Error( "trigger_once_enabledeath does not have an aiName \n" );
671 	}
672 
673 	ent->wait   = -1;           // this will remove itself after one use
674 	ent->AIScript_AlertEntity = Enable_Trigger_Touch;
675 	ent->use    = Use_Multi;
676 
677 	InitTrigger( ent );
678 
679 	trap_LinkEntity( ent );
680 }
681 
682 
683 /*QUAKED trigger_aidoor (.5 .5 .5) ?
684 These entities must be placed on all doors one for each side of the door
685 this will enable ai's to operate the door and help in preventing ai's and
686 the player from getting stuck when the door is deciding which way to open
687 */
688 
trigger_aidoor_stayopen(gentity_t * ent,gentity_t * other,trace_t * trace)689 void trigger_aidoor_stayopen( gentity_t * ent, gentity_t * other, trace_t * trace ) {
690 	gentity_t *door;
691 
692 	if ( other->client && other->health > 0 ) {
693 		if ( !ent->target || !( strlen( ent->target ) ) ) {
694 			// ent->target of "" will crash game in Q_stricmp()
695 			G_Printf( "trigger_aidoor at loc %s does not have a target\n", vtos( ent->s.origin ) );
696 			return;
697 		}
698 
699 		door = G_Find( NULL, FOFS( targetname ), ent->target );
700 
701 		if ( !door ) {
702 			G_Printf( "trigger_aidoor at loc %s cannot find target '%s'\n", vtos( ent->s.origin ), ent->target );
703 			return;
704 		}
705 
706 		if ( door->moverState == MOVER_POS2ROTATE ) {     // door is in open state waiting to close keep it open
707 			door->nextthink = level.time + door->wait + 3000;
708 		}
709 
710 //----(SA)	added
711 		if ( door->moverState == MOVER_POS2 ) {   // door is in open state waiting to close keep it open
712 			door->nextthink = level.time + door->wait + 3000;
713 		}
714 //----(SA)	end
715 
716 		// Ridah, door isn't ready, find a free ai_marker, and wait there until it's open
717 		if ( other->r.svFlags & SVF_CASTAI ) {
718 
719 			// we dont have keys, so assume we are not trying to get through this door
720 			if ( door->key > KEY_NONE /*&& door->key < KEY_NUM_KEYS*/ ) {  // door requires key
721 				return;
722 			}
723 
724 			G_Activate( door, other );
725 
726 			// if the door isn't currently opening for us, we should move out the way
727 			// Ridah, had to change this, since it was causing AI to wait at door when the door is open, and won't close because they are sitting on the aidoor brush
728 			// TTimo: gcc: suggest parentheses around && within ||
729 			//   woa this test gets a nasty look
730 			if (
731 				( door->grenadeFired > level.time ) ||
732 				(
733 					!(
734 						( door->activator == other ) &&
735 						( door->moverState != MOVER_POS1 ) &&
736 						( door->moverState != MOVER_POS1ROTATE )
737 						)
738 					&& ( door->moverState != MOVER_POS2ROTATE )
739 					&& ( door->moverState != MOVER_POS2 )
740 				)
741 				) {
742 				// if we aren't already heading for an ai_marker, look for one we can go to
743 				AICast_AIDoor_Touch( other, ent, door );
744 			}
745 		}
746 	}
747 
748 }
749 
SP_trigger_aidoor(gentity_t * ent)750 void SP_trigger_aidoor( gentity_t *ent ) {
751 	if ( !ent->targetname ) {
752 		G_Printf( "trigger_aidoor at loc %s does not have a targetname for ai_marker assignments\n", vtos( ent->s.origin ) );
753 	}
754 
755 	ent->touch = trigger_aidoor_stayopen;
756 	InitTrigger( ent );
757 	trap_LinkEntity( ent );
758 }
759 
760 
gas_touch(gentity_t * ent,gentity_t * other,trace_t * trace)761 void gas_touch( gentity_t *ent, gentity_t *other, trace_t *trace ) {
762 	gentity_t       *traceEnt;
763 	trace_t tr;
764 	vec3_t dir;
765 	int damage = 1;
766 
767 	if ( !other->client ) {
768 		return;
769 	}
770 
771 	if ( ent->s.density == 5 ) {
772 		ent->touch = 0;
773 		damage = 5;
774 	}
775 
776 	trap_Trace( &tr, ent->r.currentOrigin, NULL, NULL, other->r.currentOrigin, ent->s.number, MASK_SHOT );
777 
778 	if ( tr.surfaceFlags & SURF_NOIMPACT ) {
779 		return;
780 	}
781 
782 	traceEnt = &g_entities[ tr.entityNum ];
783 
784 	if ( traceEnt->aiSkin && strstr( traceEnt->aiSkin, "venom" ) ) {
785 		return;
786 	}
787 
788 	if ( traceEnt->takedamage ) {
789 
790 		VectorClear( dir );
791 
792 		G_Damage( traceEnt, ent, ent, dir, tr.endpos,
793 				  damage, 0, MOD_POISONGAS );
794 	}
795 }
796 
gas_think(gentity_t * ent)797 void gas_think( gentity_t *ent ) {
798 	gentity_t *tent;
799 
800 	ent->count++;
801 
802 	if ( ent->health < ent->count ) {
803 		ent->think = G_FreeEntity;
804 		if ( ent->s.density == 5 ) {
805 			ent->nextthink = level.time + FRAMETIME;
806 		} else {
807 			ent->nextthink = level.time + 3000;
808 		}
809 		return;
810 	}
811 
812 	ent->r.maxs[0] = ent->r.maxs[1] = ent->r.maxs[2]++;
813 	ent->r.mins[0] = ent->r.mins[1] = ent->r.mins[2]--;
814 
815 	ent->nextthink = level.time + FRAMETIME;
816 
817 	tent = G_TempEntity( ent->r.currentOrigin, EV_SMOKE );
818 	VectorCopy( ent->r.currentOrigin, tent->s.origin );
819 
820 	if ( ent->s.density == 5 ) {
821 		tent->s.time = 500;
822 		tent->s.time2 = 100;
823 		tent->s.density = 5;
824 
825 		tent->s.angles2[0] = 8;
826 		tent->s.angles2[1] = 32;
827 	} else
828 	{
829 		tent->s.time = 5000;
830 		tent->s.time2 = 3000;
831 		tent->s.density = 5;
832 
833 		tent->s.angles2[0] = 24;
834 		tent->s.angles2[1] = 96;
835 	}
836 
837 	trap_LinkEntity( ent );
838 }
839 
840 /*QUAKED test_gas (0 0.5 0) (-4 -4 -4) (4 4 4)
841 */
SP_gas(gentity_t * self)842 void SP_gas( gentity_t *self ) {
843 	self->think = gas_think;
844 	self->nextthink = level.time + FRAMETIME;
845 	self->r.contents = CONTENTS_TRIGGER;
846 	self->touch = gas_touch;
847 	trap_LinkEntity( self );
848 
849 	if ( !self->health ) {
850 		self->health = 100;
851 	}
852 }
853 
854 
855 // DHM - Nerve :: Multiplayer triggers
856 
857 #define RED_FLAG 1
858 #define BLUE_FLAG 2
859 
860 /*QUAKED trigger_flagonly (.5 .5 .5) ? RED_FLAG BLUE_FLAG
861 Player must be carrying the proper flag for it to trigger.
862 It will call the "death" function in the object's script.
863 
864 "scriptName"	The object name in the script file
865 
866 RED_FLAG -- only trigger if player is carrying red flag
867 BLUE_FLAG -- only trigger if player is carrying blue flag
868 */
869 
Touch_flagonly(gentity_t * ent,gentity_t * other,trace_t * trace)870 void Touch_flagonly( gentity_t *ent, gentity_t *other, trace_t *trace ) {
871 
872 	if ( !other->client ) {
873 		return;
874 	}
875 
876 	if ( ent->spawnflags & RED_FLAG && other->client->ps.powerups[ PW_REDFLAG ] ) {
877 
878 		G_Script_ScriptEvent( ent, "death", "" );
879 
880 		// Removes itself
881 		ent->touch = 0;
882 		ent->nextthink = level.time + FRAMETIME;
883 		ent->think = G_FreeEntity;
884 	} else if ( ent->spawnflags & BLUE_FLAG && other->client->ps.powerups[ PW_BLUEFLAG ] )   {
885 
886 		G_Script_ScriptEvent( ent, "death", "" );
887 
888 		// Removes itself
889 		ent->touch = 0;
890 		ent->nextthink = level.time + FRAMETIME;
891 		ent->think = G_FreeEntity;
892 	}
893 }
894 
SP_trigger_flagonly(gentity_t * ent)895 void SP_trigger_flagonly( gentity_t *ent ) {
896 	ent->touch  = Touch_flagonly;
897 
898 	InitTrigger( ent );
899 	trap_LinkEntity( ent );
900 }
901 
902 
903 
904 /*QUAKED trigger_objective_info (.5 .5 .5) ? AXIS_OBJECTIVE ALLIED_OBJECTIVE
905 Players in this field will see a message saying that they are near an objective.
906 You specify which objective it is with a number in "count"
907 
908   "count"		The objective number
909   "track"		If this is specified, it will override the default message
910 */
911 #define AXIS_OBJECTIVE      1
912 #define ALLIED_OBJECTIVE    2
913 
Touch_objective_info(gentity_t * ent,gentity_t * other,trace_t * trace)914 void Touch_objective_info( gentity_t *ent, gentity_t *other, trace_t *trace ) {
915 
916 	if ( other->timestamp > level.time ) {
917 		return;
918 	}
919 
920 	other->timestamp = level.time + 4500;
921 
922 	if ( ent->track ) {
923 		if ( ent->spawnflags & AXIS_OBJECTIVE ) {
924 			trap_SendServerCommand( other - g_entities, va( "oid 0 \"" S_COLOR_RED "You are near %s\n\"", ent->track ) );
925 		} else if ( ent->spawnflags & ALLIED_OBJECTIVE ) {
926 			trap_SendServerCommand( other - g_entities, va( "oid 1 \"" S_COLOR_BLUE "You are near %s\n\"", ent->track ) );
927 		} else {
928 			trap_SendServerCommand( other - g_entities, va( "oid -1 \"You are near %s\n\"", ent->track ) );
929 		}
930 	} else {
931 		if ( ent->spawnflags & AXIS_OBJECTIVE ) {
932 			trap_SendServerCommand( other - g_entities, va( "oid 0 \"" S_COLOR_RED "You are near objective #%i\n\"", ent->count ) );
933 		} else if ( ent->spawnflags & ALLIED_OBJECTIVE ) {
934 			trap_SendServerCommand( other - g_entities, va( "oid 1 \"" S_COLOR_BLUE "You are near objective #%i\n\"", ent->count ) );
935 		} else {
936 			trap_SendServerCommand( other - g_entities, va( "oid -1 \"You are near objective #%i\n\"", ent->count ) );
937 		}
938 	}
939 
940 }
941 
SP_trigger_objective_info(gentity_t * ent)942 void SP_trigger_objective_info( gentity_t *ent ) {
943 	ent->touch  = Touch_objective_info;
944 
945 	InitTrigger( ent );
946 	trap_LinkEntity( ent );
947 }
948 
949 
950 // dhm - end
951