codemp/game/g_utils.c

Go to the documentation of this file.
00001 // Copyright (C) 1999-2000 Id Software, Inc.
00002 //
00003 // g_utils.c -- misc utility functions for game module
00004 
00005 #include "g_local.h"
00006 #include "bg_saga.h"
00007 #include "q_shared.h"
00008 
00009 typedef struct {
00010   char oldShader[MAX_QPATH];
00011   char newShader[MAX_QPATH];
00012   float timeOffset;
00013 } shaderRemap_t;
00014 
00015 #define MAX_SHADER_REMAPS 128
00016 
00017 int remapCount = 0;
00018 shaderRemap_t remappedShaders[MAX_SHADER_REMAPS];
00019 
00020 void AddRemap(const char *oldShader, const char *newShader, float timeOffset) {
00021         int i;
00022 
00023         for (i = 0; i < remapCount; i++) {
00024                 if (Q_stricmp(oldShader, remappedShaders[i].oldShader) == 0) {
00025                         // found it, just update this one
00026                         strcpy(remappedShaders[i].newShader,newShader);
00027                         remappedShaders[i].timeOffset = timeOffset;
00028                         return;
00029                 }
00030         }
00031         if (remapCount < MAX_SHADER_REMAPS) {
00032                 strcpy(remappedShaders[remapCount].newShader,newShader);
00033                 strcpy(remappedShaders[remapCount].oldShader,oldShader);
00034                 remappedShaders[remapCount].timeOffset = timeOffset;
00035                 remapCount++;
00036         }
00037 }
00038 
00039 const char *BuildShaderStateConfig(void) {
00040         static char     buff[MAX_STRING_CHARS*4];
00041         char out[(MAX_QPATH * 2) + 5];
00042         int i;
00043   
00044         memset(buff, 0, MAX_STRING_CHARS);
00045         for (i = 0; i < remapCount; i++) {
00046                 Com_sprintf(out, (MAX_QPATH * 2) + 5, "%s=%s:%5.2f@", remappedShaders[i].oldShader, remappedShaders[i].newShader, remappedShaders[i].timeOffset);
00047                 Q_strcat( buff, sizeof( buff ), out);
00048         }
00049         return buff;
00050 }
00051 
00052 /*
00053 =========================================================================
00054 
00055 model / sound configstring indexes
00056 
00057 =========================================================================
00058 */
00059 
00060 /*
00061 ================
00062 G_FindConfigstringIndex
00063 
00064 ================
00065 */
00066 static int G_FindConfigstringIndex( const char *name, int start, int max, qboolean create ) {
00067         int             i;
00068         char    s[MAX_STRING_CHARS];
00069 
00070         if ( !name || !name[0] ) {
00071                 return 0;
00072         }
00073 
00074         for ( i=1 ; i<max ; i++ ) {
00075                 trap_GetConfigstring( start + i, s, sizeof( s ) );
00076                 if ( !s[0] ) {
00077                         break;
00078                 }
00079                 if ( !strcmp( s, name ) ) {
00080                         return i;
00081                 }
00082         }
00083 
00084         if ( !create ) {
00085                 return 0;
00086         }
00087 
00088         if ( i == max ) {
00089                 G_Error( "G_FindConfigstringIndex: overflow" );
00090         }
00091 
00092         trap_SetConfigstring( start + i, name );
00093 
00094         return i;
00095 }
00096 
00097 /*
00098 Ghoul2 Insert Start
00099 */
00100 
00101 int G_BoneIndex( const char *name ) {
00102         return G_FindConfigstringIndex (name, CS_G2BONES, MAX_G2BONES, qtrue);
00103 }
00104 /*
00105 Ghoul2 Insert End
00106 */
00107 
00108 int G_ModelIndex( const char *name ) {
00109 #ifdef _DEBUG_MODEL_PATH_ON_SERVER
00110         //debug to see if we are shoving data into configstrings for models that don't exist, and if
00111         //so, where we are doing it from -rww
00112         fileHandle_t fh;
00113 
00114         trap_FS_FOpenFile(name, &fh, FS_READ);
00115         if (!fh)
00116         { //try models/ then, this is assumed for registering models
00117                 trap_FS_FOpenFile(va("models/%s", name), &fh, FS_READ);
00118                 if (!fh)
00119                 {
00120                         Com_Printf("ERROR: Server tried to modelindex %s but it doesn't exist.\n", name);
00121                 }
00122         }
00123 
00124         if (fh)
00125         {
00126                 trap_FS_FCloseFile(fh);
00127         }
00128 #endif
00129         return G_FindConfigstringIndex (name, CS_MODELS, MAX_MODELS, qtrue);
00130 }
00131 
00132 int     G_IconIndex( const char* name ) 
00133 {
00134         assert(name && name[0]);
00135         return G_FindConfigstringIndex (name, CS_ICONS, MAX_ICONS, qtrue);
00136 }
00137 
00138 int G_SoundIndex( const char *name ) {
00139         assert(name && name[0]);
00140         return G_FindConfigstringIndex (name, CS_SOUNDS, MAX_SOUNDS, qtrue);
00141 }
00142 
00143 int G_SoundSetIndex(const char *name)
00144 {
00145         return G_FindConfigstringIndex (name, CS_AMBIENT_SET, MAX_AMBIENT_SETS, qtrue);
00146 }
00147 
00148 int G_EffectIndex( const char *name )
00149 {
00150         return G_FindConfigstringIndex (name, CS_EFFECTS, MAX_FX, qtrue);
00151 }
00152 
00153 int G_BSPIndex( const char *name )
00154 {
00155         return G_FindConfigstringIndex (name, CS_BSP_MODELS, MAX_SUB_BSP, qtrue);
00156 }
00157 
00158 //=====================================================================
00159 
00160 
00161 //see if we can or should allow this guy to use a custom skeleton -rww
00162 qboolean G_PlayerHasCustomSkeleton(gentity_t *ent)
00163 {
00164         /*
00165         siegeClass_t *scl;
00166 
00167         if (g_gametype.integer != GT_SIEGE)
00168         { //only in siege
00169                 return qfalse;
00170         }
00171 
00172         if (ent->s.number >= MAX_CLIENTS ||
00173                 !ent->client ||
00174                 ent->client->siegeClass == -1)
00175         { //invalid class
00176                 return qfalse;
00177         }
00178 
00179         scl = &bgSiegeClasses[ent->client->siegeClass];
00180         if (!(scl->classflags & (1<<CFL_CUSTOMSKEL)))
00181         { //class is not flagged for this
00182                 return qfalse;
00183         }
00184 
00185         return qtrue;
00186         */
00187         return qfalse;
00188 }
00189 
00190 /*
00191 ================
00192 G_TeamCommand
00193 
00194 Broadcasts a command to only a specific team
00195 ================
00196 */
00197 void G_TeamCommand( team_t team, char *cmd ) {
00198         int             i;
00199 
00200         for ( i = 0 ; i < level.maxclients ; i++ ) {
00201                 if ( level.clients[i].pers.connected == CON_CONNECTED ) {
00202                         if ( level.clients[i].sess.sessionTeam == team ) {
00203                                 trap_SendServerCommand( i, va("%s", cmd ));
00204                         }
00205                 }
00206         }
00207 }
00208 
00209 
00210 /*
00211 =============
00212 G_Find
00213 
00214 Searches all active entities for the next one that holds
00215 the matching string at fieldofs (use the FOFS() macro) in the structure.
00216 
00217 Searches beginning at the entity after from, or the beginning if NULL
00218 NULL will be returned if the end of the list is reached.
00219 
00220 =============
00221 */
00222 gentity_t *G_Find (gentity_t *from, int fieldofs, const char *match)
00223 {
00224         char    *s;
00225 
00226         if (!from)
00227                 from = g_entities;
00228         else
00229                 from++;
00230 
00231         for ( ; from < &g_entities[level.num_entities] ; from++)
00232         {
00233                 if (!from->inuse)
00234                         continue;
00235                 s = *(char **) ((byte *)from + fieldofs);
00236                 if (!s)
00237                         continue;
00238                 if (!Q_stricmp (s, match))
00239                         return from;
00240         }
00241 
00242         return NULL;
00243 }
00244 
00245 
00246 
00247 /*
00248 ============
00249 G_RadiusList - given an origin and a radius, return all entities that are in use that are within the list
00250 ============
00251 */
00252 int G_RadiusList ( vec3_t origin, float radius, gentity_t *ignore, qboolean takeDamage, gentity_t *ent_list[MAX_GENTITIES])                                       
00253 {
00254         float           dist;
00255         gentity_t       *ent;
00256         int                     entityList[MAX_GENTITIES];
00257         int                     numListedEntities;
00258         vec3_t          mins, maxs;
00259         vec3_t          v;
00260         int                     i, e;
00261         int                     ent_count = 0;
00262 
00263         if ( radius < 1 ) 
00264         {
00265                 radius = 1;
00266         }
00267 
00268         for ( i = 0 ; i < 3 ; i++ ) 
00269         {
00270                 mins[i] = origin[i] - radius;
00271                 maxs[i] = origin[i] + radius;
00272         }
00273 
00274         numListedEntities = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
00275 
00276         for ( e = 0 ; e < numListedEntities ; e++ ) 
00277         {
00278                 ent = &g_entities[entityList[ e ]];
00279 
00280                 if ((ent == ignore) || !(ent->inuse) || ent->takedamage != takeDamage)
00281                         continue;
00282 
00283                 // find the distance from the edge of the bounding box
00284                 for ( i = 0 ; i < 3 ; i++ ) 
00285                 {
00286                         if ( origin[i] < ent->r.absmin[i] ) 
00287                         {
00288                                 v[i] = ent->r.absmin[i] - origin[i];
00289                         } else if ( origin[i] > ent->r.absmax[i] ) 
00290                         {
00291                                 v[i] = origin[i] - ent->r.absmax[i];
00292                         } else 
00293                         {
00294                                 v[i] = 0;
00295                         }
00296                 }
00297 
00298                 dist = VectorLength( v );
00299                 if ( dist >= radius ) 
00300                 {
00301                         continue;
00302                 }
00303                 
00304                 // ok, we are within the radius, add us to the incoming list
00305                 ent_list[ent_count] = ent;
00306                 ent_count++;
00307 
00308         }
00309         // we are done, return how many we found
00310         return(ent_count);
00311 }
00312 
00313 
00314 //----------------------------------------------------------
00315 void G_Throw( gentity_t *targ, vec3_t newDir, float push )
00316 //----------------------------------------------------------
00317 {
00318         vec3_t  kvel;
00319         float   mass;
00320 
00321         if ( targ->physicsBounce > 0 )  //overide the mass
00322         {
00323                 mass = targ->physicsBounce;
00324         }
00325         else
00326         {
00327                 mass = 200;
00328         }
00329 
00330         if ( g_gravity.value > 0 )
00331         {
00332                 VectorScale( newDir, g_knockback.value * (float)push / mass * 0.8, kvel );
00333                 kvel[2] = newDir[2] * g_knockback.value * (float)push / mass * 1.5;
00334         }
00335         else
00336         {
00337                 VectorScale( newDir, g_knockback.value * (float)push / mass, kvel );
00338         }
00339 
00340         if ( targ->client )
00341         {
00342                 VectorAdd( targ->client->ps.velocity, kvel, targ->client->ps.velocity );
00343         }
00344         else if ( targ->s.pos.trType != TR_STATIONARY && targ->s.pos.trType != TR_LINEAR_STOP && targ->s.pos.trType != TR_NONLINEAR_STOP )
00345         {
00346                 VectorAdd( targ->s.pos.trDelta, kvel, targ->s.pos.trDelta );
00347                 VectorCopy( targ->r.currentOrigin, targ->s.pos.trBase );
00348                 targ->s.pos.trTime = level.time;
00349         }
00350 
00351         // set the timer so that the other client can't cancel
00352         // out the movement immediately
00353         if ( targ->client && !targ->client->ps.pm_time ) 
00354         {
00355                 int             t;
00356 
00357                 t = push * 2;
00358 
00359                 if ( t < 50 ) 
00360                 {
00361                         t = 50;
00362                 }
00363                 if ( t > 200 ) 
00364                 {
00365                         t = 200;
00366                 }
00367                 targ->client->ps.pm_time = t;
00368                 targ->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
00369         }
00370 }
00371 
00372 //methods of creating/freeing "fake" dynamically allocated client entity structures.
00373 //You ABSOLUTELY MUST free this client after creating it before game shutdown. If
00374 //it is left around you will have a memory leak, because true dynamic memory is
00375 //allocated by the exe.
00376 void G_FreeFakeClient(gclient_t **cl)
00377 { //or not, the dynamic stuff is busted somehow at the moment. Yet it still works in the test.
00378   //I think something is messed up in being able to cast the memory to stuff to modify it,
00379   //while modifying it directly seems to work fine.
00380         //trap_TrueFree((void **)cl);
00381 }
00382 
00383 //allocate a veh object
00384 #define MAX_VEHICLES_AT_A_TIME          128
00385 static Vehicle_t g_vehiclePool[MAX_VEHICLES_AT_A_TIME];
00386 static qboolean g_vehiclePoolOccupied[MAX_VEHICLES_AT_A_TIME];
00387 static qboolean g_vehiclePoolInit = qfalse;
00388 void G_AllocateVehicleObject(Vehicle_t **pVeh)
00389 {
00390         int i = 0;
00391 
00392         if (!g_vehiclePoolInit)
00393         {
00394                 g_vehiclePoolInit = qtrue;
00395                 memset(g_vehiclePoolOccupied, 0, sizeof(g_vehiclePoolOccupied));
00396         }
00397 
00398         while (i < MAX_VEHICLES_AT_A_TIME)
00399         { //iterate through and try to find a free one
00400                 if (!g_vehiclePoolOccupied[i])
00401                 {
00402                         g_vehiclePoolOccupied[i] = qtrue;
00403                         memset(&g_vehiclePool[i], 0, sizeof(Vehicle_t));
00404                         *pVeh = &g_vehiclePool[i];
00405                         return;
00406                 }
00407                 i++;
00408         }
00409         Com_Error(ERR_DROP, "Ran out of vehicle pool slots.");
00410 }
00411 
00412 //free the pointer, sort of a lame method
00413 void G_FreeVehicleObject(Vehicle_t *pVeh)
00414 {
00415         int i = 0;
00416         while (i < MAX_VEHICLES_AT_A_TIME)
00417         {
00418                 if (g_vehiclePoolOccupied[i] &&
00419                         &g_vehiclePool[i] == pVeh)
00420                 { //guess this is it
00421                         g_vehiclePoolOccupied[i] = qfalse;
00422                         break;
00423                 }
00424                 i++;
00425         }
00426 }
00427 
00428 gclient_t *gClPtrs[MAX_GENTITIES];
00429 
00430 void G_CreateFakeClient(int entNum, gclient_t **cl)
00431 {
00432         //trap_TrueMalloc((void **)cl, sizeof(gclient_t));
00433         if (!gClPtrs[entNum])
00434         {
00435                 gClPtrs[entNum] = (gclient_t *) BG_Alloc(sizeof(gclient_t));
00436         }
00437         *cl = gClPtrs[entNum];
00438 }
00439 
00440 #ifdef _XBOX
00441 void G_ClPtrClear(void)
00442 {
00443         for(int i=0; i<MAX_GENTITIES; i++) {
00444                 gClPtrs[i] = NULL;
00445         }
00446 }
00447 #endif
00448 
00449 //call this on game shutdown to run through and get rid of all the lingering client pointers.
00450 void G_CleanAllFakeClients(void)
00451 {
00452         int i = MAX_CLIENTS; //start off here since all ents below have real client structs.
00453         gentity_t *ent;
00454 
00455         while (i < MAX_GENTITIES)
00456         {
00457                 ent = &g_entities[i];
00458 
00459                 if (ent->inuse && ent->s.eType == ET_NPC && ent->client)
00460                 {
00461                         G_FreeFakeClient(&ent->client);
00462                 }
00463                 i++;
00464         }
00465 }
00466 
00467 /*
00468 =============
00469 G_SetAnim
00470 
00471 Finally reworked PM_SetAnim to allow non-pmove calls, so we take our
00472 local anim index into account and make the call -rww
00473 =============
00474 */
00475 #include "../namespace_begin.h"
00476 void BG_SetAnim(playerState_t *ps, animation_t *animations, int setAnimParts,int anim,int setAnimFlags, int blendTime);
00477 #include "../namespace_end.h"
00478 
00479 void G_SetAnim(gentity_t *ent, usercmd_t *ucmd, int setAnimParts, int anim, int setAnimFlags, int blendTime)
00480 {
00481 #if 0 //old hackish way
00482         pmove_t pmv;
00483 
00484         assert(ent && ent->inuse && ent->client);
00485 
00486         memset (&pmv, 0, sizeof(pmv));
00487         pmv.ps = &ent->client->ps;
00488         pmv.animations = bgAllAnims[ent->localAnimIndex].anims;
00489         if (!ucmd)
00490         {
00491                 pmv.cmd = ent->client->pers.cmd;
00492         }
00493         else
00494         {
00495                 pmv.cmd = *ucmd;
00496         }
00497         pmv.trace = trap_Trace;
00498         pmv.pointcontents = trap_PointContents;
00499         pmv.gametype = g_gametype.integer;
00500 
00501         //don't need to bother with ghoul2 stuff, it's not even used in PM_SetAnim.
00502         pm = &pmv;
00503         PM_SetAnim(setAnimParts, anim, setAnimFlags, blendTime);
00504 #else //new clean and shining way!
00505         assert(ent->client);
00506     BG_SetAnim(&ent->client->ps, bgAllAnims[ent->localAnimIndex].anims, setAnimParts,
00507                 anim, setAnimFlags, blendTime);
00508 #endif
00509 }
00510 
00511 
00512 /*
00513 =============
00514 G_PickTarget
00515 
00516 Selects a random entity from among the targets
00517 =============
00518 */
00519 #define MAXCHOICES      32
00520 
00521 gentity_t *G_PickTarget (char *targetname)
00522 {
00523         gentity_t       *ent = NULL;
00524         int             num_choices = 0;
00525         gentity_t       *choice[MAXCHOICES];
00526 
00527         if (!targetname)
00528         {
00529                 G_Printf("G_PickTarget called with NULL targetname\n");
00530                 return NULL;
00531         }
00532 
00533         while(1)
00534         {
00535                 ent = G_Find (ent, FOFS(targetname), targetname);
00536                 if (!ent)
00537                         break;
00538                 choice[num_choices++] = ent;
00539                 if (num_choices == MAXCHOICES)
00540                         break;
00541         }
00542 
00543         if (!num_choices)
00544         {
00545                 G_Printf("G_PickTarget: target %s not found\n", targetname);
00546                 return NULL;
00547         }
00548 
00549         return choice[rand() % num_choices];
00550 }
00551 
00552 void GlobalUse(gentity_t *self, gentity_t *other, gentity_t *activator)
00553 {
00554         if (!self || (self->flags & FL_INACTIVE))
00555         {
00556                 return;
00557         }
00558 
00559         if (!self->use)
00560         {
00561                 return;
00562         }
00563         self->use(self, other, activator);
00564 }
00565 
00566 void G_UseTargets2( gentity_t *ent, gentity_t *activator, const char *string ) {
00567         gentity_t               *t;
00568         
00569         if ( !ent ) {
00570                 return;
00571         }
00572 
00573         if (ent->targetShaderName && ent->targetShaderNewName) {
00574                 float f = level.time * 0.001;
00575                 AddRemap(ent->targetShaderName, ent->targetShaderNewName, f);
00576                 trap_SetConfigstring(CS_SHADERSTATE, BuildShaderStateConfig());
00577         }
00578 
00579         if ( !string || !string[0] ) {
00580                 return;
00581         }
00582 
00583         t = NULL;
00584         while ( (t = G_Find (t, FOFS(targetname), string)) != NULL ) {
00585                 if ( t == ent ) {
00586                         G_Printf ("WARNING: Entity used itself.\n");
00587                 } else {
00588                         if ( t->use ) {
00589                                 GlobalUse(t, ent, activator);
00590                         }
00591                 }
00592                 if ( !ent->inuse ) {
00593                         G_Printf("entity was removed while using targets\n");
00594                         return;
00595                 }
00596         }
00597 }
00598 /*
00599 ==============================
00600 G_UseTargets
00601 
00602 "activator" should be set to the entity that initiated the firing.
00603 
00604 Search for (string)targetname in all entities that
00605 match (string)self.target and call their .use function
00606 
00607 ==============================
00608 */
00609 void G_UseTargets( gentity_t *ent, gentity_t *activator )
00610 {
00611         if (!ent)
00612         {
00613                 return;
00614         }
00615         G_UseTargets2(ent, activator, ent->target);
00616 }
00617 
00618 
00619 /*
00620 =============
00621 TempVector
00622 
00623 This is just a convenience function
00624 for making temporary vectors for function calls
00625 =============
00626 */
00627 float   *tv( float x, float y, float z ) {
00628         static  int             index;
00629         static  vec3_t  vecs[8];
00630         float   *v;
00631 
00632         // use an array so that multiple tempvectors won't collide
00633         // for a while
00634         v = vecs[index];
00635         index = (index + 1)&7;
00636 
00637         v[0] = x;
00638         v[1] = y;
00639         v[2] = z;
00640 
00641         return v;
00642 }
00643 
00644 
00645 /*
00646 =============
00647 VectorToString
00648 
00649 This is just a convenience function
00650 for printing vectors
00651 =============
00652 */
00653 char    *vtos( const vec3_t v ) {
00654         static  int             index;
00655         static  char    str[8][32];
00656         char    *s;
00657 
00658         // use an array so that multiple vtos won't collide
00659         s = str[index];
00660         index = (index + 1)&7;
00661 
00662         Com_sprintf (s, 32, "(%i %i %i)", (int)v[0], (int)v[1], (int)v[2]);
00663 
00664         return s;
00665 }
00666 
00667 
00668 /*
00669 ===============
00670 G_SetMovedir
00671 
00672 The editor only specifies a single value for angles (yaw),
00673 but we have special constants to generate an up or down direction.
00674 Angles will be cleared, because it is being used to represent a direction
00675 instead of an orientation.
00676 ===============
00677 */
00678 void G_SetMovedir( vec3_t angles, vec3_t movedir ) {
00679         static vec3_t VEC_UP            = {0, -1, 0};
00680         static vec3_t MOVEDIR_UP        = {0, 0, 1};
00681         static vec3_t VEC_DOWN          = {0, -2, 0};
00682         static vec3_t MOVEDIR_DOWN      = {0, 0, -1};
00683 
00684         if ( VectorCompare (angles, VEC_UP) ) {
00685                 VectorCopy (MOVEDIR_UP, movedir);
00686         } else if ( VectorCompare (angles, VEC_DOWN) ) {
00687                 VectorCopy (MOVEDIR_DOWN, movedir);
00688         } else {
00689                 AngleVectors (angles, movedir, NULL, NULL);
00690         }
00691         VectorClear( angles );
00692 }
00693 
00694 void G_InitGentity( gentity_t *e ) {
00695         e->inuse = qtrue;
00696         e->classname = "noclass";
00697         e->s.number = e - g_entities;
00698         e->r.ownerNum = ENTITYNUM_NONE;
00699         e->s.modelGhoul2 = 0; //assume not
00700 
00701         trap_ICARUS_FreeEnt( e );       //ICARUS information must be added after this point
00702 }
00703 
00704 //give us some decent info on all the active ents -rww
00705 static void G_SpewEntList(void)
00706 {
00707         int i = 0;
00708         int numNPC = 0;
00709         int numProjectile = 0;
00710         int numTempEnt = 0;
00711         int numTempEntST = 0;
00712         char className[MAX_STRING_CHARS];
00713         gentity_t *ent;
00714         char *str;
00715 #ifdef FINAL_BUILD
00716         #define VM_OR_FINAL_BUILD
00717 #elif defined Q3_VM
00718         #define VM_OR_FINAL_BUILD
00719 #endif
00720 
00721 #ifndef VM_OR_FINAL_BUILD
00722         fileHandle_t fh;
00723         trap_FS_FOpenFile("entspew.txt", &fh, FS_WRITE);
00724 #endif
00725 
00726         while (i < ENTITYNUM_MAX_NORMAL)
00727         {
00728                 ent = &g_entities[i];
00729                 if (ent->inuse)
00730                 {
00731                         if (ent->s.eType == ET_NPC)
00732                         {
00733                                 numNPC++;
00734                         }
00735                         else if (ent->s.eType == ET_MISSILE)
00736                         {
00737                                 numProjectile++;
00738                         }
00739                         else if (ent->freeAfterEvent)
00740                         {
00741                                 numTempEnt++;
00742                                 if (ent->s.eFlags & EF_SOUNDTRACKER)
00743                                 {
00744                                         numTempEntST++;
00745                                 }
00746 
00747                                 str = va("TEMPENT %4i: EV %i\n", ent->s.number, ent->s.eType-ET_EVENTS);
00748                                 Com_Printf(str);
00749 #ifndef VM_OR_FINAL_BUILD
00750                                 if (fh)
00751                                 {
00752                                         trap_FS_Write(str, strlen(str), fh);
00753                                 }
00754 #endif
00755                         }
00756 
00757                         if (ent->classname && ent->classname[0])
00758                         {
00759                                 strcpy(className, ent->classname);
00760                         }
00761                         else
00762                         {
00763                                 strcpy(className, "Unknown");
00764                         }
00765                         str = va("ENT %4i: Classname %s\n", ent->s.number, className);
00766                         Com_Printf(str);
00767 #ifndef VM_OR_FINAL_BUILD
00768                         if (fh)
00769                         {
00770                                 trap_FS_Write(str, strlen(str), fh);
00771                         }
00772 #endif
00773                 }
00774 
00775                 i++;
00776         }
00777 
00778         str = va("TempEnt count: %i\nTempEnt ST: %i\nNPC count: %i\nProjectile count: %i\n", numTempEnt, numTempEntST, numNPC, numProjectile);
00779         Com_Printf(str);
00780 #ifndef VM_OR_FINAL_BUILD
00781         if (fh)
00782         {
00783                 trap_FS_Write(str, strlen(str), fh);
00784                 trap_FS_FCloseFile(fh);
00785         }
00786 #endif
00787 }
00788 
00789 /*
00790 =================
00791 G_Spawn
00792 
00793 Either finds a free entity, or allocates a new one.
00794 
00795   The slots from 0 to MAX_CLIENTS-1 are always reserved for clients, and will
00796 never be used by anything else.
00797 
00798 Try to avoid reusing an entity that was recently freed, because it
00799 can cause the client to think the entity morphed into something else
00800 instead of being removed and recreated, which can cause interpolated
00801 angles and bad trails.
00802 =================
00803 */
00804 gentity_t *G_Spawn( void ) {
00805         int                     i, force;
00806         gentity_t       *e;
00807 
00808         e = NULL;       // shut up warning
00809         i = 0;          // shut up warning
00810         for ( force = 0 ; force < 2 ; force++ ) {
00811                 // if we go through all entities and can't find one to free,
00812                 // override the normal minimum times before use
00813                 e = &g_entities[MAX_CLIENTS];
00814                 for ( i = MAX_CLIENTS ; i<level.num_entities ; i++, e++) {
00815                         if ( e->inuse ) {
00816                                 continue;
00817                         }
00818 
00819                         // the first couple seconds of server time can involve a lot of
00820                         // freeing and allocating, so relax the replacement policy
00821                         if ( !force && e->freetime > level.startTime + 2000 && level.time - e->freetime < 1000 )
00822                         {
00823                                 continue;
00824                         }
00825 
00826                         // reuse this slot
00827                         G_InitGentity( e );
00828                         return e;
00829                 }
00830                 if ( i != MAX_GENTITIES ) {
00831                         break;
00832                 }
00833         }
00834         if ( i == ENTITYNUM_MAX_NORMAL ) {
00835                 /*
00836                 for (i = 0; i < MAX_GENTITIES; i++) {
00837                         G_Printf("%4i: %s\n", i, g_entities[i].classname);
00838                 }
00839                 */
00840                 G_SpewEntList();
00841                 G_Error( "G_Spawn: no free entities" );
00842         }
00843         
00844         // open up a new slot
00845         level.num_entities++;
00846 
00847         // let the server system know that there are more entities
00848         trap_LocateGameData( level.gentities, level.num_entities, sizeof( gentity_t ), 
00849                 &level.clients[0].ps, sizeof( level.clients[0] ) );
00850 
00851         G_InitGentity( e );
00852         return e;
00853 }
00854 
00855 /*
00856 =================
00857 G_EntitiesFree
00858 =================
00859 */
00860 qboolean G_EntitiesFree( void ) {
00861         int                     i;
00862         gentity_t       *e;
00863 
00864         e = &g_entities[MAX_CLIENTS];
00865         for ( i = MAX_CLIENTS; i < level.num_entities; i++, e++) {
00866                 if ( e->inuse ) {
00867                         continue;
00868                 }
00869                 // slot available
00870                 return qtrue;
00871         }
00872         return qfalse;
00873 }
00874 
00875 #define MAX_G2_KILL_QUEUE 256
00876 
00877 int gG2KillIndex[MAX_G2_KILL_QUEUE];
00878 int gG2KillNum = 0;
00879 
00880 void G_SendG2KillQueue(void)
00881 {
00882         char g2KillString[1024];
00883         int i = 0;
00884         
00885         if (!gG2KillNum)
00886         {
00887                 return;
00888         }
00889 
00890         Com_sprintf(g2KillString, 1024, "kg2");
00891 
00892         while (i < gG2KillNum && i < 64)
00893         { //send 64 at once, max...
00894                 Q_strcat(g2KillString, 1024, va(" %i", gG2KillIndex[i]));
00895                 i++;
00896         }
00897 
00898         trap_SendServerCommand(-1, g2KillString);
00899 
00900         //Clear the count because we just sent off the whole queue
00901         gG2KillNum -= i;
00902         if (gG2KillNum < 0)
00903         { //hmm, should be impossible, but I'm paranoid as we're far past beta.
00904                 assert(0);
00905                 gG2KillNum = 0;
00906         }
00907 }
00908 
00909 void G_KillG2Queue(int entNum)
00910 {
00911         if (gG2KillNum >= MAX_G2_KILL_QUEUE)
00912         { //This would be considered a Bad Thing.
00913 #ifdef _DEBUG
00914                 Com_Printf("WARNING: Exceeded the MAX_G2_KILL_QUEUE count for this frame!\n");
00915 #endif
00916                 //Since we're out of queue slots, just send it now as a seperate command (eats more bandwidth, but we have no choice)
00917                 trap_SendServerCommand(-1, va("kg2 %i", entNum));
00918                 return;
00919         }
00920 
00921         gG2KillIndex[gG2KillNum] = entNum;
00922         gG2KillNum++;
00923 }
00924 
00925 /*
00926 =================
00927 G_FreeEntity
00928 
00929 Marks the entity as free
00930 =================
00931 */
00932 void G_FreeEntity( gentity_t *ed ) {
00933         //gentity_t *te;
00934 
00935         if (ed->isSaberEntity)
00936         {
00937 #ifdef _DEBUG
00938                 Com_Printf("Tried to remove JM saber!\n");
00939 #endif
00940                 return;
00941         }
00942 
00943         trap_UnlinkEntity (ed);         // unlink from world
00944 
00945         trap_ICARUS_FreeEnt( ed );      //ICARUS information must be added after this point
00946 
00947         if ( ed->neverFree ) {
00948                 return;
00949         }
00950 
00951         //rww - this may seem a bit hackish, but unfortunately we have no access
00952         //to anything ghoul2-related on the server and thus must send a message
00953         //to let the client know he needs to clean up all the g2 stuff for this
00954         //now-removed entity
00955         if (ed->s.modelGhoul2)
00956         { //force all clients to accept an event to destroy this instance, right now
00957                 /*
00958                 te = G_TempEntity( vec3_origin, EV_DESTROY_GHOUL2_INSTANCE );
00959                 te->r.svFlags |= SVF_BROADCAST;
00960                 te->s.eventParm = ed->s.number;
00961                 */
00962                 //Or not. Events can be dropped, so that would be a bad thing.
00963                 G_KillG2Queue(ed->s.number);
00964         }
00965 
00966         //And, free the server instance too, if there is one.
00967         if (ed->ghoul2)
00968         {
00969                 trap_G2API_CleanGhoul2Models(&(ed->ghoul2));
00970         }
00971 
00972         if (ed->s.eType == ET_NPC && ed->m_pVehicle)
00973         { //tell the "vehicle pool" that this one is now free
00974                 G_FreeVehicleObject(ed->m_pVehicle);
00975         }
00976 
00977         if (ed->s.eType == ET_NPC && ed->client)
00978         { //this "client" structure is one of our dynamically allocated ones, so free the memory
00979                 int saberEntNum = -1;
00980                 int i = 0;
00981                 if (ed->client->ps.saberEntityNum)
00982                 {
00983                         saberEntNum = ed->client->ps.saberEntityNum;
00984                 }
00985                 else if (ed->client->saberStoredIndex)
00986                 {
00987                         saberEntNum = ed->client->saberStoredIndex;
00988                 }
00989 
00990                 if (saberEntNum > 0 && g_entities[saberEntNum].inuse)
00991                 {
00992                         g_entities[saberEntNum].neverFree = qfalse;
00993                         G_FreeEntity(&g_entities[saberEntNum]);
00994                 }
00995 
00996                 while (i < MAX_SABERS)
00997                 {
00998                         if (ed->client->weaponGhoul2[i] && trap_G2_HaveWeGhoul2Models(ed->client->weaponGhoul2[i]))
00999                         {
01000                                 trap_G2API_CleanGhoul2Models(&ed->client->weaponGhoul2[i]);
01001                         }
01002                         i++;
01003                 }
01004 
01005                 G_FreeFakeClient(&ed->client);
01006         }
01007 
01008         if (ed->s.eFlags & EF_SOUNDTRACKER)
01009         {
01010                 int i = 0;
01011                 gentity_t *ent;
01012 
01013                 while (i < MAX_CLIENTS)
01014                 {
01015                         ent = &g_entities[i];
01016 
01017                         if (ent && ent->inuse && ent->client)
01018                         {
01019                                 int ch = TRACK_CHANNEL_NONE-50;
01020 
01021                                 while (ch < NUM_TRACK_CHANNELS-50)
01022                                 {
01023                                         if (ent->client->ps.fd.killSoundEntIndex[ch] == ed->s.number)
01024                                         {
01025                                                 ent->client->ps.fd.killSoundEntIndex[ch] = 0;
01026                                         }
01027 
01028                                         ch++;
01029                                 }
01030                         }
01031 
01032                         i++;
01033                 }
01034 
01035                 //make sure clientside loop sounds are killed on the tracker and client
01036                 trap_SendServerCommand(-1, va("kls %i %i", ed->s.trickedentindex, ed->s.number));
01037         }
01038 
01039         memset (ed, 0, sizeof(*ed));
01040         ed->classname = "freed";
01041         ed->freetime = level.time;
01042         ed->inuse = qfalse;
01043 }
01044 
01045 /*
01046 =================
01047 G_TempEntity
01048 
01049 Spawns an event entity that will be auto-removed
01050 The origin will be snapped to save net bandwidth, so care
01051 must be taken if the origin is right on a surface (snap towards start vector first)
01052 =================
01053 */
01054 gentity_t *G_TempEntity( vec3_t origin, int event ) {
01055         gentity_t               *e;
01056         vec3_t          snapped;
01057 
01058         e = G_Spawn();
01059         e->s.eType = ET_EVENTS + event;
01060 
01061         e->classname = "tempEntity";
01062         e->eventTime = level.time;
01063         e->freeAfterEvent = qtrue;
01064 
01065         VectorCopy( origin, snapped );
01066         SnapVector( snapped );          // save network bandwidth
01067         G_SetOrigin( e, snapped );
01068         //WTF?  Why aren't we setting the s.origin? (like below)
01069         //cg_events.c code checks origin all over the place!!!
01070         //Trying to save bandwidth...?
01071         //VectorCopy( snapped, e->s.origin );
01072 
01073         // find cluster for PVS
01074         trap_LinkEntity( e );
01075 
01076         return e;
01077 }
01078 
01079 
01080 /*
01081 =================
01082 G_SoundTempEntity
01083 
01084 Special event entity that keeps sound trackers in mind
01085 =================
01086 */
01087 gentity_t *G_SoundTempEntity( vec3_t origin, int event, int channel ) {
01088         gentity_t               *e;
01089         vec3_t          snapped;
01090 
01091         e = G_Spawn();
01092 
01093         e->s.eType = ET_EVENTS + event;
01094         e->inuse = qtrue;
01095 
01096         e->classname = "tempEntity";
01097         e->eventTime = level.time;
01098         e->freeAfterEvent = qtrue;
01099 
01100         VectorCopy( origin, snapped );
01101         SnapVector( snapped );          // save network bandwidth
01102         G_SetOrigin( e, snapped );
01103 
01104         // find cluster for PVS
01105         trap_LinkEntity( e );
01106 
01107         return e;
01108 }
01109 
01110 
01111 //scale health down below 1024 to fit in health bits
01112 void G_ScaleNetHealth(gentity_t *self)
01113 {
01114         int maxHealth = self->maxHealth;
01115 
01116     if (maxHealth < 1000)
01117         { //it's good then
01118                 self->s.maxhealth = maxHealth;
01119                 self->s.health = self->health;
01120 
01121                 if (self->s.health < 0)
01122                 { //don't let it wrap around
01123                         self->s.health = 0;
01124                 }
01125                 return;
01126         }
01127 
01128         //otherwise, scale it down
01129         self->s.maxhealth = (maxHealth/100);
01130         self->s.health = (self->health/100);
01131 
01132         if (self->s.health < 0)
01133         { //don't let it wrap around
01134                 self->s.health = 0;
01135         }
01136 
01137         if (self->health > 0 &&
01138                 self->s.health <= 0)
01139         { //don't let it scale to 0 if the thing is still not "dead"
01140                 self->s.health = 1;
01141         }
01142 }
01143 
01144 
01145 
01146 /*
01147 ==============================================================================
01148 
01149 Kill box
01150 
01151 ==============================================================================
01152 */
01153 
01154 /*
01155 =================
01156 G_KillBox
01157 
01158 Kills all entities that would touch the proposed new positioning
01159 of ent.  Ent should be unlinked before calling this!
01160 =================
01161 */
01162 void G_KillBox (gentity_t *ent) {
01163         int                     i, num;
01164         int                     touch[MAX_GENTITIES];
01165         gentity_t       *hit;
01166         vec3_t          mins, maxs;
01167 
01168         VectorAdd( ent->client->ps.origin, ent->r.mins, mins );
01169         VectorAdd( ent->client->ps.origin, ent->r.maxs, maxs );
01170         num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES );
01171 
01172         for (i=0 ; i<num ; i++) {
01173                 hit = &g_entities[touch[i]];
01174                 if ( !hit->client ) {
01175                         continue;
01176                 }
01177 
01178                 if (hit->s.number == ent->s.number)
01179                 { //don't telefrag yourself!
01180                         continue;
01181                 }
01182 
01183                 if (ent->r.ownerNum == hit->s.number)
01184                 { //don't telefrag your vehicle!
01185                         continue;
01186                 }
01187 
01188                 // nail it
01189                 G_Damage ( hit, ent, ent, NULL, NULL,
01190                         100000, DAMAGE_NO_PROTECTION, MOD_TELEFRAG);
01191         }
01192 
01193 }
01194 
01195 //==============================================================================
01196 
01197 /*
01198 ===============
01199 G_AddPredictableEvent
01200 
01201 Use for non-pmove events that would also be predicted on the
01202 client side: jumppads and item pickups
01203 Adds an event+parm and twiddles the event counter
01204 ===============
01205 */
01206 void G_AddPredictableEvent( gentity_t *ent, int event, int eventParm ) {
01207         if ( !ent->client ) {
01208                 return;
01209         }
01210         BG_AddPredictableEventToPlayerstate( event, eventParm, &ent->client->ps );
01211 }
01212 
01213 
01214 /*
01215 ===============
01216 G_AddEvent
01217 
01218 Adds an event+parm and twiddles the event counter
01219 ===============
01220 */
01221 void G_AddEvent( gentity_t *ent, int event, int eventParm ) {
01222         int             bits;
01223 
01224         if ( !event ) {
01225                 G_Printf( "G_AddEvent: zero event added for entity %i\n", ent->s.number );
01226                 return;
01227         }
01228 
01229         // clients need to add the event in playerState_t instead of entityState_t
01230         if ( ent->client ) {
01231                 bits = ent->client->ps.externalEvent & EV_EVENT_BITS;
01232                 bits = ( bits + EV_EVENT_BIT1 ) & EV_EVENT_BITS;
01233                 ent->client->ps.externalEvent = event | bits;
01234                 ent->client->ps.externalEventParm = eventParm;
01235                 ent->client->ps.externalEventTime = level.time;
01236         } else {
01237                 bits = ent->s.event & EV_EVENT_BITS;
01238                 bits = ( bits + EV_EVENT_BIT1 ) & EV_EVENT_BITS;
01239                 ent->s.event = event | bits;
01240                 ent->s.eventParm = eventParm;
01241         }
01242         ent->eventTime = level.time;
01243 }
01244 
01245 /*
01246 =============
01247 G_PlayEffect
01248 =============
01249 */
01250 gentity_t *G_PlayEffect(int fxID, vec3_t org, vec3_t ang)
01251 {
01252         gentity_t       *te;
01253 
01254         te = G_TempEntity( org, EV_PLAY_EFFECT );
01255         VectorCopy(ang, te->s.angles);
01256         VectorCopy(org, te->s.origin);
01257         te->s.eventParm = fxID;
01258 
01259         return te;
01260 }
01261 
01262 /*
01263 =============
01264 G_PlayEffectID
01265 =============
01266 */
01267 gentity_t *G_PlayEffectID(const int fxID, vec3_t org, vec3_t ang)
01268 { //play an effect by the G_EffectIndex'd ID instead of a predefined effect ID
01269         gentity_t       *te;
01270 
01271         te = G_TempEntity( org, EV_PLAY_EFFECT_ID );
01272         VectorCopy(ang, te->s.angles);
01273         VectorCopy(org, te->s.origin);
01274         te->s.eventParm = fxID;
01275 
01276         if (!te->s.angles[0] &&
01277                 !te->s.angles[1] &&
01278                 !te->s.angles[2])
01279         { //play off this dir by default then.
01280                 te->s.angles[1] = 1;
01281         }
01282 
01283         return te;
01284 }
01285 
01286 /*
01287 =============
01288 G_ScreenShake
01289 =============
01290 */
01291 gentity_t *G_ScreenShake(vec3_t org, gentity_t *target, float intensity, int duration, qboolean global)
01292 {
01293         gentity_t       *te;
01294 
01295         te = G_TempEntity( org, EV_SCREENSHAKE );
01296         VectorCopy(org, te->s.origin);
01297         te->s.angles[0] = intensity;
01298         te->s.time = duration;
01299 
01300         if (target)
01301         {
01302                 te->s.modelindex = target->s.number+1;
01303         }
01304         else
01305         {
01306                 te->s.modelindex = 0;
01307         }
01308 
01309         if (global)
01310         {
01311                 te->r.svFlags |= SVF_BROADCAST;
01312         }
01313 
01314         return te;
01315 }
01316 
01317 /*
01318 =============
01319 G_MuteSound
01320 =============
01321 */
01322 void G_MuteSound( int entnum, int channel )
01323 {
01324         gentity_t       *te, *e;
01325 
01326         te = G_TempEntity( vec3_origin, EV_MUTE_SOUND );
01327         te->r.svFlags = SVF_BROADCAST;
01328         te->s.trickedentindex2 = entnum;
01329         te->s.trickedentindex = channel;
01330 
01331         e = &g_entities[entnum];
01332 
01333         if (e && (e->s.eFlags & EF_SOUNDTRACKER))
01334         {
01335                 G_FreeEntity(e);
01336                 e->s.eFlags = 0;
01337         }
01338 }
01339 
01340 /*
01341 =============
01342 G_Sound
01343 =============
01344 */
01345 void G_Sound( gentity_t *ent, int channel, int soundIndex ) {
01346         gentity_t       *te;
01347 
01348         assert(soundIndex);
01349 
01350         te = G_SoundTempEntity( ent->r.currentOrigin, EV_GENERAL_SOUND, channel );
01351         te->s.eventParm = soundIndex;
01352         te->s.saberEntityNum = channel;
01353 
01354         if (ent && ent->client && channel > TRACK_CHANNEL_NONE)
01355         { //let the client remember the index of the player entity so he can kill the most recent sound on request
01356                 if (g_entities[ent->client->ps.fd.killSoundEntIndex[channel-50]].inuse &&
01357                         ent->client->ps.fd.killSoundEntIndex[channel-50] > MAX_CLIENTS)
01358                 {
01359                         G_MuteSound(ent->client->ps.fd.killSoundEntIndex[channel-50], CHAN_VOICE);
01360                         if (ent->client->ps.fd.killSoundEntIndex[channel-50] > MAX_CLIENTS && g_entities[ent->client->ps.fd.killSoundEntIndex[channel-50]].inuse)
01361                         {
01362                                 G_FreeEntity(&g_entities[ent->client->ps.fd.killSoundEntIndex[channel-50]]);
01363                         }
01364                         ent->client->ps.fd.killSoundEntIndex[channel-50] = 0;
01365                 }
01366 
01367                 ent->client->ps.fd.killSoundEntIndex[channel-50] = te->s.number;
01368                 te->s.trickedentindex = ent->s.number;
01369                 te->s.eFlags = EF_SOUNDTRACKER;
01370                 //te->freeAfterEvent = qfalse;
01371         }
01372 }
01373 
01374 /*
01375 =============
01376 G_SoundAtLoc
01377 =============
01378 */
01379 void G_SoundAtLoc( vec3_t loc, int channel, int soundIndex ) {
01380         gentity_t       *te;
01381 
01382         te = G_TempEntity( loc, EV_GENERAL_SOUND );
01383         te->s.eventParm = soundIndex;
01384         te->s.saberEntityNum = channel;
01385 }
01386 
01387 /*
01388 =============
01389 G_EntitySound
01390 =============
01391 */
01392 void G_EntitySound( gentity_t *ent, int channel, int soundIndex ) {
01393         gentity_t       *te;
01394 
01395         te = G_TempEntity( ent->r.currentOrigin, EV_ENTITY_SOUND );
01396         te->s.eventParm = soundIndex;
01397         te->s.clientNum = ent->s.number;
01398         te->s.trickedentindex = channel;
01399 }
01400 
01401 //To make porting from SP easier.
01402 void G_SoundOnEnt( gentity_t *ent, int channel, const char *soundPath )
01403 {
01404         gentity_t       *te;
01405 
01406         te = G_TempEntity( ent->r.currentOrigin, EV_ENTITY_SOUND );
01407         te->s.eventParm = G_SoundIndex((char *)soundPath);
01408         te->s.clientNum = ent->s.number;
01409         te->s.trickedentindex = channel;
01410 
01411 }
01412 
01413 #ifdef _XBOX
01414 //-----------------------------
01415 void G_EntityPosition( int i, vec3_t ret )
01416 {
01417         if ( /*g_entities &&*/ i >= 0 && i < MAX_GENTITIES && g_entities[i].inuse)
01418         {
01419 #if 0   // VVFIXME - Do we really care about doing this? It's slow and unnecessary
01420                 gentity_t *ent = g_entities + i;
01421 
01422                 if (ent->bmodel)
01423                 {
01424                         vec3_t mins, maxs;
01425                         clipHandle_t h = CM_InlineModel( ent->s.modelindex );
01426                         CM_ModelBounds( cmg, h, mins, maxs );
01427                         ret[0] = (mins[0] + maxs[0]) / 2 + ent->currentOrigin[0];
01428                         ret[1] = (mins[1] + maxs[1]) / 2 + ent->currentOrigin[1];
01429                         ret[2] = (mins[2] + maxs[2]) / 2 + ent->currentOrigin[2];
01430                 }
01431                 else
01432 #endif
01433                 {
01434                         VectorCopy(g_entities[i].r.currentOrigin, ret);
01435                 }
01436         }
01437         else
01438         {
01439                 ret[0] = ret[1] = ret[2] = 0;
01440         }
01441 }
01442 #endif
01443 
01444 //==============================================================================
01445 
01446 /*
01447 ==============
01448 ValidUseTarget
01449 
01450 Returns whether or not the targeted entity is useable
01451 ==============
01452 */
01453 qboolean ValidUseTarget( gentity_t *ent )
01454 {
01455         if ( !ent->use )
01456         {
01457                 return qfalse;
01458         }
01459 
01460         if ( ent->flags & FL_INACTIVE )
01461         {//set by target_deactivate
01462                 return qfalse;
01463         }
01464 
01465         if ( !(ent->r.svFlags & SVF_PLAYER_USABLE) )
01466         {//Check for flag that denotes BUTTON_USE useability
01467                 return qfalse;
01468         }
01469 
01470         return qtrue;
01471 }
01472 
01473 //use an ammo/health dispenser on another client
01474 void G_UseDispenserOn(gentity_t *ent, int dispType, gentity_t *target)
01475 {
01476         if (dispType == HI_HEALTHDISP)
01477         {
01478                 target->client->ps.stats[STAT_HEALTH] += 4;
01479 
01480                 if (target->client->ps.stats[STAT_HEALTH] > target->client->ps.stats[STAT_MAX_HEALTH])
01481                 {
01482                         target->client->ps.stats[STAT_HEALTH] = target->client->ps.stats[STAT_MAX_HEALTH];
01483                 }
01484 
01485                 target->client->isMedHealed = level.time + 500;
01486                 target->health = target->client->ps.stats[STAT_HEALTH];
01487         }
01488         else if (dispType == HI_AMMODISP)
01489         {
01490                 if (ent->client->medSupplyDebounce < level.time)
01491                 { //do the next increment
01492                         //increment based on the amount of ammo used per normal shot.
01493                         target->client->ps.ammo[weaponData[target->client->ps.weapon].ammoIndex] += weaponData[target->client->ps.weapon].energyPerShot;
01494 
01495                         if (target->client->ps.ammo[weaponData[target->client->ps.weapon].ammoIndex] > ammoData[weaponData[target->client->ps.weapon].ammoIndex].max)
01496                         { //cap it off
01497                                 target->client->ps.ammo[weaponData[target->client->ps.weapon].ammoIndex] = ammoData[weaponData[target->client->ps.weapon].ammoIndex].max;
01498                         }
01499 
01500                         //base the next supply time on how long the weapon takes to fire. Seems fair enough.
01501                         ent->client->medSupplyDebounce = level.time + weaponData[target->client->ps.weapon].fireTime;
01502                 }
01503                 target->client->isMedSupplied = level.time + 500;
01504         }
01505 }
01506 
01507 //see if this guy needs servicing from a specific type of dispenser
01508 int G_CanUseDispOn(gentity_t *ent, int dispType)
01509 {
01510         if (!ent->client || !ent->inuse || ent->health < 1 ||
01511                 ent->client->ps.stats[STAT_HEALTH] < 1)
01512         { //dead or invalid
01513                 return 0;
01514         }
01515 
01516         if (dispType == HI_HEALTHDISP)
01517         {
01518         if (ent->client->ps.stats[STAT_HEALTH] < ent->client->ps.stats[STAT_MAX_HEALTH])
01519                 { //he's hurt
01520                         return 1;
01521                 }
01522 
01523                 //otherwise no
01524                 return 0;
01525         }
01526         else if (dispType == HI_AMMODISP)
01527         {
01528                 if (ent->client->ps.weapon <= WP_NONE || ent->client->ps.weapon > LAST_USEABLE_WEAPON)
01529                 { //not a player-useable weapon
01530                         return 0;
01531                 }
01532 
01533                 if (ent->client->ps.ammo[weaponData[ent->client->ps.weapon].ammoIndex] < ammoData[weaponData[ent->client->ps.weapon].ammoIndex].max)
01534                 { //needs more ammo for current weapon
01535                         return 1;
01536                 }
01537 
01538                 //needs none
01539                 return 0;
01540         }
01541 
01542         //invalid type?
01543         return 0;
01544 }
01545 
01546 qboolean TryHeal(gentity_t *ent, gentity_t *target)
01547 {
01548         if (g_gametype.integer == GT_SIEGE && ent->client->siegeClass != -1 &&
01549                 target && target->inuse && target->maxHealth && target->healingclass &&
01550                 target->healingclass[0] && target->health > 0 && target->health < target->maxHealth)
01551         { //it's not dead yet...
01552                 siegeClass_t *scl = &bgSiegeClasses[ent->client->siegeClass];
01553 
01554                 if (!Q_stricmp(scl->name, target->healingclass))
01555                 { //this thing can be healed by the class this player is using
01556                         if (target->healingDebounce < level.time)
01557                         { //do the actual heal
01558                                 target->health += 10;
01559                                 if (target->health > target->maxHealth)
01560                                 { //don't go too high
01561                                         target->health = target->maxHealth;
01562                                 }
01563                                 target->healingDebounce = level.time + target->healingrate;
01564                                 if (target->healingsound && target->healingsound[0])
01565                                 { //play it
01566                                         if (target->s.solid == SOLID_BMODEL)
01567                                         { //ok, well, just play it on the client then.
01568                                                 G_Sound(ent, CHAN_AUTO, G_SoundIndex(target->healingsound));
01569                                         }
01570                                         else
01571                                         {
01572                                                 G_Sound(target, CHAN_AUTO, G_SoundIndex(target->healingsound));
01573                                         }
01574                                 }
01575 
01576                                 //update net health for bar
01577                                 G_ScaleNetHealth(target);
01578                                 if (target->target_ent &&
01579                                         target->target_ent->maxHealth)
01580                                 {
01581                                         target->target_ent->health = target->health;
01582                                         G_ScaleNetHealth(target->target_ent);
01583                                 }
01584                         }
01585 
01586                         //keep them in the healing anim even when the healing debounce is not yet expired
01587                         if (ent->client->ps.torsoAnim == BOTH_BUTTON_HOLD ||
01588                                 ent->client->ps.torsoAnim == BOTH_CONSOLE1)
01589                         { //extend the time
01590                                 ent->client->ps.torsoTimer = 500;
01591                         }
01592                         else
01593                         {
01594                                 G_SetAnim( ent, NULL, SETANIM_TORSO, BOTH_BUTTON_HOLD, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0 );
01595                         }
01596 
01597                         return qtrue;
01598                 }
01599         }
01600 
01601         return qfalse;
01602 }
01603 
01604 /*
01605 ==============
01606 TryUse
01607 
01608 Try and use an entity in the world, directly ahead of us
01609 ==============
01610 */
01611 
01612 #define USE_DISTANCE    64.0f
01613 
01614 extern void Touch_Button(gentity_t *ent, gentity_t *other, trace_t *trace );
01615 extern qboolean gSiegeRoundBegun;
01616 static vec3_t   playerMins = {-15, -15, DEFAULT_MINS_2};
01617 static vec3_t   playerMaxs = {15, 15, DEFAULT_MAXS_2};
01618 void TryUse( gentity_t *ent )
01619 {
01620         gentity_t       *target;
01621         trace_t         trace;
01622         vec3_t          src, dest, vf;
01623         vec3_t          viewspot;
01624 
01625         if (g_gametype.integer == GT_SIEGE &&
01626                 !gSiegeRoundBegun)
01627         { //nothing can be used til the round starts.
01628                 return;
01629         }
01630 
01631         if (!ent || !ent->client || (ent->client->ps.weaponTime > 0 && ent->client->ps.torsoAnim != BOTH_BUTTON_HOLD && ent->client->ps.torsoAnim != BOTH_CONSOLE1) || ent->health < 1 ||
01632                 (ent->client->ps.pm_flags & PMF_FOLLOW) || ent->client->sess.sessionTeam == TEAM_SPECTATOR ||
01633                 (ent->client->ps.forceHandExtend != HANDEXTEND_NONE && ent->client->ps.forceHandExtend != HANDEXTEND_DRAGGING))
01634         {
01635                 return;
01636         }
01637 
01638         if (ent->client->ps.emplacedIndex)
01639         { //on an emplaced gun or using a vehicle, don't do anything when hitting use key
01640                 return;
01641         }
01642 
01643         if (ent->s.number < MAX_CLIENTS && ent->client && ent->client->ps.m_iVehicleNum)
01644         {
01645                 gentity_t *currentVeh = &g_entities[ent->client->ps.m_iVehicleNum];
01646                 if (currentVeh->inuse && currentVeh->m_pVehicle)
01647                 {
01648                         Vehicle_t *pVeh = currentVeh->m_pVehicle;
01649                         if (!pVeh->m_iBoarding)
01650                         {
01651                                 pVeh->m_pVehicleInfo->Eject( pVeh, (bgEntity_t *)ent, qfalse );
01652                         }
01653                         return;
01654                 }
01655         }
01656 
01657         if (ent->client->jetPackOn)
01658         { //can't use anything else to jp is off
01659                 goto tryJetPack;
01660         }
01661 
01662         if (ent->client->bodyGrabIndex != ENTITYNUM_NONE)
01663         { //then hitting the use key just means let go
01664                 if (ent->client->bodyGrabTime < level.time)
01665                 {
01666                         gentity_t *grabbed = &g_entities[ent->client->bodyGrabIndex];
01667 
01668                         if (grabbed->inuse)
01669                         {
01670                                 if (grabbed->client)
01671                                 {
01672                                         grabbed->client->ps.ragAttach = 0;
01673                                 }
01674                                 else
01675                                 {
01676                                         grabbed->s.ragAttach = 0;
01677                                 }
01678                         }
01679                         ent->client->bodyGrabIndex = ENTITYNUM_NONE;
01680                         ent->client->bodyGrabTime = level.time + 1000;
01681                 }
01682                 return;
01683         }
01684 
01685         VectorCopy(ent->client->ps.origin, viewspot);
01686         viewspot[2] += ent->client->ps.viewheight;
01687 
01688         VectorCopy( viewspot, src );
01689         AngleVectors( ent->client->ps.viewangles, vf, NULL, NULL );
01690 
01691         VectorMA( src, USE_DISTANCE, vf, dest );
01692 
01693         //Trace ahead to find a valid target
01694         trap_Trace( &trace, src, vec3_origin, vec3_origin, dest, ent->s.number, MASK_OPAQUE|CONTENTS_SOLID|CONTENTS_BODY|CONTENTS_ITEM|CONTENTS_CORPSE );
01695         
01696         if ( trace.fraction == 1.0f || trace.entityNum < 1 )
01697         {
01698                 goto tryJetPack;
01699         }
01700 
01701         target = &g_entities[trace.entityNum];
01702 
01703 //Enable for corpse dragging
01704 #if 0
01705         if (target->inuse && target->s.eType == ET_BODY &&
01706                 ent->client->bodyGrabTime < level.time)
01707         { //then grab the body
01708                 target->s.eFlags |= EF_RAG; //make sure it's in rag state
01709                 if (!ent->s.number)
01710                 { //switch cl 0 and entitynum_none, so we can operate on the "if non-0" concept
01711                         target->s.ragAttach = ENTITYNUM_NONE;
01712                 }
01713                 else
01714                 {
01715                         target->s.ragAttach = ent->s.number;
01716                 }
01717                 ent->client->bodyGrabTime = level.time + 1000;
01718                 ent->client->bodyGrabIndex = target->s.number;
01719                 return;
01720         }
01721 #endif
01722 
01723         if (target && target->m_pVehicle && target->client &&
01724                 target->s.NPC_class == CLASS_VEHICLE &&
01725                 !ent->client->ps.zoomMode)
01726         { //if target is a vehicle then perform appropriate checks
01727                 Vehicle_t *pVeh = target->m_pVehicle;
01728 
01729                 if (pVeh->m_pVehicleInfo)
01730                 {
01731                         if ( ent->r.ownerNum == target->s.number )
01732                         { //user is already on this vehicle so eject him
01733                                 pVeh->m_pVehicleInfo->Eject( pVeh, (bgEntity_t *)ent, qfalse );
01734                         }
01735                         else
01736                         { // Otherwise board this vehicle.
01737                                 if (g_gametype.integer < GT_TEAM ||
01738                                         !target->alliedTeam ||
01739                                         (target->alliedTeam == ent->client->sess.sessionTeam))
01740                                 { //not belonging to a team, or client is on same team
01741                                         pVeh->m_pVehicleInfo->Board( pVeh, (bgEntity_t *)ent );
01742                                 }
01743                         }
01744                         //clear the damn button!
01745                         ent->client->pers.cmd.buttons &= ~BUTTON_USE;
01746                         return;
01747                 }
01748         }
01749 
01750 #if 0 //ye olde method
01751         if (ent->client->ps.stats[STAT_HOLDABLE_ITEM] > 0 &&
01752                 bg_itemlist[ent->client->ps.stats[STAT_HOLDABLE_ITEM]].giType == IT_HOLDABLE)
01753         {
01754                 if (bg_itemlist[ent->client->ps.stats[STAT_HOLDABLE_ITEM]].giTag == HI_HEALTHDISP ||
01755                         bg_itemlist[ent->client->ps.stats[STAT_HOLDABLE_ITEM]].giTag == HI_AMMODISP)
01756                 { //has a dispenser item selected
01757             if (target && target->client && target->health > 0 && OnSameTeam(ent, target) &&
01758                                 G_CanUseDispOn(target, bg_itemlist[ent->client->ps.stats[STAT_HOLDABLE_ITEM]].giTag))
01759                         { //a live target that's on my team, we can use him
01760                                 G_UseDispenserOn(ent, bg_itemlist[ent->client->ps.stats[STAT_HOLDABLE_ITEM]].giTag, target);
01761 
01762                                 //for now, we will use the standard use anim
01763                                 if (ent->client->ps.torsoAnim == BOTH_BUTTON_HOLD)
01764                                 { //extend the time
01765                                         ent->client->ps.torsoTimer = 500;
01766                                 }
01767                                 else
01768                                 {
01769                                         G_SetAnim( ent, NULL, SETANIM_TORSO, BOTH_BUTTON_HOLD, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0 );
01770                                 }
01771                                 ent->client->ps.weaponTime = ent->client->ps.torsoTimer;
01772                                 return;
01773                         }
01774                 }
01775         }
01776 #else
01777     if ( ((ent->client->ps.stats[STAT_HOLDABLE_ITEMS] & (1 << HI_HEALTHDISP)) || (ent->client->ps.stats[STAT_HOLDABLE_ITEMS] & (1 << HI_AMMODISP))) &&
01778                 target && target->inuse && target->client && target->health > 0 && OnSameTeam(ent, target) &&
01779                 (G_CanUseDispOn(target, HI_HEALTHDISP) || G_CanUseDispOn(target, HI_AMMODISP)) )
01780         { //a live target that's on my team, we can use him
01781                 if (G_CanUseDispOn(target, HI_HEALTHDISP))
01782                 {
01783                         G_UseDispenserOn(ent, HI_HEALTHDISP, target);
01784                 }
01785                 if (G_CanUseDispOn(target, HI_AMMODISP))
01786                 {
01787                         G_UseDispenserOn(ent, HI_AMMODISP, target);
01788                 }
01789 
01790                 //for now, we will use the standard use anim
01791                 if (ent->client->ps.torsoAnim == BOTH_BUTTON_HOLD)
01792                 { //extend the time
01793                         ent->client->ps.torsoTimer = 500;
01794                 }
01795                 else
01796                 {
01797                         G_SetAnim( ent, NULL, SETANIM_TORSO, BOTH_BUTTON_HOLD, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0 );
01798                 }
01799                 ent->client->ps.weaponTime = ent->client->ps.torsoTimer;
01800                 return;
01801         }
01802 
01803 #endif
01804 
01805         //Check for a use command
01806         if ( ValidUseTarget( target ) 
01807                 && (g_gametype.integer != GT_SIEGE 
01808                         || !target->alliedTeam 
01809                         || target->alliedTeam != ent->client->sess.sessionTeam 
01810                         || g_ff_objectives.integer) )
01811         {
01812                 if (ent->client->ps.torsoAnim == BOTH_BUTTON_HOLD ||
01813                         ent->client->ps.torsoAnim == BOTH_CONSOLE1)
01814                 { //extend the time
01815                         ent->client->ps.torsoTimer = 500;
01816                 }
01817                 else
01818                 {
01819                         G_SetAnim( ent, NULL, SETANIM_TORSO, BOTH_BUTTON_HOLD, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0 );
01820                 }
01821                 ent->client->ps.weaponTime = ent->client->ps.torsoTimer;
01822                 /*
01823                 NPC_SetAnim( ent, SETANIM_TORSO, BOTH_FORCEPUSH, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
01824                 if ( !VectorLengthSquared( ent->client->ps.velocity ) )
01825                 {
01826                         NPC_SetAnim( ent, SETANIM_LEGS, BOTH_FORCEPUSH, SETANIM_FLAG_NORMAL|SETANIM_FLAG_HOLD );
01827                 }
01828                 */
01829                 if ( target->touch == Touch_Button )
01830                 {//pretend we touched it
01831                         target->touch(target, ent, NULL);
01832                 }
01833                 else
01834                 {
01835                         GlobalUse(target, ent, ent);
01836                 }
01837                 return;
01838         }
01839 
01840         if (TryHeal(ent, target))
01841         {
01842                 return;
01843         }
01844 
01845 tryJetPack:
01846         //if we got here, we didn't actually use anything else, so try to toggle jetpack if we are in the air, or if it is already on
01847         if (ent->client->ps.stats[STAT_HOLDABLE_ITEMS] & (1 << HI_JETPACK))
01848         {
01849                 if (ent->client->jetPackOn || ent->client->ps.groundEntityNum == ENTITYNUM_NONE)
01850                 {
01851                         ItemUse_Jetpack(ent);
01852                         return;
01853                 }
01854         }
01855 
01856         if ( (ent->client->ps.stats[STAT_HOLDABLE_ITEMS] & (1 << HI_AMMODISP)) /*&&
01857                 G_ItemUsable(&ent->client->ps, HI_AMMODISP)*/ )
01858         { //if you used nothing, then try spewing out some ammo
01859                 trace_t trToss;
01860                 vec3_t fAng;
01861                 vec3_t fwd;
01862 
01863                 VectorSet(fAng, 0.0f, ent->client->ps.viewangles[YAW], 0.0f);
01864                 AngleVectors(fAng, fwd, 0, 0);
01865 
01866         VectorMA(ent->client->ps.origin, 64.0f, fwd, fwd);              
01867                 trap_Trace(&trToss, ent->client->ps.origin, playerMins, playerMaxs, fwd, ent->s.number, ent->clipmask);
01868                 if (trToss.fraction == 1.0f && !trToss.allsolid && !trToss.startsolid)
01869                 {
01870                         ItemUse_UseDisp(ent, HI_AMMODISP);
01871                         G_AddEvent(ent, EV_USE_ITEM0+HI_AMMODISP, 0);
01872                         return;
01873                 }
01874         }
01875 }
01876 
01877 qboolean G_PointInBounds( vec3_t point, vec3_t mins, vec3_t maxs )
01878 {
01879         int i;
01880 
01881         for(i = 0; i < 3; i++ )
01882         {
01883                 if ( point[i] < mins[i] )
01884                 {
01885                         return qfalse;
01886                 }
01887                 if ( point[i] > maxs[i] )
01888                 {
01889                         return qfalse;
01890                 }
01891         }
01892 
01893         return qtrue;
01894 }
01895 
01896 qboolean G_BoxInBounds( vec3_t point, vec3_t mins, vec3_t maxs, vec3_t boundsMins, vec3_t boundsMaxs )
01897 {
01898         vec3_t boxMins;
01899         vec3_t boxMaxs;
01900 
01901         VectorAdd( point, mins, boxMins );
01902         VectorAdd( point, maxs, boxMaxs );
01903 
01904         if(boxMaxs[0]>boundsMaxs[0])
01905                 return qfalse;
01906 
01907         if(boxMaxs[1]>boundsMaxs[1])
01908                 return qfalse;
01909 
01910         if(boxMaxs[2]>boundsMaxs[2])
01911                 return qfalse;
01912 
01913         if(boxMins[0]<boundsMins[0])
01914                 return qfalse;
01915 
01916         if(boxMins[1]<boundsMins[1])
01917                 return qfalse;
01918 
01919         if(boxMins[2]<boundsMins[2])
01920                 return qfalse;
01921 
01922         //box is completely contained within bounds
01923         return qtrue;
01924 }
01925 
01926 
01927 void G_SetAngles( gentity_t *ent, vec3_t angles )
01928 {
01929         VectorCopy( angles, ent->r.currentAngles );
01930         VectorCopy( angles, ent->s.angles );
01931         VectorCopy( angles, ent->s.apos.trBase );
01932 }
01933 
01934 qboolean G_ClearTrace( vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int ignore, int clipmask )
01935 {
01936         static  trace_t tr;
01937 
01938         trap_Trace( &tr, start, mins, maxs, end, ignore, clipmask );
01939 
01940         if ( tr.allsolid || tr.startsolid || tr.fraction < 1.0 )
01941         {
01942                 return qfalse;
01943         }
01944 
01945         return qtrue;
01946 }
01947 
01948 /*
01949 ================
01950 G_SetOrigin
01951 
01952 Sets the pos trajectory for a fixed position
01953 ================
01954 */
01955 void G_SetOrigin( gentity_t *ent, vec3_t origin ) {
01956         VectorCopy( origin, ent->s.pos.trBase );
01957         ent->s.pos.trType = TR_STATIONARY;
01958         ent->s.pos.trTime = 0;
01959         ent->s.pos.trDuration = 0;
01960         VectorClear( ent->s.pos.trDelta );
01961 
01962         VectorCopy( origin, ent->r.currentOrigin );
01963 }
01964 
01965 qboolean G_CheckInSolid (gentity_t *self, qboolean fix)
01966 {
01967         trace_t trace;
01968         vec3_t  end, mins;
01969 
01970         VectorCopy(self->r.currentOrigin, end);
01971         end[2] += self->r.mins[2];
01972         VectorCopy(self->r.mins, mins);
01973         mins[2] = 0;
01974 
01975         trap_Trace(&trace, self->r.currentOrigin, mins, self->r.maxs, end, self->s.number, self->clipmask);
01976         if(trace.allsolid || trace.startsolid)
01977         {
01978                 return qtrue;
01979         }
01980         
01981         if(trace.fraction < 1.0)
01982         {
01983                 if(fix)
01984                 {//Put them at end of trace and check again
01985                         vec3_t  neworg;
01986 
01987                         VectorCopy(trace.endpos, neworg);
01988                         neworg[2] -= self->r.mins[2];
01989                         G_SetOrigin(self, neworg);
01990                         trap_LinkEntity(self);
01991 
01992                         return G_CheckInSolid(self, qfalse);
01993                 }
01994                 else
01995                 {
01996                         return qtrue;
01997                 }
01998         }
01999                 
02000         return qfalse;
02001 }
02002 
02003 /*
02004 ================
02005 DebugLine
02006 
02007   debug polygons only work when running a local game
02008   with r_debugSurface set to 2
02009 ================
02010 */
02011 int DebugLine(vec3_t start, vec3_t end, int color) {
02012         vec3_t points[4], dir, cross, up = {0, 0, 1};
02013         float dot;
02014 
02015         VectorCopy(start, points[0]);
02016         VectorCopy(start, points[1]);
02017         //points[1][2] -= 2;
02018         VectorCopy(end, points[2]);
02019         //points[2][2] -= 2;
02020         VectorCopy(end, points[3]);
02021 
02022 
02023         VectorSubtract(end, start, dir);
02024         VectorNormalize(dir);
02025         dot = DotProduct(dir, up);
02026         if (dot > 0.99 || dot < -0.99) VectorSet(cross, 1, 0, 0);
02027         else CrossProduct(dir, up, cross);
02028 
02029         VectorNormalize(cross);
02030 
02031         VectorMA(points[0], 2, cross, points[0]);
02032         VectorMA(points[1], -2, cross, points[1]);
02033         VectorMA(points[2], -2, cross, points[2]);
02034         VectorMA(points[3], 2, cross, points[3]);
02035 
02036         return trap_DebugPolygonCreate(color, 4, points);
02037 }
02038 
02039 void G_ROFF_NotetrackCallback( gentity_t *cent, const char *notetrack)
02040 {
02041         char type[256];
02042         int i = 0;
02043         int addlArg = 0;
02044 
02045         if (!cent || !notetrack)
02046         {
02047                 return;
02048         }
02049 
02050         while (notetrack[i] && notetrack[i] != ' ')
02051         {
02052                 type[i] = notetrack[i];
02053                 i++;
02054         }
02055 
02056         type[i] = '\0';
02057 
02058         if (!i || !type[0])
02059         {
02060                 return;
02061         }
02062 
02063         if (notetrack[i] == ' ')
02064         {
02065                 addlArg = 1;
02066         }
02067 
02068         if (strcmp(type, "loop") == 0)
02069         {
02070                 if (addlArg) //including an additional argument means reset to original position before loop
02071                 {
02072                         VectorCopy(cent->s.origin2, cent->s.pos.trBase);
02073                         VectorCopy(cent->s.origin2, cent->r.currentOrigin);
02074                         VectorCopy(cent->s.angles2, cent->s.apos.trBase);
02075                         VectorCopy(cent->s.angles2, cent->r.currentAngles);
02076                 }
02077 
02078                 trap_ROFF_Play(cent->s.number, cent->roffid, qfalse);
02079         }
02080 }
02081 
02082 void G_SpeechEvent( gentity_t *self, int event )
02083 {
02084         G_AddEvent(self, event, 0);
02085 }
02086 
02087 qboolean G_ExpandPointToBBox( vec3_t point, const vec3_t mins, const vec3_t maxs, int ignore, int clipmask )
02088 {
02089         trace_t tr;
02090         vec3_t  start, end;
02091         int i;
02092 
02093         VectorCopy( point, start );
02094         
02095         for ( i = 0; i < 3; i++ )
02096         {
02097                 VectorCopy( start, end );
02098                 end[i] += mins[i];
02099                 trap_Trace( &tr, start, vec3_origin, vec3_origin, end, ignore, clipmask );
02100                 if ( tr.allsolid || tr.startsolid )
02101                 {
02102                         return qfalse;
02103                 }
02104                 if ( tr.fraction < 1.0 )
02105                 {
02106                         VectorCopy( start, end );
02107                         end[i] += maxs[i]-(mins[i]*tr.fraction);
02108                         trap_Trace( &tr, start, vec3_origin, vec3_origin, end, ignore, clipmask );
02109                         if ( tr.allsolid || tr.startsolid )
02110                         {
02111                                 return qfalse;
02112                         }
02113                         if ( tr.fraction < 1.0 )
02114                         {
02115                                 return qfalse;
02116                         }
02117                         VectorCopy( end, start );
02118                 }
02119         }
02120         //expanded it, now see if it's all clear
02121         trap_Trace( &tr, start, mins, maxs, start, ignore, clipmask );
02122         if ( tr.allsolid || tr.startsolid )
02123         {
02124                 return qfalse;
02125         }
02126         VectorCopy( start, point );
02127         return qtrue;
02128 }
02129 
02130 extern qboolean G_FindClosestPointOnLineSegment( const vec3_t start, const vec3_t end, const vec3_t from, vec3_t result );
02131 float ShortestLineSegBewteen2LineSegs( vec3_t start1, vec3_t end1, vec3_t start2, vec3_t end2, vec3_t close_pnt1, vec3_t close_pnt2 )
02132 {
02133         float   current_dist, new_dist;
02134         vec3_t  new_pnt;
02135         //start1, end1 : the first segment
02136         //start2, end2 : the second segment
02137 
02138         //output, one point on each segment, the closest two points on the segments.
02139 
02140         //compute some temporaries:
02141         //vec start_dif = start2 - start1
02142         vec3_t  start_dif;
02143         vec3_t  v1;
02144         vec3_t  v2;
02145         float v1v1, v2v2, v1v2;
02146         float denom;
02147 
02148         VectorSubtract( start2, start1, start_dif );
02149         //vec v1 = end1 - start1
02150         VectorSubtract( end1, start1, v1 );
02151         //vec v2 = end2 - start2
02152         VectorSubtract( end2, start2, v2 );
02153         //
02154         v1v1 = DotProduct( v1, v1 );
02155         v2v2 = DotProduct( v2, v2 );
02156         v1v2 = DotProduct( v1, v2 );
02157 
02158         //the main computation
02159 
02160         denom = (v1v2 * v1v2) - (v1v1 * v2v2);
02161 
02162         //if denom is small, then skip all this and jump to the section marked below
02163         if ( fabs(denom) > 0.001f )
02164         {
02165                 float s = -( (v2v2*DotProduct( v1, start_dif )) - (v1v2*DotProduct( v2, start_dif )) ) / denom;
02166                 float t = ( (v1v1*DotProduct( v2, start_dif )) - (v1v2*DotProduct( v1, start_dif )) ) / denom;
02167                 qboolean done = qtrue;
02168 
02169                 if ( s < 0 )
02170                 {
02171                         done = qfalse;
02172                         s = 0;// and see note below
02173                 }
02174 
02175                 if ( s > 1 ) 
02176                 {
02177                         done = qfalse;
02178                         s = 1;// and see note below
02179                 }
02180 
02181                 if ( t < 0 ) 
02182                 {
02183                         done = qfalse;
02184                         t = 0;// and see note below
02185                 }
02186 
02187                 if ( t > 1 ) 
02188                 {
02189                         done = qfalse;
02190                         t = 1;// and see note below
02191                 }
02192 
02193                 //vec close_pnt1 = start1 + s * v1
02194                 VectorMA( start1, s, v1, close_pnt1 );
02195                 //vec close_pnt2 = start2 + t * v2
02196                 VectorMA( start2, t, v2, close_pnt2 );
02197 
02198                 current_dist = Distance( close_pnt1, close_pnt2 );
02199                 //now, if none of those if's fired, you are done. 
02200                 if ( done )
02201                 {
02202                         return current_dist;
02203                 }
02204                 //If they did fire, then we need to do some additional tests.
02205 
02206                 //What we are gonna do is see if we can find a shorter distance than the above
02207                 //involving the endpoints.
02208         }
02209         else
02210         {
02211                 //******start here for paralell lines with current_dist = infinity****
02212                 current_dist = Q3_INFINITE;
02213         }
02214 
02215         //test 2 close_pnts first
02216         /*
02217         G_FindClosestPointOnLineSegment( start1, end1, close_pnt2, new_pnt );
02218         new_dist = Distance( close_pnt2, new_pnt );
02219         if ( new_dist < current_dist )
02220         {//then update close_pnt1 close_pnt2 and current_dist
02221                 VectorCopy( new_pnt, close_pnt1 );
02222                 VectorCopy( close_pnt2, close_pnt2 );
02223                 current_dist = new_dist;
02224         }
02225 
02226         G_FindClosestPointOnLineSegment( start2, end2, close_pnt1, new_pnt );
02227         new_dist = Distance( close_pnt1, new_pnt );
02228         if ( new_dist < current_dist )
02229         {//then update close_pnt1 close_pnt2 and current_dist
02230                 VectorCopy( close_pnt1, close_pnt1 );
02231                 VectorCopy( new_pnt, close_pnt2 );
02232                 current_dist = new_dist;
02233         }
02234         */
02235         //test all the endpoints
02236         new_dist = Distance( start1, start2 );
02237         if ( new_dist < current_dist )
02238         {//then update close_pnt1 close_pnt2 and current_dist
02239                 VectorCopy( start1, close_pnt1 );
02240                 VectorCopy( start2, close_pnt2 );
02241                 current_dist = new_dist;
02242         }
02243 
02244         new_dist = Distance( start1, end2 );
02245         if ( new_dist < current_dist )
02246         {//then update close_pnt1 close_pnt2 and current_dist
02247                 VectorCopy( start1, close_pnt1 );
02248                 VectorCopy( end2, close_pnt2 );
02249                 current_dist = new_dist;
02250         }
02251 
02252         new_dist = Distance( end1, start2 );
02253         if ( new_dist < current_dist )
02254         {//then update close_pnt1 close_pnt2 and current_dist
02255                 VectorCopy( end1, close_pnt1 );
02256                 VectorCopy( start2, close_pnt2 );
02257                 current_dist = new_dist;
02258         }
02259 
02260         new_dist = Distance( end1, end2 );
02261         if ( new_dist < current_dist )
02262         {//then update close_pnt1 close_pnt2 and current_dist
02263                 VectorCopy( end1, close_pnt1 );
02264                 VectorCopy( end2, close_pnt2 );
02265                 current_dist = new_dist;
02266         }
02267 
02268         //Then we have 4 more point / segment tests
02269 
02270         G_FindClosestPointOnLineSegment( start2, end2, start1, new_pnt );
02271         new_dist = Distance( start1, new_pnt );
02272         if ( new_dist < current_dist )
02273         {//then update close_pnt1 close_pnt2 and current_dist
02274                 VectorCopy( start1, close_pnt1 );
02275                 VectorCopy( new_pnt, close_pnt2 );
02276                 current_dist = new_dist;
02277         }
02278 
02279         G_FindClosestPointOnLineSegment( start2, end2, end1, new_pnt );
02280         new_dist = Distance( end1, new_pnt );
02281         if ( new_dist < current_dist )
02282         {//then update close_pnt1 close_pnt2 and current_dist
02283                 VectorCopy( end1, close_pnt1 );
02284                 VectorCopy( new_pnt, close_pnt2 );
02285                 current_dist = new_dist;
02286         }
02287 
02288         G_FindClosestPointOnLineSegment( start1, end1, start2, new_pnt );
02289         new_dist = Distance( start2, new_pnt );
02290         if ( new_dist < current_dist )
02291         {//then update close_pnt1 close_pnt2 and current_dist
02292                 VectorCopy( new_pnt, close_pnt1 );
02293                 VectorCopy( start2, close_pnt2 );
02294                 current_dist = new_dist;
02295         }
02296 
02297         G_FindClosestPointOnLineSegment( start1, end1, end2, new_pnt );
02298         new_dist = Distance( end2, new_pnt );
02299         if ( new_dist < current_dist )
02300         {//then update close_pnt1 close_pnt2 and current_dist
02301                 VectorCopy( new_pnt, close_pnt1 );
02302                 VectorCopy( end2, close_pnt2 );
02303                 current_dist = new_dist;
02304         }
02305 
02306         return current_dist;
02307 }
02308 
02309 void GetAnglesForDirection( const vec3_t p1, const vec3_t p2, vec3_t out )
02310 {
02311         vec3_t v;
02312 
02313         VectorSubtract( p2, p1, v );
02314         vectoangles( v, out );
02315 }