codemp/game/g_trigger.c

Go to the documentation of this file.
00001 // Copyright (C) 1999-2000 Id Software, Inc.
00002 //
00003 #include "g_local.h"
00004 #include "bg_saga.h"
00005 
00006 int gTrigFallSound;
00007 
00008 void InitTrigger( gentity_t *self ) {
00009         if (!VectorCompare (self->s.angles, vec3_origin))
00010                 G_SetMovedir (self->s.angles, self->movedir);
00011 
00012         trap_SetBrushModel( self, self->model );
00013         self->r.contents = CONTENTS_TRIGGER;            // replaces the -1 from trap_SetBrushModel
00014         self->r.svFlags = SVF_NOCLIENT;
00015 
00016         if(self->spawnflags & 128)
00017         {
00018                 self->flags |= FL_INACTIVE;
00019         }
00020 }
00021 
00022 // the wait time has passed, so set back up for another activation
00023 void multi_wait( gentity_t *ent ) {
00024         ent->nextthink = 0;
00025 }
00026 
00027 void trigger_cleared_fire (gentity_t *self);
00028 
00029 // the trigger was just activated
00030 // ent->activator should be set to the activator so it can be held through a delay
00031 // so wait for the delay time before firing
00032 void multi_trigger_run( gentity_t *ent ) 
00033 {
00034         ent->think = 0;
00035 
00036         G_ActivateBehavior( ent, BSET_USE );
00037 
00038         if ( ent->soundSet && ent->soundSet[0] )
00039         {
00040                 trap_SetConfigstring( CS_GLOBAL_AMBIENT_SET, ent->soundSet );
00041         }
00042 
00043         if (ent->genericValue4)
00044         { //we want to activate target3 for team1 or target4 for team2
00045                 if (ent->genericValue4 == SIEGETEAM_TEAM1 &&
00046                         ent->target3 && ent->target3[0])
00047                 {
00048                         G_UseTargets2(ent, ent->activator, ent->target3);
00049                 }
00050                 else if (ent->genericValue4 == SIEGETEAM_TEAM2 &&
00051                         ent->target4 && ent->target4[0])
00052                 {
00053                         G_UseTargets2(ent, ent->activator, ent->target4);
00054                 }
00055 
00056                 ent->genericValue4 = 0;
00057         }
00058 
00059         G_UseTargets (ent, ent->activator);
00060         if ( ent->noise_index ) 
00061         {
00062                 G_Sound( ent->activator, CHAN_AUTO, ent->noise_index );
00063         }
00064 
00065         if ( ent->target2 && ent->target2[0] && ent->wait >= 0 )
00066         {
00067                 ent->think = trigger_cleared_fire;
00068                 ent->nextthink = level.time + ent->speed;
00069         }
00070         else if ( ent->wait > 0 ) 
00071         {
00072                 if ( ent->painDebounceTime != level.time )
00073                 {//first ent to touch it this frame
00074                         //ent->e_ThinkFunc = thinkF_multi_wait;
00075                         ent->nextthink = level.time + ( ent->wait + ent->random * crandom() ) * 1000;
00076                         ent->painDebounceTime = level.time;
00077                 }
00078         } 
00079         else if ( ent->wait < 0 )
00080         {
00081                 // we can't just remove (self) here, because this is a touch function
00082                 // called while looping through area links...
00083                 ent->r.contents &= ~CONTENTS_TRIGGER;//so the EntityContact trace doesn't have to be done against me
00084                 ent->think = 0;
00085                 ent->use = 0;
00086                 //Don't remove, Icarus may barf?
00087                 //ent->nextthink = level.time + FRAMETIME;
00088                 //ent->think = G_FreeEntity;
00089         }
00090         if( ent->activator && ent->activator->client )
00091         {       // mark the trigger as being touched by the player
00092                 ent->aimDebounceTime = level.time;
00093         }
00094 }
00095 
00096 //determine if the class given is listed in the string using the | formatting
00097 qboolean G_NameInTriggerClassList(char *list, char *str)
00098 {
00099         char cmp[MAX_STRING_CHARS];
00100         int i = 0;
00101         int j;
00102 
00103         while (list[i])
00104         {
00105         j = 0;
00106         while (list[i] && list[i] != '|')
00107                 {
00108                         cmp[j] = list[i];
00109                         i++;
00110                         j++;
00111                 }
00112                 cmp[j] = 0;
00113 
00114                 if (!Q_stricmp(str, cmp))
00115                 { //found it
00116                         return qtrue;
00117                 }
00118                 if (list[i] != '|')
00119                 { //reached the end and never found it
00120                         return qfalse;
00121                 }
00122                 i++;
00123         }
00124 
00125         return qfalse;
00126 }
00127 
00128 extern qboolean gSiegeRoundBegun;
00129 void SiegeItemRemoveOwner(gentity_t *ent, gentity_t *carrier);
00130 void multi_trigger( gentity_t *ent, gentity_t *activator ) 
00131 {
00132         qboolean haltTrigger = qfalse;
00133 
00134         if ( ent->think == multi_trigger_run )
00135         {//already triggered, just waiting to run
00136                 return;
00137         }
00138 
00139         if (g_gametype.integer == GT_SIEGE &&
00140                 !gSiegeRoundBegun)
00141         { //nothing can be used til the round starts.
00142                 return;
00143         }
00144 
00145         if (g_gametype.integer == GT_SIEGE &&
00146                 activator && activator->client &&
00147                 ent->alliedTeam &&
00148                 activator->client->sess.sessionTeam != ent->alliedTeam)
00149         { //this team can't activate this trigger.
00150                 return;
00151         }
00152 
00153         if (g_gametype.integer == GT_SIEGE &&
00154                 ent->idealclass && ent->idealclass[0])
00155         { //only certain classes can activate it
00156                 if (!activator ||
00157                         !activator->client ||
00158                         activator->client->siegeClass < 0)
00159                 { //no class
00160                         return;
00161                 }
00162 
00163                 if (!G_NameInTriggerClassList(bgSiegeClasses[activator->client->siegeClass].name, ent->idealclass))
00164                 { //wasn't in the list
00165                         return;
00166                 }
00167         }
00168 
00169         if (g_gametype.integer == GT_SIEGE && ent->genericValue1)
00170         {
00171                 haltTrigger = qtrue;
00172 
00173                 if (activator && activator->client &&
00174                         activator->client->holdingObjectiveItem &&
00175                         ent->targetname && ent->targetname[0])
00176                 {
00177                         gentity_t *objItem = &g_entities[activator->client->holdingObjectiveItem];
00178 
00179                         if (objItem && objItem->inuse)
00180                         {
00181                                 if (objItem->goaltarget && objItem->goaltarget[0] &&
00182                                         !Q_stricmp(ent->targetname, objItem->goaltarget))
00183                                 {
00184                                         if (objItem->genericValue7 != activator->client->sess.sessionTeam)
00185                                         { //The carrier of the item is not on the team which disallows objective scoring for it
00186                                                 if (objItem->target3 && objItem->target3[0])
00187                                                 { //if it has a target3, fire it off instead of using the trigger
00188                                                         G_UseTargets2(objItem, objItem, objItem->target3);
00189 
00190                             //3-24-03 - want to fire off the target too I guess, if we have one.
00191                                                         if (ent->targetname && ent->targetname[0])
00192                                                         {
00193                                                                 haltTrigger = qfalse;
00194                                                         }
00195                                                 }
00196                                                 else
00197                                                 {
00198                                                         haltTrigger = qfalse;
00199                                                 }
00200 
00201                                                 //now that the item has been delivered, it can go away.
00202                                                 SiegeItemRemoveOwner(objItem, activator);
00203                                                 objItem->nextthink = 0;
00204                                                 objItem->neverFree = qfalse;
00205                                                 G_FreeEntity(objItem);
00206                                         }
00207                                 }
00208                         }
00209                 }
00210         }
00211         else if (ent->genericValue1)
00212         { //Never activate in non-siege gametype I guess.
00213                 return;
00214         }
00215 
00216         if (ent->genericValue2)
00217         { //has "teambalance" property
00218                 int i = 0;
00219                 int team1ClNum = 0;
00220                 int team2ClNum = 0;
00221                 int owningTeam = ent->genericValue3;
00222                 int newOwningTeam = 0;
00223                 int numEnts = 0;
00224                 int entityList[MAX_GENTITIES];
00225                 gentity_t *cl;
00226 
00227                 if (g_gametype.integer != GT_SIEGE)
00228                 {
00229                         return;
00230                 }
00231 
00232                 if (!activator->client ||
00233                         (activator->client->sess.sessionTeam != SIEGETEAM_TEAM1 && activator->client->sess.sessionTeam != SIEGETEAM_TEAM2))
00234                 { //activator must be a valid client to begin with
00235                         return;
00236                 }
00237 
00238                 //Count up the number of clients standing within the bounds of the trigger and the number of them on each team
00239                 numEnts = trap_EntitiesInBox( ent->r.absmin, ent->r.absmax, entityList, MAX_GENTITIES );
00240                 while (i < numEnts)
00241                 {
00242                         if (entityList[i] < MAX_CLIENTS)
00243                         { //only care about clients
00244                                 cl = &g_entities[entityList[i]];
00245 
00246                                 //the client is valid
00247                                 if (cl->inuse && cl->client &&
00248                                         (cl->client->sess.sessionTeam == SIEGETEAM_TEAM1 || cl->client->sess.sessionTeam == SIEGETEAM_TEAM2) &&
00249                                         cl->health > 0 &&
00250                                         !(cl->client->ps.eFlags & EF_DEAD))
00251                                 {
00252                                         //See which team he's on
00253                                         if (cl->client->sess.sessionTeam == SIEGETEAM_TEAM1)
00254                                         {
00255                                                 team1ClNum++;
00256                                         }
00257                                         else
00258                                         {
00259                                                 team2ClNum++;
00260                                         }
00261                                 }
00262                         }
00263                         i++;
00264                 }
00265 
00266                 if (!team1ClNum && !team2ClNum)
00267                 { //no one in the box? How did we get activated? Oh well.
00268                         return;
00269                 }
00270 
00271                 if (team1ClNum == team2ClNum)
00272                 { //if equal numbers the ownership will remain the same as it is now
00273                         return;
00274                 }
00275 
00276                 //decide who owns it now
00277                 if (team1ClNum > team2ClNum)
00278                 {
00279                         newOwningTeam = SIEGETEAM_TEAM1;
00280                 }
00281                 else
00282                 {
00283                         newOwningTeam = SIEGETEAM_TEAM2;
00284                 }
00285 
00286                 if (owningTeam == newOwningTeam)
00287                 { //it's the same one it already was, don't care then.
00288                         return;
00289                 }
00290 
00291                 //Set the new owner and set the variable which will tell us to activate a team-specific target
00292                 ent->genericValue3 = newOwningTeam;
00293                 ent->genericValue4 = newOwningTeam;
00294         }
00295 
00296         if (haltTrigger)
00297         { //This is an objective trigger and the activator is not carrying an objective item that matches the targetname.
00298                 return;
00299         }
00300 
00301         if ( ent->nextthink > level.time ) 
00302         {
00303                 if( ent->spawnflags & 2048 ) // MULTIPLE - allow multiple entities to touch this trigger in a single frame
00304                 {
00305                         if ( ent->painDebounceTime && ent->painDebounceTime != level.time )
00306                         {//this should still allow subsequent ents to fire this trigger in the current frame
00307                                 return;         // can't retrigger until the wait is over
00308                         }
00309                 }
00310                 else
00311                 {
00312                         return;
00313                 }
00314 
00315         }
00316 
00317         // if the player has already activated this trigger this frame
00318         if( activator && !activator->s.number && ent->aimDebounceTime == level.time )
00319         {
00320                 return; 
00321         }
00322 
00323         if ( ent->flags & FL_INACTIVE )
00324         {//Not active at this time
00325                 return;
00326         }
00327 
00328         ent->activator = activator;
00329 
00330         if(ent->delay && ent->painDebounceTime < (level.time + ent->delay) )
00331         {//delay before firing trigger
00332                 ent->think = multi_trigger_run;
00333                 ent->nextthink = level.time + ent->delay;
00334                 ent->painDebounceTime = level.time;
00335                 
00336         }
00337         else
00338         {
00339                 multi_trigger_run (ent);
00340         }
00341 }
00342 
00343 void Use_Multi( gentity_t *ent, gentity_t *other, gentity_t *activator ) 
00344 {
00345         multi_trigger( ent, activator );
00346 }
00347 
00348 qboolean G_PointInBounds( vec3_t point, vec3_t mins, vec3_t maxs );
00349 
00350 void Touch_Multi( gentity_t *self, gentity_t *other, trace_t *trace ) 
00351 {
00352         if( !other->client ) 
00353         {
00354                 return;
00355         }
00356 
00357         if ( self->flags & FL_INACTIVE )
00358         {//set by target_deactivate
00359                 return;
00360         }
00361 
00362         if( self->alliedTeam )
00363         {
00364                 if ( other->client->sess.sessionTeam != self->alliedTeam )
00365                 {
00366                         return;
00367                 }
00368         }
00369 
00370 // moved to just above multi_trigger because up here it just checks if the trigger is not being touched
00371 // 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"
00372 //      if ( self->e_ThinkFunc == thinkF_trigger_cleared_fire )
00373 //      {//We're waiting to fire our target2 first
00374 //              self->nextthink = level.time + self->speed;
00375 //              return;
00376 //      }
00377 
00378         if ( self->spawnflags & 1 )
00379         {
00380                 if ( other->s.eType == ET_NPC )
00381                 {
00382                         return;
00383                 }
00384         }
00385         else
00386         {
00387                 if ( self->spawnflags & 16 )
00388                 {//NPCONLY
00389                         if ( other->NPC == NULL )
00390                         {
00391                                 return;
00392                         }
00393                 }
00394 
00395                 if ( self->NPC_targetname && self->NPC_targetname[0] )
00396                 {
00397                         if ( other->script_targetname && other->script_targetname[0] )
00398                         {
00399                                 if ( Q_stricmp( self->NPC_targetname, other->script_targetname ) != 0 )
00400                                 {//not the right guy to fire me off
00401                                         return;
00402                                 }
00403                         }
00404                         else
00405                         {
00406                                 return;
00407                         }
00408                 }
00409         }
00410 
00411         if ( self->spawnflags & 2 )
00412         {//FACING
00413                 vec3_t  forward;
00414 
00415                 AngleVectors( other->client->ps.viewangles, forward, NULL, NULL );
00416 
00417                 if ( DotProduct( self->movedir, forward ) < 0.5 )
00418                 {//Not Within 45 degrees
00419                         return;
00420                 }
00421         }
00422 
00423         if ( self->spawnflags & 4 )
00424         {//USE_BUTTON
00425                 if( !( other->client->pers.cmd.buttons & BUTTON_USE ) )
00426                 {//not pressing use button
00427                         return;
00428                 }
00429 
00430                 if ((other->client->ps.weaponTime > 0 && other->client->ps.torsoAnim != BOTH_BUTTON_HOLD && other->client->ps.torsoAnim != BOTH_CONSOLE1) || other->health < 1 ||
00431                         (other->client->ps.pm_flags & PMF_FOLLOW) || other->client->sess.sessionTeam == TEAM_SPECTATOR ||
00432                         other->client->ps.forceHandExtend != HANDEXTEND_NONE)
00433                 { //player has to be free of other things to use.
00434                         return;
00435                 }
00436 
00437                 if (self->genericValue7)
00438                 { //we have to be holding the use key in this trigger for x milliseconds before firing
00439                         if (g_gametype.integer == GT_SIEGE &&
00440                                 self->idealclass && self->idealclass[0])
00441                         { //only certain classes can activate it
00442                                 if (!other ||
00443                                         !other->client ||
00444                                         other->client->siegeClass < 0)
00445                                 { //no class
00446                                         return;
00447                                 }
00448 
00449                                 if (!G_NameInTriggerClassList(bgSiegeClasses[other->client->siegeClass].name, self->idealclass))
00450                                 { //wasn't in the list
00451                                         return;
00452                                 }
00453                         }
00454 
00455                         if (!G_PointInBounds( other->client->ps.origin, self->r.absmin, self->r.absmax ))
00456                         {
00457                                 return;
00458                         }
00459                         else if (other->client->isHacking != self->s.number && other->s.number < MAX_CLIENTS )
00460                         { //start the hack
00461                                 other->client->isHacking = self->s.number;
00462                                 VectorCopy(other->client->ps.viewangles, other->client->hackingAngles);
00463                                 other->client->ps.hackingTime = level.time + self->genericValue7;
00464                                 other->client->ps.hackingBaseTime = self->genericValue7;
00465                                 if (other->client->ps.hackingBaseTime > 60000)
00466                                 { //don't allow a bit overflow
00467                                         other->client->ps.hackingTime = level.time + 60000;
00468                                         other->client->ps.hackingBaseTime = 60000;
00469                                 }
00470                                 return;
00471                         }
00472                         else if (other->client->ps.hackingTime < level.time)
00473                         { //finished with the hack, reset the hacking values and let it fall through
00474                                 other->client->isHacking = 0; //can't hack a client
00475                                 other->client->ps.hackingTime = 0;
00476                         }
00477                         else
00478                         { //hack in progress
00479                                 return;
00480                         }
00481                 }
00482         }
00483 
00484         if ( self->spawnflags & 8 )
00485         {//FIRE_BUTTON
00486                 if( !( other->client->pers.cmd.buttons & BUTTON_ATTACK ) &&
00487                         !( other->client->pers.cmd.buttons & BUTTON_ALT_ATTACK ) )
00488                 {//not pressing fire button or altfire button
00489                         return;
00490                 }
00491         }
00492 
00493         if ( self->radius )
00494         {
00495                 vec3_t  eyeSpot;
00496 
00497                 //Only works if your head is in it, but we allow leaning out
00498                 //NOTE: We don't use CalcEntitySpot SPOT_HEAD because we don't want this
00499                 //to be reliant on the physical model the player uses.
00500                 VectorCopy(other->client->ps.origin, eyeSpot);
00501                 eyeSpot[2] += other->client->ps.viewheight;
00502 
00503                 if ( G_PointInBounds( eyeSpot, self->r.absmin, self->r.absmax ) )
00504                 {
00505                         if( !( other->client->pers.cmd.buttons & BUTTON_ATTACK ) &&
00506                                 !( other->client->pers.cmd.buttons & BUTTON_ALT_ATTACK ) )
00507                         {//not attacking, so hiding bonus
00508                                 /*
00509                                 //FIXME:  should really have sound events clear the hiddenDist
00510                                 other->client->hiddenDist = self->radius;
00511                                 //NOTE: movedir HAS to be normalized!
00512                                 if ( VectorLength( self->movedir ) )
00513                                 {//They can only be hidden from enemies looking in this direction
00514                                         VectorCopy( self->movedir, other->client->hiddenDir );
00515                                 }
00516                                 else
00517                                 {
00518                                         VectorClear( other->client->hiddenDir );
00519                                 }
00520                                 */
00521                                 //Not using this, at least not yet.
00522                         }
00523                 }
00524         }
00525 
00526         if ( self->spawnflags & 4 )
00527         {//USE_BUTTON
00528                 if (other->client->ps.torsoAnim != BOTH_BUTTON_HOLD &&
00529                         other->client->ps.torsoAnim != BOTH_CONSOLE1)
00530                 {
00531                         G_SetAnim( other, NULL, SETANIM_TORSO, BOTH_BUTTON_HOLD, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0 );
00532                 }
00533                 else
00534                 {
00535                         other->client->ps.torsoTimer = 500;
00536                 }
00537                 other->client->ps.weaponTime = other->client->ps.torsoTimer;
00538         }
00539         
00540         if ( self->think == trigger_cleared_fire )
00541         {//We're waiting to fire our target2 first
00542                 self->nextthink = level.time + self->speed;
00543                 return;
00544         }
00545 
00546         multi_trigger( self, other );
00547 }
00548 
00549 void trigger_cleared_fire (gentity_t *self)
00550 {
00551         G_UseTargets2( self, self->activator, self->target2 );
00552         self->think = 0;
00553         // should start the wait timer now, because the trigger's just been cleared, so we must "wait" from this point
00554         if ( self->wait > 0 ) 
00555         {
00556                 self->nextthink = level.time + ( self->wait + self->random * crandom() ) * 1000;
00557         }
00558 }
00559 
00560 /*QUAKED trigger_multiple (.1 .5 .1) ? CLIENTONLY FACING USE_BUTTON FIRE_BUTTON NPCONLY x x INACTIVE MULTIPLE
00561 CLIENTONLY - only a player can trigger this by touch
00562 FACING - Won't fire unless triggering ent's view angles are within 45 degrees of trigger's angles (in addition to any other conditions)
00563 USE_BUTTON - Won't fire unless player is in it and pressing use button (in addition to any other conditions)
00564 FIRE_BUTTON - Won't fire unless player/NPC is in it and pressing fire button (in addition to any other conditions)
00565 NPCONLY - only non-player NPCs can trigger this by touch
00566 INACTIVE - Start off, has to be activated to be touchable/usable
00567 MULTIPLE - multiple entities can touch this trigger in a single frame *and* if needed, the trigger can have a wait of > 0
00568 
00569 "wait"          Seconds between triggerings, 0 default, number < 0 means one time only.
00570 "random"        wait variance, default is 0
00571 "delay"         how many seconds to wait to fire targets after tripped
00572 "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.
00573 "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')
00574 "speed"         How many seconds to wait to fire the target2, default is 1
00575 "noise"         Sound to play when the trigger fires (plays at activator's origin)
00576 "NPC_targetname"  Only the NPC with this NPC_targetname fires this trigger
00577 
00578 Variable sized repeatable trigger.  Must be targeted at one or more entities.
00579 so, the basic time between firing is a random time between
00580 (wait - random) and (wait + random)
00581 
00582 "team" - If set, only this team can trip this trigger
00583         0 - any
00584         1 - red
00585         2 - blue
00586 
00587 "soundSet"      Ambient sound set to play when this trigger is activated
00588 
00589 usetime         -       If specified (in milliseconds) along with the USE_BUTTON flag, will
00590                                 require a client to hold the use key for x amount of ms before firing.
00591 
00592 Applicable only during Siege gametype:
00593 teamuser        -       if 1, team 2 can't use this. If 2, team 1 can't use this.
00594 siegetrig       -       if non-0, can only be activated by players carrying a misc_siege_item
00595                                 which is associated with this trigger by the item's goaltarget value.
00596 teambalance     -       if non-0, is "owned" by the last team that activated. Can only be activated
00597                                 by the other team if the number of players on the other team inside     the
00598                                 trigger outnumber the number of players on the owning team inside the
00599                                 trigger.
00600 target3         -       fire when activated by team1
00601 target4         -       fire when activated by team2
00602 
00603 idealclass      -       Can only be used by this class/these classes. You can specify use by
00604                                 multiple classes with the use of |, e.g.:
00605                                 "Imperial Medic|Imperial Assassin|Imperial Demolitionist"
00606 */
00607 void SP_trigger_multiple( gentity_t *ent ) 
00608 {
00609         char    *s;
00610         if ( G_SpawnString( "noise", "", &s ) ) 
00611         {
00612                 if (s && s[0])
00613                 {
00614                         ent->noise_index = G_SoundIndex(s);
00615                 }
00616                 else
00617                 {
00618                         ent->noise_index = 0;
00619                 }
00620         }
00621 
00622         G_SpawnInt("usetime", "0", &ent->genericValue7);
00623 
00624         //For siege gametype
00625         G_SpawnInt("siegetrig", "0", &ent->genericValue1);
00626     G_SpawnInt("teambalance", "0", &ent->genericValue2);
00627 
00628         G_SpawnInt("delay", "0", &ent->delay);
00629 
00630         if ( (ent->wait > 0) && (ent->random >= ent->wait) ) {
00631                 ent->random = ent->wait - FRAMETIME;
00632                 Com_Printf(S_COLOR_YELLOW"trigger_multiple has random >= wait\n");
00633         }
00634 
00635         ent->delay *= 1000;//1 = 1 msec, 1000 = 1 sec
00636         if ( !ent->speed && ent->target2 && ent->target2[0] )
00637         {
00638                 ent->speed = 1000;
00639         }
00640         else
00641         {
00642                 ent->speed *= 1000;
00643         }
00644 
00645         ent->touch = Touch_Multi;
00646         ent->use   = Use_Multi;
00647 
00648         if ( ent->team && ent->team[0] )
00649         {
00650                 ent->alliedTeam = atoi(ent->team);
00651                 ent->team = NULL;
00652         }
00653 
00654         InitTrigger( ent );
00655         trap_LinkEntity (ent);
00656 }
00657 
00658 
00659 /*QUAKED trigger_once (.5 1 .5) ? CLIENTONLY FACING USE_BUTTON FIRE_BUTTON x x x INACTIVE MULTIPLE
00660 CLIENTONLY - only a player can trigger this by touch
00661 FACING - Won't fire unless triggering ent's view angles are within 45 degrees of trigger's angles (in addition to any other conditions)
00662 USE_BUTTON - Won't fire unless player is in it and pressing use button (in addition to any other conditions)
00663 FIRE_BUTTON - Won't fire unless player/NPC is in it and pressing fire button (in addition to any other conditions)
00664 INACTIVE - Start off, has to be activated to be touchable/usable
00665 MULTIPLE - multiple entities can touch this trigger in a single frame *and* if needed, the trigger can have a wait of > 0
00666 
00667 "random"        wait variance, default is 0
00668 "delay"         how many seconds to wait to fire targets after tripped
00669 Variable sized repeatable trigger.  Must be targeted at one or more entities.
00670 so, the basic time between firing is a random time between
00671 (wait - random) and (wait + random)
00672 "noise"         Sound to play when the trigger fires (plays at activator's origin)
00673 "NPC_targetname"  Only the NPC with this NPC_targetname fires this trigger
00674 
00675 "team" - If set, only this team can trip this trigger
00676         0 - any
00677         1 - red
00678         2 - blue
00679 
00680 "soundSet"      Ambient sound set to play when this trigger is activated
00681 
00682 usetime         -       If specified (in milliseconds) along with the USE_BUTTON flag, will
00683                                 require a client to hold the use key for x amount of ms before firing.
00684 
00685 Applicable only during Siege gametype:
00686 teamuser - if 1, team 2 can't use this. If 2, team 1 can't use this.
00687 siegetrig - if non-0, can only be activated by players carrying a misc_siege_item
00688                         which is associated with this trigger by the item's goaltarget value.
00689 
00690 idealclass      -       Can only be used by this class/these classes. You can specify use by
00691                                 multiple classes with the use of |, e.g.:
00692                                 "Imperial Medic|Imperial Assassin|Imperial Demolitionist"
00693 */
00694 void SP_trigger_once( gentity_t *ent ) 
00695 {
00696         char    *s;
00697         if ( G_SpawnString( "noise", "", &s ) ) 
00698         {
00699                 if (s && s[0])
00700                 {
00701                         ent->noise_index = G_SoundIndex(s);
00702                 }
00703                 else
00704                 {
00705                         ent->noise_index = 0;
00706                 }
00707         }
00708 
00709         G_SpawnInt("usetime", "0", &ent->genericValue7);
00710 
00711         //For siege gametype
00712         G_SpawnInt("siegetrig", "0", &ent->genericValue1);
00713 
00714         G_SpawnInt("delay", "0", &ent->delay);
00715 
00716         ent->wait = -1;
00717 
00718         ent->touch = Touch_Multi;
00719         ent->use   = Use_Multi;
00720 
00721         if ( ent->team && ent->team[0] )
00722         {
00723                 ent->alliedTeam = atoi(ent->team);
00724                 ent->team = NULL;
00725         }
00726 
00727         ent->delay *= 1000;//1 = 1 msec, 1000 = 1 sec
00728 
00729         InitTrigger( ent );
00730         trap_LinkEntity (ent);
00731 }
00732 
00733 /*
00734 ======================================================================
00735 trigger_lightningstrike -rww
00736 ======================================================================
00737 */
00738 //lightning strike trigger lightning strike event
00739 void Do_Strike(gentity_t *ent)
00740 {
00741         trace_t localTrace;
00742         vec3_t strikeFrom;
00743         vec3_t strikePoint;
00744         vec3_t fxAng;
00745         
00746         //maybe allow custom fx direction at some point?
00747         VectorSet(fxAng, 90.0f, 0.0f, 0.0f);
00748 
00749         //choose a random point to strike within the bounds of the trigger
00750         strikePoint[0] = flrand(ent->r.absmin[0], ent->r.absmax[0]);
00751         strikePoint[1] = flrand(ent->r.absmin[1], ent->r.absmax[1]);
00752 
00753         //consider the bottom mins the ground level
00754         strikePoint[2] = ent->r.absmin[2];
00755 
00756         //set the from point
00757         strikeFrom[0] = strikePoint[0];
00758         strikeFrom[1] = strikePoint[1];
00759         strikeFrom[2] = ent->r.absmax[2]-4.0f;
00760 
00761         //now trace for damaging stuff, and do the effect
00762         trap_Trace(&localTrace, strikeFrom, NULL, NULL, strikePoint, ent->s.number, MASK_PLAYERSOLID);
00763         VectorCopy(localTrace.endpos, strikePoint);
00764 
00765         if (localTrace.startsolid || localTrace.allsolid)
00766         { //got a bad spot, think again next frame to try another strike
00767                 ent->nextthink = level.time;
00768                 return;
00769         }
00770 
00771         if (ent->radius)
00772         { //do a radius damage at the end pos
00773                 G_RadiusDamage(strikePoint, ent, ent->damage, ent->radius, ent, NULL, MOD_SUICIDE);
00774         }
00775         else
00776         { //only damage individuals
00777                 gentity_t *trHit = &g_entities[localTrace.entityNum];
00778 
00779                 if (trHit->inuse && trHit->takedamage)
00780                 { //damage it then
00781                         G_Damage(trHit, ent, ent, NULL, trHit->r.currentOrigin, ent->damage, 0, MOD_SUICIDE);
00782                 }
00783         }
00784 
00785         G_PlayEffectID(ent->genericValue2, strikeFrom, fxAng);
00786 }
00787 
00788 //lightning strike trigger think loop
00789 void Think_Strike(gentity_t *ent)
00790 {
00791         if (ent->genericValue1)
00792         { //turned off currently
00793                 return;
00794         }
00795 
00796         ent->nextthink = level.time + ent->wait + Q_irand(0, ent->random);
00797         Do_Strike(ent);
00798 }
00799 
00800 //lightning strike trigger use event function
00801 void Use_Strike( gentity_t *ent, gentity_t *other, gentity_t *activator ) 
00802 {
00803         ent->genericValue1 = !ent->genericValue1;
00804 
00805         if (!ent->genericValue1)
00806         { //turn it back on
00807                 ent->nextthink = level.time;
00808         }
00809 }
00810 
00811 /*QUAKED trigger_lightningstrike (.1 .5 .1) ? START_OFF
00812 START_OFF - start trigger disabled
00813 
00814 "lightningfx"   effect to use for lightning, MUST be specified
00815 "wait"                  Seconds between strikes, 1000 default
00816 "random"                wait variance, default is 2000
00817 "dmg"                   damage on strike (default 50)
00818 "radius"                if non-0, does a radius damage at the lightning strike
00819                                 impact point (using this value as the radius). otherwise
00820                                 will only do line trace damage. default 0.
00821 
00822 use to toggle on and off
00823 */
00824 void SP_trigger_lightningstrike( gentity_t *ent ) 
00825 {
00826         char *s;
00827 
00828         ent->use = Use_Strike;
00829         ent->think = Think_Strike;
00830         ent->nextthink = level.time + 500;
00831 
00832         G_SpawnString("lightningfx", "", &s);
00833         if (!s || !s[0])
00834         {
00835                 Com_Error(ERR_DROP, "trigger_lightningstrike with no lightningfx");
00836         }
00837 
00838         //get a configstring index for it
00839         ent->genericValue2 = G_EffectIndex(s);
00840 
00841         if (ent->spawnflags & 1)
00842         { //START_OFF
00843                 ent->genericValue1 = 1;
00844         }
00845 
00846         if (!ent->wait)
00847         { //default 1000
00848                 ent->wait = 1000;
00849         }
00850         if (!ent->random)
00851         { //default 2000
00852                 ent->random = 2000;
00853         }
00854         if (!ent->damage)
00855         { //default 50
00856                 ent->damage = 50;
00857         }
00858 
00859         InitTrigger( ent );
00860         trap_LinkEntity (ent);
00861 }
00862 
00863 
00864 /*
00865 ==============================================================================
00866 
00867 trigger_always
00868 
00869 ==============================================================================
00870 */
00871 
00872 void trigger_always_think( gentity_t *ent ) {
00873         G_UseTargets(ent, ent);
00874         G_FreeEntity( ent );
00875 }
00876 
00877 /*QUAKED trigger_always (.5 .5 .5) (-8 -8 -8) (8 8 8)
00878 This trigger will always fire.  It is activated by the world.
00879 */
00880 void SP_trigger_always (gentity_t *ent) {
00881         // we must have some delay to make sure our use targets are present
00882         ent->nextthink = level.time + 300;
00883         ent->think = trigger_always_think;
00884 }
00885 
00886 
00887 /*
00888 ==============================================================================
00889 
00890 trigger_push
00891 
00892 ==============================================================================
00893 */
00894 //trigger_push
00895 #define PUSH_LINEAR             4
00896 #define PUSH_RELATIVE   16
00897 #define PUSH_MULTIPLE   2048
00898 //target_push
00899 #define PUSH_CONSTANT   2
00900 
00901 void trigger_push_touch (gentity_t *self, gentity_t *other, trace_t *trace ) {
00902         if ( self->flags & FL_INACTIVE )
00903         {//set by target_deactivate
00904                 return;
00905         }
00906 
00907         if ( !(self->spawnflags&PUSH_LINEAR) )
00908         {//normal throw
00909                 if ( !other->client ) {
00910                         return;
00911                 }
00912                 BG_TouchJumpPad( &other->client->ps, &self->s );
00913                 return;
00914         }
00915 
00916         //linear
00917         if( level.time < self->painDebounceTime + self->wait  ) // normal 'wait' check
00918         {
00919                 if( self->spawnflags & PUSH_MULTIPLE ) // MULTIPLE - allow multiple entities to touch this trigger in one frame
00920                 {
00921                         if ( self->painDebounceTime && level.time > self->painDebounceTime ) // if we haven't reached the next frame continue to let ents touch the trigger
00922                         {
00923                                 return;
00924                         }
00925                 }
00926                 else // only allowing one ent per frame to touch trigger
00927                 {
00928                         return;
00929                 }
00930         }
00931 
00932         /*
00933         //???
00934         // if the player has already activated this trigger this frame
00935         if( other && !other->s.number && self->aimDebounceTime == level.time )
00936         {
00937                 return;         
00938         }
00939         */
00940         
00941         /*
00942         if( self->spawnflags & PUSH_CONVEYOR )
00943         {   // only push player if he's on the ground
00944                 if( other->s.groundEntityNum == ENTITYNUM_NONE )
00945                 {
00946                         return;
00947                 }
00948         }
00949         */
00950 
00951         /*
00952         if ( self->spawnflags & 1 )
00953         {//PLAYERONLY
00954                 if ( other->s.number >= MAX_CLIENTS )
00955                 {
00956                         return;
00957                 }
00958         }
00959         else
00960         {
00961                 if ( self->spawnflags & 8 )
00962                 {//NPCONLY
00963                         if ( other->NPC == NULL )
00964                         {
00965                                 return;
00966                         }
00967                 }
00968         }
00969         */
00970 
00971         if ( !other->client ) {
00972                 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 ) )
00973                 {//already moving
00974                         VectorCopy( other->r.currentOrigin, other->s.pos.trBase );
00975                         VectorCopy( self->s.origin2, other->s.pos.trDelta );
00976                         other->s.pos.trTime = level.time;
00977                 }
00978                 return;
00979         }
00980 
00981         if ( other->client->ps.pm_type != PM_NORMAL 
00982                 && other->client->ps.pm_type != PM_DEAD 
00983                 && other->client->ps.pm_type != PM_FREEZE ) 
00984         {
00985                 return;
00986         }
00987         
00988         if ( (self->spawnflags&PUSH_RELATIVE) )
00989         {//relative, dir to it * speed
00990                 vec3_t dir;
00991                 VectorSubtract( self->s.origin2, other->r.currentOrigin, dir );
00992                 if ( self->speed )
00993                 {
00994                         VectorNormalize( dir );
00995                         VectorScale( dir, self->speed, dir );
00996                 }
00997                 VectorCopy( dir, other->client->ps.velocity );
00998         }
00999         else if ( (self->spawnflags&PUSH_LINEAR) )
01000         {//linear dir * speed
01001                 VectorScale( self->s.origin2, self->speed, other->client->ps.velocity );
01002         }
01003         else
01004         {
01005                 VectorCopy( self->s.origin2, other->client->ps.velocity );
01006         }
01007         //so we don't take damage unless we land lower than we start here...
01008         /*
01009         other->client->ps.forceJumpZStart = 0;
01010         other->client->ps.pm_flags |= PMF_TRIGGER_PUSHED;//pushed by a trigger
01011         other->client->ps.jumpZStart = other->client->ps.origin[2];
01012         */
01013 
01014         if ( self->wait == -1 )
01015         {
01016                 self->touch = NULL;
01017         }
01018         else if ( self->wait > 0 )
01019         {
01020                 self->painDebounceTime = level.time;
01021                 
01022         }
01023         /*
01024         if( other && !other->s.number )
01025         {       // mark that the player has activated this trigger this frame
01026                 self->aimDebounceTime =level.time;
01027         }
01028         */
01029 }
01030 
01031 
01032 /*
01033 =================
01034 AimAtTarget
01035 
01036 Calculate origin2 so the target apogee will be hit
01037 =================
01038 */
01039 void AimAtTarget( gentity_t *self ) {
01040         gentity_t       *ent;
01041         vec3_t          origin;
01042         float           height, gravity, time, forward;
01043         float           dist;
01044 
01045         VectorAdd( self->r.absmin, self->r.absmax, origin );
01046         VectorScale ( origin, 0.5f, origin );
01047 
01048         ent = G_PickTarget( self->target );
01049         if ( !ent ) {
01050                 G_FreeEntity( self );
01051                 return;
01052         }
01053 
01054         if ( self->classname && !Q_stricmp( "trigger_push", self->classname ) )
01055         {
01056                 if ( (self->spawnflags&PUSH_RELATIVE) )
01057                 {//relative, not an arc or linear
01058                         VectorCopy( ent->r.currentOrigin, self->s.origin2 );
01059                         return;
01060                 }
01061                 else if ( (self->spawnflags&PUSH_LINEAR) )
01062                 {//linear, not an arc
01063                         VectorSubtract( ent->r.currentOrigin, origin, self->s.origin2 );
01064                         VectorNormalize( self->s.origin2 );
01065                         return;
01066                 }
01067         }
01068 
01069         if ( self->classname && !Q_stricmp( "target_push", self->classname ) )
01070         {
01071                 if( self->spawnflags & PUSH_CONSTANT )
01072                 {
01073                         VectorSubtract ( ent->s.origin, self->s.origin, self->s.origin2 );
01074                         VectorNormalize( self->s.origin2);
01075                         VectorScale (self->s.origin2, self->speed, self->s.origin2);
01076                         return;
01077                 }
01078         }
01079 
01080         height = ent->s.origin[2] - origin[2];
01081         gravity = g_gravity.value;
01082         time = sqrt( height / ( .5 * gravity ) );
01083         if ( !time ) {
01084                 G_FreeEntity( self );
01085                 return;
01086         }
01087 
01088         // set s.origin2 to the push velocity
01089         VectorSubtract ( ent->s.origin, origin, self->s.origin2 );
01090         self->s.origin2[2] = 0;
01091         dist = VectorNormalize( self->s.origin2);
01092 
01093         forward = dist / time;
01094         VectorScale( self->s.origin2, forward, self->s.origin2 );
01095 
01096         self->s.origin2[2] = time * gravity;
01097 }
01098 
01099 
01100 /*QUAKED trigger_push (.5 .5 .5) ? x x LINEAR x RELATIVE x x INACTIVE MULTIPLE
01101 Must point at a target_position, which will be the apex of the leap.
01102 This will be client side predicted, unlike target_push
01103 
01104 LINEAR - Instead of tossing the client at the target_position, it will push them towards it.  Must set a "speed" (see below)
01105 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)
01106 INACTIVE - not active until targeted by a target_activate
01107 MULTIPLE - multiple entities can touch this trigger in a single frame *and* if needed, the trigger can have a wait of > 0
01108 
01109 wait - how long to wait between pushes: -1 = push only once
01110 speed - when used with the LINEAR spawnflag, pushes the client toward the position at a constant speed (default is 1000)
01111 */
01112 void SP_trigger_push( gentity_t *self ) {
01113         InitTrigger (self);
01114 
01115         // unlike other triggers, we need to send this one to the client
01116         self->r.svFlags &= ~SVF_NOCLIENT;
01117 
01118         // make sure the client precaches this sound
01119         G_SoundIndex("sound/weapons/force/jump.wav");
01120 
01121         self->s.eType = ET_PUSH_TRIGGER;
01122         
01123         if ( !(self->spawnflags&2) )
01124         {//start on
01125                 self->touch = trigger_push_touch;
01126         }
01127 
01128         if ( self->spawnflags & 4 )
01129         {//linear
01130                 self->speed = 1000;
01131         }
01132 
01133         self->think = AimAtTarget;
01134         self->nextthink = level.time + FRAMETIME;
01135         trap_LinkEntity (self);
01136 }
01137 
01138 void Use_target_push( gentity_t *self, gentity_t *other, gentity_t *activator ) {
01139         if ( !activator->client ) {
01140                 return;
01141         }
01142 
01143         if ( activator->client->ps.pm_type != PM_NORMAL && activator->client->ps.pm_type != PM_FLOAT ) {
01144                 return;
01145         }
01146 
01147         G_ActivateBehavior(self,BSET_USE);
01148 
01149         VectorCopy (self->s.origin2, activator->client->ps.velocity);
01150 
01151         // play fly sound every 1.5 seconds
01152         if ( activator->fly_sound_debounce_time < level.time ) {
01153                 activator->fly_sound_debounce_time = level.time + 1500;
01154                 if (self->noise_index)
01155                 {
01156                         G_Sound( activator, CHAN_AUTO, self->noise_index );
01157                 }
01158         }
01159 }
01160 
01161 /*QUAKED target_push (.5 .5 .5) (-8 -8 -8) (8 8 8) bouncepad CONSTANT
01162 CONSTANT will push activator in direction of 'target' at constant 'speed'
01163 
01164 Pushes the activator in the direction.of angle, or towards a target apex.
01165 "speed"         defaults to 1000
01166 if "bouncepad", play bounce noise instead of none
01167 */
01168 void SP_target_push( gentity_t *self ) {
01169         if (!self->speed) {
01170                 self->speed = 1000;
01171         }
01172         G_SetMovedir (self->s.angles, self->s.origin2);
01173         VectorScale (self->s.origin2, self->speed, self->s.origin2);
01174 
01175         if ( self->spawnflags & 1 ) {
01176                 self->noise_index = G_SoundIndex("sound/weapons/force/jump.wav");
01177         } else {
01178                 self->noise_index = 0;  //G_SoundIndex("sound/misc/windfly.wav");
01179         }
01180         if ( self->target ) {
01181                 VectorCopy( self->s.origin, self->r.absmin );
01182                 VectorCopy( self->s.origin, self->r.absmax );
01183                 self->think = AimAtTarget;
01184                 self->nextthink = level.time + FRAMETIME;
01185         }
01186         self->use = Use_target_push;
01187 }
01188 
01189 /*
01190 ==============================================================================
01191 
01192 trigger_teleport
01193 
01194 ==============================================================================
01195 */
01196 
01197 void trigger_teleporter_touch (gentity_t *self, gentity_t *other, trace_t *trace ) {
01198         gentity_t       *dest;
01199 
01200         if ( self->flags & FL_INACTIVE )
01201         {//set by target_deactivate
01202                 return;
01203         }
01204 
01205         if ( !other->client ) {
01206                 return;
01207         }
01208         if ( other->client->ps.pm_type == PM_DEAD ) {
01209                 return;
01210         }
01211         // Spectators only?
01212         if ( ( self->spawnflags & 1 ) && 
01213                 other->client->sess.sessionTeam != TEAM_SPECTATOR ) {
01214                 return;
01215         }
01216 
01217 
01218         dest =  G_PickTarget( self->target );
01219         if (!dest) {
01220                 G_Printf ("Couldn't find teleporter destination\n");
01221                 return;
01222         }
01223 
01224         TeleportPlayer( other, dest->s.origin, dest->s.angles );
01225 }
01226 
01227 
01228 /*QUAKED trigger_teleport (.5 .5 .5) ? SPECTATOR
01229 Allows client side prediction of teleportation events.
01230 Must point at a target_position, which will be the teleport destination.
01231 
01232 If spectator is set, only spectators can use this teleport
01233 Spectator teleporters are not normally placed in the editor, but are created
01234 automatically near doors to allow spectators to move through them
01235 */
01236 void SP_trigger_teleport( gentity_t *self ) {
01237         InitTrigger (self);
01238 
01239         // unlike other triggers, we need to send this one to the client
01240         // unless is a spectator trigger
01241         if ( self->spawnflags & 1 ) {
01242                 self->r.svFlags |= SVF_NOCLIENT;
01243         } else {
01244                 self->r.svFlags &= ~SVF_NOCLIENT;
01245         }
01246 
01247         // make sure the client precaches this sound
01248         G_SoundIndex("sound/weapons/force/speed.wav");
01249 
01250         self->s.eType = ET_TELEPORT_TRIGGER;
01251         self->touch = trigger_teleporter_touch;
01252 
01253         trap_LinkEntity (self);
01254 }
01255 
01256 
01257 /*
01258 ==============================================================================
01259 
01260 trigger_hurt
01261 
01262 ==============================================================================
01263 */
01264 
01265 /*QUAKED trigger_hurt (.5 .5 .5) ? START_OFF CAN_TARGET SILENT NO_PROTECTION SLOW
01266 Any entity that touches this will be hurt.
01267 It does dmg points of damage each server frame
01268 Targeting the trigger will toggle its on / off state.
01269 
01270 CAN_TARGET              if you target it, it will toggle on and off
01271 SILENT                  supresses playing the sound
01272 SLOW                    changes the damage rate to once per second
01273 NO_PROTECTION   *nothing* stops the damage
01274 
01275 "team"                  team (1 or 2) to allow hurting (if none then hurt anyone) only applicable for siege
01276 "dmg"                   default 5 (whole numbers only)
01277 If dmg is set to -1 this brush will use the fade-kill method
01278 
01279 */
01280 void hurt_use( gentity_t *self, gentity_t *other, gentity_t *activator ) {
01281         if (activator && activator->inuse && activator->client)
01282         {
01283                 self->activator = activator;
01284         }
01285         else
01286         {
01287                 self->activator = NULL;
01288         }
01289 
01290         G_ActivateBehavior(self,BSET_USE);
01291 
01292         if ( self->r.linked ) {
01293                 trap_UnlinkEntity( self );
01294         } else {
01295                 trap_LinkEntity( self );
01296         }
01297 }
01298 
01299 void hurt_touch( gentity_t *self, gentity_t *other, trace_t *trace ) {
01300         int             dflags;
01301 
01302         if (g_gametype.integer == GT_SIEGE && self->team && self->team[0])
01303         {
01304                 int team = atoi(self->team);
01305 
01306                 if (other->inuse && other->s.number < MAX_CLIENTS && other->client &&
01307                         other->client->sess.sessionTeam != team)
01308                 { //real client don't hurt
01309                         return;
01310                 }
01311                 else if (other->inuse && other->client && other->s.eType == ET_NPC &&
01312                         other->s.NPC_class == CLASS_VEHICLE && other->s.teamowner != team)
01313                 { //vehicle owned by team don't hurt
01314                         return;
01315                 }
01316         }
01317 
01318         if ( self->flags & FL_INACTIVE )
01319         {//set by target_deactivate
01320                 return;
01321         }
01322 
01323         if ( !other->takedamage ) {
01324                 return;
01325         }
01326 
01327         if ( self->timestamp > level.time ) {
01328                 return;
01329         }
01330 
01331         if (self->damage == -1 && other && other->client && other->health < 1)
01332         {
01333                 other->client->ps.fallingToDeath = 0;
01334                 respawn(other);
01335                 return;
01336         }
01337 
01338         if (self->damage == -1 && other && other->client && other->client->ps.fallingToDeath)
01339         {
01340                 return;
01341         }
01342 
01343         if ( self->spawnflags & 16 ) {
01344                 self->timestamp = level.time + 1000;
01345         } else {
01346                 self->timestamp = level.time + FRAMETIME;
01347         }
01348 
01349         // play sound
01350         /*
01351         if ( !(self->spawnflags & 4) && self->damage != -1 ) {
01352                 G_Sound( other, CHAN_AUTO, self->noise_index );
01353         }
01354         */
01355 
01356         if (self->spawnflags & 8)
01357                 dflags = DAMAGE_NO_PROTECTION;
01358         else
01359                 dflags = 0;
01360 
01361         if (self->damage == -1 && other && other->client)
01362         {
01363                 if (other->client->ps.otherKillerTime > level.time)
01364                 { //we're as good as dead, so if someone pushed us into this then remember them
01365                         other->client->ps.otherKillerTime = level.time + 20000;
01366                         other->client->ps.otherKillerDebounceTime = level.time + 10000;
01367                 }
01368                 other->client->ps.fallingToDeath = level.time;
01369 
01370                 //rag on the way down, this flag will automatically be cleared for us on respawn
01371                 other->client->ps.eFlags |= EF_RAG;
01372 
01373                 //make sure his jetpack is off
01374                 Jetpack_Off(other);
01375 
01376                 if (other->NPC)
01377                 { //kill it now
01378                         vec3_t vDir;
01379 
01380                         VectorSet(vDir, 0, 1, 0);
01381                         G_Damage(other, other, other, vDir, other->client->ps.origin, Q3_INFINITE, 0, MOD_FALLING);
01382                 }
01383                 else
01384                 {
01385                         G_EntitySound(other, CHAN_VOICE, G_SoundIndex("*falling1.wav"));
01386                 }
01387 
01388                 self->timestamp = 0; //do not ignore others
01389         }
01390         else    
01391         {
01392                 int dmg = self->damage;
01393 
01394                 if (dmg == -1)
01395                 { //so fall-to-blackness triggers destroy evertyhing
01396                         dmg = 99999;
01397                         self->timestamp = 0;
01398                 }
01399                 if (self->activator && self->activator->inuse && self->activator->client)
01400                 {
01401                         G_Damage (other, self->activator, self->activator, NULL, NULL, dmg, dflags|DAMAGE_NO_PROTECTION, MOD_TRIGGER_HURT);
01402                 }
01403                 else
01404                 {
01405                         G_Damage (other, self, self, NULL, NULL, dmg, dflags|DAMAGE_NO_PROTECTION, MOD_TRIGGER_HURT);
01406                 }
01407         }
01408 }
01409 
01410 void SP_trigger_hurt( gentity_t *self ) {
01411         InitTrigger (self);
01412 
01413         gTrigFallSound = G_SoundIndex("*falling1.wav");
01414 
01415         self->noise_index = G_SoundIndex( "sound/weapons/force/speed.wav" );
01416         self->touch = hurt_touch;
01417 
01418         if ( !self->damage ) {
01419                 self->damage = 5;
01420         }
01421 
01422         self->r.contents = CONTENTS_TRIGGER;
01423 
01424         if ( self->spawnflags & 2 ) {
01425                 self->use = hurt_use;
01426         }
01427 
01428         // link in to the world if starting active
01429         if ( ! (self->spawnflags & 1) ) {
01430                 trap_LinkEntity (self);
01431         }
01432         else if (self->r.linked)
01433         {
01434                 trap_UnlinkEntity(self);
01435         }
01436 }
01437 
01438 #define INITIAL_SUFFOCATION_DELAY       500 //.5 seconds
01439 void space_touch( gentity_t *self, gentity_t *other, trace_t *trace )
01440 {
01441         if (!other || !other->inuse || !other->client )
01442                 //NOTE: we need vehicles to know this, too...
01443                 //|| other->s.number >= MAX_CLIENTS)
01444         {
01445                 return;
01446         }
01447 
01448         if ( other->s.number < MAX_CLIENTS//player
01449                 && other->client->ps.m_iVehicleNum//in a vehicle
01450                 && other->client->ps.m_iVehicleNum >= MAX_CLIENTS )
01451         {//a player client inside a vehicle
01452                 gentity_t *veh = &g_entities[other->client->ps.m_iVehicleNum];
01453 
01454                 if (veh->inuse && veh->client && veh->m_pVehicle &&
01455                         veh->m_pVehicle->m_pVehicleInfo->hideRider)
01456                 { //if they are "inside" a vehicle, then let that protect them from THE HORRORS OF SPACE.
01457                         other->client->inSpaceSuffocation = 0;
01458                         other->client->inSpaceIndex = ENTITYNUM_NONE;
01459                         return;
01460                 }
01461         }
01462 
01463         if (!G_PointInBounds(other->client->ps.origin, self->r.absmin, self->r.absmax))
01464         { //his origin must be inside the trigger
01465                 return;
01466         }
01467 
01468         if (!other->client->inSpaceIndex ||
01469                 other->client->inSpaceIndex == ENTITYNUM_NONE)
01470         { //freshly entering space
01471                 other->client->inSpaceSuffocation = level.time + INITIAL_SUFFOCATION_DELAY;
01472         }
01473 
01474         other->client->inSpaceIndex = self->s.number;
01475 }
01476 
01477 /*QUAKED trigger_space (.5 .5 .5) ? 
01478 causes human clients to suffocate and have no gravity.
01479 
01480 */
01481 void SP_trigger_space(gentity_t *self)
01482 {
01483         InitTrigger(self);
01484         self->r.contents = CONTENTS_TRIGGER;
01485         
01486         self->touch = space_touch;
01487 
01488     trap_LinkEntity(self);
01489 }
01490 
01491 void shipboundary_touch( gentity_t *self, gentity_t *other, trace_t *trace )
01492 {
01493         gentity_t *ent;
01494 
01495         if (!other || !other->inuse || !other->client ||
01496                 other->s.number < MAX_CLIENTS ||
01497                 !other->m_pVehicle)
01498         { //only let vehicles touch
01499                 return;
01500         }
01501 
01502         if ( other->client->ps.hyperSpaceTime && level.time - other->client->ps.hyperSpaceTime < HYPERSPACE_TIME )
01503         {//don't interfere with hyperspacing ships
01504                 return;
01505         }
01506 
01507         ent = G_Find (NULL, FOFS(targetname), self->target);
01508         if (!ent || !ent->inuse)
01509         { //this is bad
01510                 G_Error("trigger_shipboundary has invalid target '%s'\n", self->target);
01511                 return;
01512         }
01513 
01514         if (!other->client->ps.m_iVehicleNum || other->m_pVehicle->m_iRemovedSurfaces)
01515         { //if a vehicle touches a boundary without a pilot in it or with parts missing, just blow the thing up
01516                 G_Damage(other, other, other, NULL, other->client->ps.origin, 99999, DAMAGE_NO_PROTECTION, MOD_SUICIDE);
01517                 return;
01518         }
01519 
01520         //make sure this sucker is linked so the prediction knows where to go
01521         trap_LinkEntity(ent);
01522 
01523         other->client->ps.vehTurnaroundIndex = ent->s.number;
01524         other->client->ps.vehTurnaroundTime = level.time + (self->genericValue1*2);
01525 
01526         //keep up the detailed checks for another 2 seconds
01527         self->genericValue7 = level.time + 2000;
01528 }
01529 
01530 void shipboundary_think(gentity_t *ent)
01531 {
01532         int                     iEntityList[MAX_GENTITIES];
01533         int                     numListedEntities;
01534         int                     i = 0;
01535         gentity_t       *listedEnt;
01536 
01537         ent->nextthink = level.time + 100;
01538 
01539         if (ent->genericValue7 < level.time)
01540         { //don't need to be doing this check, no one has touched recently
01541                 return;
01542         }
01543 
01544         numListedEntities = trap_EntitiesInBox( ent->r.absmin, ent->r.absmax, iEntityList, MAX_GENTITIES );
01545         while (i < numListedEntities)
01546         {
01547                 listedEnt = &g_entities[iEntityList[i]];
01548                 if (listedEnt->inuse && listedEnt->client && listedEnt->client->ps.m_iVehicleNum)
01549                 {
01550             if (listedEnt->s.eType == ET_NPC &&
01551                                 listedEnt->s.NPC_class == CLASS_VEHICLE)
01552                         {
01553                                 Vehicle_t *pVeh = listedEnt->m_pVehicle;
01554                                 if (pVeh && pVeh->m_pVehicleInfo->type == VH_FIGHTER)
01555                                 {
01556                     shipboundary_touch(ent, listedEnt, NULL);
01557                                 }
01558                         }
01559                 }
01560                 i++;
01561         }
01562 }
01563 
01564 /*QUAKED trigger_shipboundary (.5 .5 .5) ? 
01565 causes vehicle to turn toward target and travel in that direction for a set time when hit.
01566 
01567 "target"                name of entity to turn toward (can be info_notnull, or whatever).
01568 "traveltime"    time to travel in this direction
01569 
01570 */
01571 void SP_trigger_shipboundary(gentity_t *self)
01572 {
01573         InitTrigger(self);
01574         self->r.contents = CONTENTS_TRIGGER;
01575         
01576         if (!self->target || !self->target[0])
01577         {
01578                 G_Error("trigger_shipboundary without a target.");
01579         }
01580         G_SpawnInt("traveltime", "0", &self->genericValue1);
01581 
01582         if (!self->genericValue1)
01583         {
01584                 G_Error("trigger_shipboundary without traveltime.");
01585         }
01586 
01587         self->think = shipboundary_think;
01588         self->nextthink = level.time + 500;
01589         self->touch = shipboundary_touch;
01590 
01591     trap_LinkEntity(self);
01592 }
01593 
01594 void hyperspace_touch( gentity_t *self, gentity_t *other, trace_t *trace )
01595 {
01596         gentity_t *ent;
01597 
01598         if (!other || !other->inuse || !other->client ||
01599                 other->s.number < MAX_CLIENTS ||
01600                 !other->m_pVehicle)
01601         { //only let vehicles touch
01602                 return;
01603         }
01604 
01605         if ( other->client->ps.hyperSpaceTime && level.time - other->client->ps.hyperSpaceTime < HYPERSPACE_TIME )
01606         {//already hyperspacing, just keep us moving
01607                 if ( (other->client->ps.eFlags2&EF2_HYPERSPACE) )
01608                 {//they've started the hyperspace but haven't been teleported yet
01609                         float timeFrac = ((float)(level.time-other->client->ps.hyperSpaceTime))/HYPERSPACE_TIME;
01610                         if ( timeFrac >= HYPERSPACE_TELEPORT_FRAC )
01611                         {//half-way, now teleport them!
01612                                 vec3_t  diff, fwd, right, up, newOrg;
01613                                 float   fDiff, rDiff, uDiff;
01614                                 //take off the flag so we only do this once
01615                                 other->client->ps.eFlags2 &= ~EF2_HYPERSPACE;
01616                                 //Get the offset from the local position
01617                                 ent = G_Find (NULL, FOFS(targetname), self->target);
01618                                 if (!ent || !ent->inuse)
01619                                 { //this is bad
01620                                         G_Error("trigger_hyperspace has invalid target '%s'\n", self->target);
01621                                         return;
01622                                 }
01623                                 VectorSubtract( other->client->ps.origin, ent->s.origin, diff );
01624                                 AngleVectors( ent->s.angles, fwd, right, up );
01625                                 fDiff = DotProduct( fwd, diff );
01626                                 rDiff = DotProduct( right, diff );
01627                                 uDiff = DotProduct( up, diff );
01628                                 //Now get the base position of the destination
01629                                 ent = G_Find (NULL, FOFS(targetname), self->target2);
01630                                 if (!ent || !ent->inuse)
01631                                 { //this is bad
01632                                         G_Error("trigger_hyperspace has invalid target2 '%s'\n", self->target2);
01633                                         return;
01634                                 }
01635                                 VectorCopy( ent->s.origin, newOrg );
01636                                 //finally, add the offset into the new origin
01637                                 AngleVectors( ent->s.angles, fwd, right, up );
01638                                 VectorMA( newOrg, fDiff, fwd, newOrg );
01639                                 VectorMA( newOrg, rDiff, right, newOrg );
01640                                 VectorMA( newOrg, uDiff, up, newOrg );
01641                                 //G_Printf("hyperspace from %s to %s\n", vtos(other->client->ps.origin), vtos(newOrg) );
01642                                 //now put them in the offset position, facing the angles that position wants them to be facing
01643                                 TeleportPlayer( other, newOrg, ent->s.angles );
01644                                 if ( other->m_pVehicle && other->m_pVehicle->m_pPilot )
01645                                 {//teleport the pilot, too
01646                                         TeleportPlayer( (gentity_t*)other->m_pVehicle->m_pPilot, newOrg, ent->s.angles );
01647                                         //FIXME: and the passengers?
01648                                 }
01649                                 //make them face the new angle
01650                                 //other->client->ps.hyperSpaceIndex = ent->s.number;
01651                                 VectorCopy( ent->s.angles, other->client->ps.hyperSpaceAngles );
01652                                 //sound
01653                                 G_Sound( other, CHAN_LOCAL, G_SoundIndex( "sound/vehicles/common/hyperend.wav" ) );
01654                         }
01655                 }
01656                 return;
01657         }
01658         else
01659         {
01660                 ent = G_Find (NULL, FOFS(targetname), self->target);
01661                 if (!ent || !ent->inuse)
01662                 { //this is bad
01663                         G_Error("trigger_hyperspace has invalid target '%s'\n", self->target);
01664                         return;
01665                 }
01666 
01667                 if (!other->client->ps.m_iVehicleNum || other->m_pVehicle->m_iRemovedSurfaces)
01668                 { //if a vehicle touches a boundary without a pilot in it or with parts missing, just blow the thing up
01669                         G_Damage(other, other, other, NULL, other->client->ps.origin, 99999, DAMAGE_NO_PROTECTION, MOD_SUICIDE);
01670                         return;
01671                 }
01672                 //other->client->ps.hyperSpaceIndex = ent->s.number;
01673                 VectorCopy( ent->s.angles, other->client->ps.hyperSpaceAngles );
01674                 other->client->ps.hyperSpaceTime = level.time;
01675         }
01676 }
01677 
01678 /*
01679 void trigger_hyperspace_find_targets( gentity_t *self )
01680 {
01681         gentity_t *targEnt = NULL;
01682         targEnt = G_Find (NULL, FOFS(targetname), self->target);
01683         if (!targEnt || !targEnt->inuse)
01684         { //this is bad
01685                 G_Error("trigger_hyperspace has invalid target '%s'\n", self->target);
01686                 return;
01687         }
01688         targEnt->r.svFlags |= SVF_BROADCAST;//crap, need to tell the cgame about the target_position
01689         targEnt = G_Find (NULL, FOFS(targetname), self->target2);
01690         if (!targEnt || !targEnt->inuse)
01691         { //this is bad
01692                 G_Error("trigger_hyperspace has invalid target2 '%s'\n", self->target2);
01693                 return;
01694         }
01695         targEnt->r.svFlags |= SVF_BROADCAST;//crap, need to tell the cgame about the target_position
01696 }
01697 */
01698 /*QUAKED trigger_hyperspace (.5 .5 .5) ? 
01699 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
01700 
01701 "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
01702 "target2"               name of target_position to teleport the ship to (will be relative to it's origin)
01703 */
01704 void SP_trigger_hyperspace(gentity_t *self)
01705 {
01706         //register the hyperspace end sound (start sounds are customized)
01707         G_SoundIndex( "sound/vehicles/common/hyperend.wav" );
01708 
01709         InitTrigger(self);
01710         self->r.contents = CONTENTS_TRIGGER;
01711         
01712         if (!self->target || !self->target[0])
01713         {
01714                 G_Error("trigger_hyperspace without a target.");
01715         }
01716         if (!self->target2 || !self->target2[0])
01717         {
01718                 G_Error("trigger_hyperspace without a target2.");
01719         }
01720         
01721         self->delay = Distance( self->r.absmax, self->r.absmin );//my size
01722 
01723         self->touch = hyperspace_touch;
01724 
01725     trap_LinkEntity(self);
01726 
01727         //self->think = trigger_hyperspace_find_targets;
01728         //self->nextthink = level.time + FRAMETIME;
01729 }
01730 /*
01731 ==============================================================================
01732 
01733 timer
01734 
01735 ==============================================================================
01736 */
01737 
01738 
01739 /*QUAKED func_timer (0.3 0.1 0.6) (-8 -8 -8) (8 8 8) START_ON
01740 This should be renamed trigger_timer...
01741 Repeatedly fires its targets.
01742 Can be turned on or off by using.
01743 
01744 "wait"                  base time between triggering all targets, default is 1
01745 "random"                wait variance, default is 0
01746 so, the basic time between firing is a random time between
01747 (wait - random) and (wait + random)
01748 
01749 */
01750 void func_timer_think( gentity_t *self ) {
01751         G_UseTargets (self, self->activator);
01752         // set time before next firing
01753         self->nextthink = level.time + 1000 * ( self->wait + crandom() * self->random );
01754 }
01755 
01756 void func_timer_use( gentity_t *self, gentity_t *other, gentity_t *activator ) {
01757         self->activator = activator;
01758 
01759         G_ActivateBehavior(self,BSET_USE);
01760 
01761         // if on, turn it off
01762         if ( self->nextthink ) {
01763                 self->nextthink = 0;
01764                 return;
01765         }
01766 
01767         // turn it on
01768         func_timer_think (self);
01769 }
01770 
01771 void SP_func_timer( gentity_t *self ) {
01772         G_SpawnFloat( "random", "1", &self->random);
01773         G_SpawnFloat( "wait", "1", &self->wait );
01774 
01775         self->use = func_timer_use;
01776         self->think = func_timer_think;
01777 
01778         if ( self->random >= self->wait ) {
01779                 self->random = self->wait - 1;//NOTE: was - FRAMETIME, but FRAMETIME is in msec (100) and these numbers are in *seconds*!
01780                 G_Printf( "func_timer at %s has random >= wait\n", vtos( self->s.origin ) );
01781         }
01782 
01783         if ( self->spawnflags & 1 ) {
01784                 self->nextthink = level.time + FRAMETIME;
01785                 self->activator = self;
01786         }
01787 
01788         self->r.svFlags = SVF_NOCLIENT;
01789 }
01790 
01791 gentity_t *asteroid_pick_random_asteroid( gentity_t *self )
01792 {
01793         int                     t_count = 0, pick;
01794         gentity_t       *t = NULL;
01795 
01796         while ( (t = G_Find (t, FOFS(targetname), self->target)) != NULL )
01797         {
01798                 if (t != self)
01799                 {
01800                         t_count++;
01801                 }
01802         }
01803 
01804         if(!t_count)
01805         {
01806                 return NULL;
01807         }
01808 
01809         if(t_count == 1)
01810         {
01811                 return t;
01812         }
01813 
01814         //FIXME: need a seed
01815         pick = Q_irand(1, t_count);
01816         t_count = 0;
01817         while ( (t = G_Find (t, FOFS(targetname), self->target)) != NULL )
01818         {
01819                 if (t != self)
01820                 {
01821                         t_count++;
01822                 }
01823                 else
01824                 {
01825                         continue;
01826                 }
01827                 
01828                 if(t_count == pick)
01829                 {
01830                         return t;
01831                 }
01832         }
01833         return NULL;
01834 }
01835 
01836 int asteroid_count_num_asteroids( gentity_t *self )
01837 {
01838         int     i, count = 0;
01839 
01840         for ( i = MAX_CLIENTS; i < ENTITYNUM_WORLD; i++ )
01841         {
01842                 if ( !g_entities[i].inuse )
01843                 {
01844                         continue;
01845                 }
01846                 if ( g_entities[i].r.ownerNum == self->s.number )
01847                 {
01848                         count++;
01849                 }
01850         }
01851         return count;
01852 }
01853 
01854 extern void SP_func_rotating (gentity_t *ent);
01855 extern void Q3_Lerp2Origin( int taskID, int entID, vec3_t origin, float duration );
01856 void asteroid_field_think(gentity_t *self)
01857 {
01858         int numAsteroids = asteroid_count_num_asteroids( self );
01859 
01860         self->nextthink = level.time + 500;
01861 
01862         if ( numAsteroids < self->count )
01863         {
01864                 //need to spawn a new asteroid
01865                 gentity_t *newAsteroid = G_Spawn();
01866                 if ( newAsteroid )
01867                 {
01868                         vec3_t startSpot, endSpot, startAngles;
01869                         float dist, speed = flrand( self->speed * 0.25f, self->speed * 2.0f );
01870                         int     capAxis, axis, time = 0;
01871                         gentity_t *copyAsteroid = asteroid_pick_random_asteroid( self );
01872                         if ( copyAsteroid )
01873                         {
01874                                 newAsteroid->model = copyAsteroid->model;
01875                                 newAsteroid->model2 = copyAsteroid->model2;
01876                                 newAsteroid->health = copyAsteroid->health;
01877                                 newAsteroid->spawnflags = copyAsteroid->spawnflags;
01878                                 newAsteroid->mass = copyAsteroid->mass;
01879                                 newAsteroid->damage = copyAsteroid->damage;
01880                                 newAsteroid->speed = copyAsteroid->speed;
01881 
01882                                 G_SetOrigin( newAsteroid, copyAsteroid->s.origin );
01883                                 G_SetAngles( newAsteroid, copyAsteroid->s.angles );
01884                                 newAsteroid->classname = "func_rotating";
01885 
01886                                 SP_func_rotating( newAsteroid );
01887 
01888                                 newAsteroid->genericValue15 = copyAsteroid->genericValue15;
01889                                 newAsteroid->s.iModelScale = copyAsteroid->s.iModelScale;
01890                                 newAsteroid->maxHealth = newAsteroid->health;
01891                                 G_ScaleNetHealth(newAsteroid);
01892                                 newAsteroid->radius = copyAsteroid->radius;
01893                                 newAsteroid->material = copyAsteroid->material;
01894                                 //CacheChunkEffects( self->material );
01895 
01896                                 //keep track of it
01897                                 newAsteroid->r.ownerNum = self->s.number;
01898 
01899                                 //move it
01900                                 capAxis = Q_irand( 0, 2 );
01901                                 for ( axis = 0; axis < 3; axis++ )
01902                                 {
01903                                         if ( axis == capAxis )
01904                                         {
01905                                                 if ( Q_irand( 0, 1 ) )
01906                                                 {
01907                                                         startSpot[axis] = self->r.mins[axis];
01908                                                         endSpot[axis] = self->r.maxs[axis];
01909                                                 }
01910                                                 else
01911                                                 {
01912                                                         startSpot[axis] = self->r.maxs[axis];
01913                                                         endSpot[axis] = self->r.mins[axis];
01914                                                 }
01915                                         }
01916                                         else
01917                                         {
01918                                                 startSpot[axis] = self->r.mins[axis]+(flrand(0,1.0f)*(self->r.maxs[axis]-self->r.mins[axis]));
01919                                                 endSpot[axis] = self->r.mins[axis]+(flrand(0,1.0f)*(self->r.maxs[axis]-self->r.mins[axis]));
01920                                         }
01921                                 }
01922                                 //FIXME: maybe trace from start to end to make sure nothing is in the way?  How big of a trace?
01923 
01924                                 G_SetOrigin( newAsteroid, startSpot );
01925                                 dist = Distance( endSpot, startSpot );
01926                                 time = ceil(dist/speed)*1000;
01927                                 Q3_Lerp2Origin( -1, newAsteroid->s.number, endSpot, time );
01928 
01929                                 //spin it
01930                                 startAngles[0] = flrand( -360, 360 );
01931                                 startAngles[1] = flrand( -360, 360 );
01932                                 startAngles[2] = flrand( -360, 360 );
01933                                 G_SetAngles( newAsteroid, startAngles );
01934                                 newAsteroid->s.apos.trDelta[0] = flrand( -100, 100 );
01935                                 newAsteroid->s.apos.trDelta[1] = flrand( -100, 100 );
01936                                 newAsteroid->s.apos.trDelta[2] = flrand( -100, 100 );
01937                                 newAsteroid->s.apos.trTime = level.time;
01938                                 newAsteroid->s.apos.trType = TR_LINEAR;
01939 
01940                                 //remove itself when done
01941                                 newAsteroid->think = G_FreeEntity;
01942                                 newAsteroid->nextthink = level.time+time;
01943 
01944                                 //think again sooner if need even more
01945                                 if ( numAsteroids+1 < self->count )
01946                                 {//still need at least one more
01947                                         //spawn it in 100ms
01948                                         self->nextthink = level.time + 100;
01949                                 }
01950                         }
01951                 }
01952         }
01953 }
01954 
01955 /*QUAKED trigger_asteroid_field (.5 .5 .5) ? 
01956 speed - how fast, on average, the asteroid moves
01957 count - how many asteroids, max, to have at one time
01958 target - target this at func_rotating asteroids
01959 */
01960 void SP_trigger_asteroid_field(gentity_t *self)
01961 {
01962         trap_SetBrushModel( self, self->model );
01963         self->r.contents = 0;
01964 
01965         if ( !self->count )
01966         {
01967                 self->health = 20;
01968         }
01969 
01970         if ( !self->speed )
01971         {
01972                 self->speed = 10000;
01973         }
01974 
01975         self->think = asteroid_field_think;
01976         self->nextthink = level.time + 100;
01977 
01978     trap_LinkEntity(self);
01979 }