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