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