codemp/game/ai_wpnav.c

Go to the documentation of this file.
00001 #include "g_local.h"
00002 #include "q_shared.h"
00003 #include "botlib.h"
00004 #include "ai_main.h"
00005 
00006 float gWPRenderTime = 0;
00007 float gDeactivated = 0;
00008 float gBotEdit = 0;
00009 int gWPRenderedFrame = 0;
00010 
00011 #include "../namespace_begin.h"
00012 wpobject_t *gWPArray[MAX_WPARRAY_SIZE];
00013 int gWPNum = 0;
00014 #include "../namespace_end.h"
00015 
00016 int gLastPrintedIndex = -1;
00017 
00018 #ifndef _XBOX
00019 nodeobject_t nodetable[MAX_NODETABLE_SIZE];
00020 int nodenum; //so we can connect broken trails
00021 #endif
00022 
00023 int gLevelFlags = 0;
00024 
00025 char *GetFlagStr( int flags )
00026 {
00027         char *flagstr;
00028         int i;
00029 
00030         flagstr = (char *)B_TempAlloc(128);
00031         i = 0;
00032 
00033         if (!flags)
00034         {
00035                 strcpy(flagstr, "none\0");
00036                 goto fend;
00037         }
00038 
00039         if (flags & WPFLAG_JUMP)
00040         {
00041                 flagstr[i] = 'j';
00042                 i++;
00043         }
00044 
00045         if (flags & WPFLAG_DUCK)
00046         {
00047                 flagstr[i] = 'd';
00048                 i++;
00049         }
00050 
00051         if (flags & WPFLAG_SNIPEORCAMPSTAND)
00052         {
00053                 flagstr[i] = 'c';
00054                 i++;
00055         }
00056 
00057         if (flags & WPFLAG_WAITFORFUNC)
00058         {
00059                 flagstr[i] = 'f';
00060                 i++;
00061         }
00062 
00063         if (flags & WPFLAG_SNIPEORCAMP)
00064         {
00065                 flagstr[i] = 's';
00066                 i++;
00067         }
00068 
00069         if (flags & WPFLAG_ONEWAY_FWD)
00070         {
00071                 flagstr[i] = 'x';
00072                 i++;
00073         }
00074 
00075         if (flags & WPFLAG_ONEWAY_BACK)
00076         {
00077                 flagstr[i] = 'y';
00078                 i++;
00079         }
00080 
00081         if (flags & WPFLAG_GOALPOINT)
00082         {
00083                 flagstr[i] = 'g';
00084                 i++;
00085         }
00086 
00087         if (flags & WPFLAG_NOVIS)
00088         {
00089                 flagstr[i] = 'n';
00090                 i++;
00091         }
00092 
00093         if (flags & WPFLAG_NOMOVEFUNC)
00094         {
00095                 flagstr[i] = 'm';
00096                 i++;
00097         }
00098 
00099         if (flags & WPFLAG_RED_FLAG)
00100         {
00101                 if (i)
00102                 {
00103                         flagstr[i] = ' ';
00104                         i++;
00105                 }
00106                 flagstr[i] = 'r';
00107                 i++;
00108                 flagstr[i] = 'e';
00109                 i++;
00110                 flagstr[i] = 'd';
00111                 i++;
00112                 flagstr[i] = ' ';
00113                 i++;
00114                 flagstr[i] = 'f';
00115                 i++;
00116                 flagstr[i] = 'l';
00117                 i++;
00118                 flagstr[i] = 'a';
00119                 i++;
00120                 flagstr[i] = 'g';
00121                 i++;
00122         }
00123 
00124         if (flags & WPFLAG_BLUE_FLAG)
00125         {
00126                 if (i)
00127                 {
00128                         flagstr[i] = ' ';
00129                         i++;
00130                 }
00131                 flagstr[i] = 'b';
00132                 i++;
00133                 flagstr[i] = 'l';
00134                 i++;
00135                 flagstr[i] = 'u';
00136                 i++;
00137                 flagstr[i] = 'e';
00138                 i++;
00139                 flagstr[i] = ' ';
00140                 i++;
00141                 flagstr[i] = 'f';
00142                 i++;
00143                 flagstr[i] = 'l';
00144                 i++;
00145                 flagstr[i] = 'a';
00146                 i++;
00147                 flagstr[i] = 'g';
00148                 i++;
00149         }
00150 
00151         if (flags & WPFLAG_SIEGE_IMPERIALOBJ)
00152         {
00153                 if (i)
00154                 {
00155                         flagstr[i] = ' ';
00156                         i++;
00157                 }
00158                 flagstr[i] = 's';
00159                 i++;
00160                 flagstr[i] = 'a';
00161                 i++;
00162                 flagstr[i] = 'g';
00163                 i++;
00164                 flagstr[i] = 'a';
00165                 i++;
00166                 flagstr[i] = '_';
00167                 i++;
00168                 flagstr[i] = 'i';
00169                 i++;
00170                 flagstr[i] = 'm';
00171                 i++;
00172                 flagstr[i] = 'p';
00173                 i++;
00174         }
00175 
00176         if (flags & WPFLAG_SIEGE_REBELOBJ)
00177         {
00178                 if (i)
00179                 {
00180                         flagstr[i] = ' ';
00181                         i++;
00182                 }
00183                 flagstr[i] = 's';
00184                 i++;
00185                 flagstr[i] = 'a';
00186                 i++;
00187                 flagstr[i] = 'g';
00188                 i++;
00189                 flagstr[i] = 'a';
00190                 i++;
00191                 flagstr[i] = '_';
00192                 i++;
00193                 flagstr[i] = 'r';
00194                 i++;
00195                 flagstr[i] = 'e';
00196                 i++;
00197                 flagstr[i] = 'b';
00198                 i++;
00199         }
00200 
00201         flagstr[i] = '\0';
00202 
00203         if (i == 0)
00204         {
00205                 strcpy(flagstr, "unknown\0");
00206         }
00207 
00208 fend:
00209         return flagstr;
00210 }
00211 
00212 void G_TestLine(vec3_t start, vec3_t end, int color, int time)
00213 {
00214         gentity_t *te;
00215 
00216         te = G_TempEntity( start, EV_TESTLINE );
00217         VectorCopy(start, te->s.origin);
00218         VectorCopy(end, te->s.origin2);
00219         te->s.time2 = time;
00220         te->s.weapon = color;
00221         te->r.svFlags |= SVF_BROADCAST;
00222 }
00223 
00224 void BotWaypointRender(void)
00225 {
00226         int i, n;
00227         int inc_checker;
00228         int bestindex;
00229         int gotbestindex;
00230         float bestdist;
00231         float checkdist;
00232         gentity_t *plum;
00233         gentity_t *viewent;
00234         char *flagstr;
00235         vec3_t a;
00236 
00237         if (!gBotEdit)
00238         {
00239                 return;
00240         }
00241 
00242         bestindex = 0;
00243 
00244         if (gWPRenderTime > level.time)
00245         {
00246                 goto checkprint;
00247         }
00248 
00249         gWPRenderTime = level.time + 100;
00250 
00251         i = gWPRenderedFrame;
00252         inc_checker = gWPRenderedFrame;
00253 
00254         while (i < gWPNum)
00255         {
00256                 if (gWPArray[i] && gWPArray[i]->inuse)
00257                 {
00258                         plum = G_TempEntity( gWPArray[i]->origin, EV_SCOREPLUM );
00259                         plum->r.svFlags |= SVF_BROADCAST;
00260                         plum->s.time = i;
00261                         
00262                         n = 0;
00263 
00264                         while (n < gWPArray[i]->neighbornum)
00265                         {
00266                                 if (gWPArray[i]->neighbors[n].forceJumpTo && gWPArray[gWPArray[i]->neighbors[n].num])
00267                                 {
00268                                         G_TestLine(gWPArray[i]->origin, gWPArray[gWPArray[i]->neighbors[n].num]->origin, 0x0000ff, 5000);
00269                                 }
00270                                 n++;
00271                         }
00272 
00273                         gWPRenderedFrame++;
00274                 }
00275                 else
00276                 {
00277                         gWPRenderedFrame = 0;
00278                         break;
00279                 }
00280 
00281                 if ((i - inc_checker) > 4)
00282                 {
00283                         break; //don't render too many at once
00284                 }
00285                 i++;
00286         }
00287 
00288         if (i >= gWPNum)
00289         {
00290                 gWPRenderTime = level.time + 1500; //wait a bit after we finish doing the whole trail
00291                 gWPRenderedFrame = 0;
00292         }
00293 
00294 checkprint:
00295 
00296         if (!bot_wp_info.value)
00297         {
00298                 return;
00299         }
00300 
00301         viewent = &g_entities[0]; //only show info to the first client
00302 
00303         if (!viewent || !viewent->client)
00304         { //client isn't in the game yet?
00305                 return;
00306         }
00307 
00308         bestdist = 256; //max distance for showing point info
00309         gotbestindex = 0;
00310 
00311         i = 0;
00312 
00313         while (i < gWPNum)
00314         {
00315                 if (gWPArray[i] && gWPArray[i]->inuse)
00316                 {
00317                         VectorSubtract(viewent->client->ps.origin, gWPArray[i]->origin, a);
00318 
00319                         checkdist = VectorLength(a);
00320 
00321                         if (checkdist < bestdist)
00322                         {
00323                                 bestdist = checkdist;
00324                                 bestindex = i;
00325                                 gotbestindex = 1;
00326                         }
00327                 }
00328                 i++;
00329         }
00330 
00331         if (gotbestindex && bestindex != gLastPrintedIndex)
00332         {
00333                 flagstr = GetFlagStr(gWPArray[bestindex]->flags);
00334                 gLastPrintedIndex = bestindex;
00335                 G_Printf(S_COLOR_YELLOW "Waypoint %i\nFlags - %i (%s) (w%f)\nOrigin - (%i %i %i)\n", (int)(gWPArray[bestindex]->index), (int)(gWPArray[bestindex]->flags), flagstr, gWPArray[bestindex]->weight, (int)(gWPArray[bestindex]->origin[0]), (int)(gWPArray[bestindex]->origin[1]), (int)(gWPArray[bestindex]->origin[2]));
00336                 //GetFlagStr allocates 128 bytes for this, if it's changed then obviously this must be as well
00337                 B_TempFree(128); //flagstr
00338 
00339                 plum = G_TempEntity( gWPArray[bestindex]->origin, EV_SCOREPLUM );
00340                 plum->r.svFlags |= SVF_BROADCAST;
00341                 plum->s.time = bestindex; //render it once
00342         }
00343         else if (!gotbestindex)
00344         {
00345                 gLastPrintedIndex = -1;
00346         }
00347 }
00348 
00349 void TransferWPData(int from, int to)
00350 {
00351         if (!gWPArray[to])
00352         {
00353                 gWPArray[to] = (wpobject_t *)B_Alloc(sizeof(wpobject_t));
00354         }
00355 
00356         if (!gWPArray[to])
00357         {
00358                 G_Printf(S_COLOR_RED "FATAL ERROR: Could not allocated memory for waypoint\n");
00359         }
00360 
00361         gWPArray[to]->flags = gWPArray[from]->flags;
00362         gWPArray[to]->weight = gWPArray[from]->weight;
00363         gWPArray[to]->associated_entity = gWPArray[from]->associated_entity;
00364         gWPArray[to]->disttonext = gWPArray[from]->disttonext;
00365         gWPArray[to]->forceJumpTo = gWPArray[from]->forceJumpTo;
00366         gWPArray[to]->index = to;
00367         gWPArray[to]->inuse = gWPArray[from]->inuse;
00368         VectorCopy(gWPArray[from]->origin, gWPArray[to]->origin);
00369 }
00370 
00371 void CreateNewWP(vec3_t origin, int flags)
00372 {
00373         if (gWPNum >= MAX_WPARRAY_SIZE)
00374         {
00375                 if (!g_RMG.integer)
00376                 {
00377                         G_Printf(S_COLOR_YELLOW "Warning: Waypoint limit hit (%i)\n", MAX_WPARRAY_SIZE);
00378                 }
00379                 return;
00380         }
00381 
00382         if (!gWPArray[gWPNum])
00383         {
00384                 gWPArray[gWPNum] = (wpobject_t *)B_Alloc(sizeof(wpobject_t));
00385         }
00386 
00387         if (!gWPArray[gWPNum])
00388         {
00389                 G_Printf(S_COLOR_RED "ERROR: Could not allocated memory for waypoint\n");
00390         }
00391 
00392         gWPArray[gWPNum]->flags = flags;
00393         gWPArray[gWPNum]->weight = 0; //calculated elsewhere
00394         gWPArray[gWPNum]->associated_entity = ENTITYNUM_NONE; //set elsewhere
00395         gWPArray[gWPNum]->forceJumpTo = 0;
00396         gWPArray[gWPNum]->disttonext = 0; //calculated elsewhere
00397         gWPArray[gWPNum]->index = gWPNum;
00398         gWPArray[gWPNum]->inuse = 1;
00399         VectorCopy(origin, gWPArray[gWPNum]->origin);
00400         gWPNum++;
00401 }
00402 
00403 void CreateNewWP_FromObject(wpobject_t *wp)
00404 {
00405         int i;
00406 
00407         if (gWPNum >= MAX_WPARRAY_SIZE)
00408         {
00409                 return;
00410         }
00411 
00412         if (!gWPArray[gWPNum])
00413         {
00414                 gWPArray[gWPNum] = (wpobject_t *)B_Alloc(sizeof(wpobject_t));
00415         }
00416 
00417         if (!gWPArray[gWPNum])
00418         {
00419                 G_Printf(S_COLOR_RED "ERROR: Could not allocated memory for waypoint\n");
00420         }
00421 
00422         gWPArray[gWPNum]->flags = wp->flags;
00423         gWPArray[gWPNum]->weight = wp->weight;
00424         gWPArray[gWPNum]->associated_entity = wp->associated_entity;
00425         gWPArray[gWPNum]->disttonext = wp->disttonext;
00426         gWPArray[gWPNum]->forceJumpTo = wp->forceJumpTo;
00427         gWPArray[gWPNum]->index = gWPNum;
00428         gWPArray[gWPNum]->inuse = 1;
00429         VectorCopy(wp->origin, gWPArray[gWPNum]->origin);
00430         gWPArray[gWPNum]->neighbornum = wp->neighbornum;
00431 
00432         i = wp->neighbornum;
00433 
00434         while (i >= 0)
00435         {
00436                 gWPArray[gWPNum]->neighbors[i].num = wp->neighbors[i].num;
00437                 gWPArray[gWPNum]->neighbors[i].forceJumpTo = wp->neighbors[i].forceJumpTo;
00438 
00439                 i--;
00440         }
00441 
00442         if (gWPArray[gWPNum]->flags & WPFLAG_RED_FLAG)
00443         {
00444                 flagRed = gWPArray[gWPNum];
00445                 oFlagRed = flagRed;
00446         }
00447         else if (gWPArray[gWPNum]->flags & WPFLAG_BLUE_FLAG)
00448         {
00449                 flagBlue = gWPArray[gWPNum];
00450                 oFlagBlue = flagBlue;
00451         }
00452 
00453         gWPNum++;
00454 }
00455 
00456 void RemoveWP(void)
00457 {
00458         if (gWPNum <= 0)
00459         {
00460                 return;
00461         }
00462 
00463         gWPNum--;
00464 
00465         if (!gWPArray[gWPNum] || !gWPArray[gWPNum]->inuse)
00466         {
00467                 return;
00468         }
00469 
00470         //B_Free((wpobject_t *)gWPArray[gWPNum]);
00471         if (gWPArray[gWPNum])
00472         {
00473                 memset( gWPArray[gWPNum], 0, sizeof(gWPArray[gWPNum]) );
00474         }
00475 
00476         //gWPArray[gWPNum] = NULL;
00477 
00478         if (gWPArray[gWPNum])
00479         {
00480                 gWPArray[gWPNum]->inuse = 0;
00481         }
00482 }
00483 
00484 void RemoveAllWP(void)
00485 {
00486         while(gWPNum) {
00487                 RemoveWP();
00488         }
00489 }
00490 
00491 void RemoveWP_InTrail(int afterindex)
00492 {
00493         int foundindex;
00494         int foundanindex;
00495         int didchange;
00496         int i;
00497 
00498         foundindex = 0;
00499         foundanindex = 0;
00500         didchange = 0;
00501         i = 0;
00502 
00503         if (afterindex < 0 || afterindex >= gWPNum)
00504         {
00505                 G_Printf(S_COLOR_YELLOW "Waypoint number %i does not exist\n", afterindex);
00506                 return;
00507         }
00508 
00509         while (i < gWPNum)
00510         {
00511                 if (gWPArray[i] && gWPArray[i]->inuse && gWPArray[i]->index == afterindex)
00512                 {
00513                         foundindex = i;
00514                         foundanindex = 1;
00515                         break;
00516                 }
00517 
00518                 i++;
00519         }
00520 
00521         if (!foundanindex)
00522         {
00523                 G_Printf(S_COLOR_YELLOW "Waypoint index %i should exist, but does not (?)\n", afterindex);
00524                 return;
00525         }
00526 
00527         i = 0;
00528 
00529         while (i <= gWPNum)
00530         {
00531                 if (gWPArray[i] && gWPArray[i]->index == foundindex)
00532                 {
00533                         //B_Free(gWPArray[i]);
00534 
00535                         //Keep reusing the memory
00536                         memset( gWPArray[i], 0, sizeof(gWPArray[i]) );
00537 
00538                         //gWPArray[i] = NULL;
00539                         gWPArray[i]->inuse = 0;
00540                         didchange = 1;
00541                 }
00542                 else if (gWPArray[i] && didchange)
00543                 {
00544                         TransferWPData(i, i-1);
00545                         //B_Free(gWPArray[i]);
00546 
00547                         //Keep reusing the memory
00548                         memset( gWPArray[i], 0, sizeof(gWPArray[i]) );
00549 
00550                         //gWPArray[i] = NULL;
00551                         gWPArray[i]->inuse = 0;
00552                 }
00553 
00554                 i++;
00555         }
00556         gWPNum--;
00557 }
00558 
00559 int CreateNewWP_InTrail(vec3_t origin, int flags, int afterindex)
00560 {
00561         int foundindex;
00562         int foundanindex;
00563         int i;
00564 
00565         foundindex = 0;
00566         foundanindex = 0;
00567         i = 0;
00568 
00569         if (gWPNum >= MAX_WPARRAY_SIZE)
00570         {
00571                 if (!g_RMG.integer)
00572                 {
00573                         G_Printf(S_COLOR_YELLOW "Warning: Waypoint limit hit (%i)\n", MAX_WPARRAY_SIZE);
00574                 }
00575                 return 0;
00576         }
00577 
00578         if (afterindex < 0 || afterindex >= gWPNum)
00579         {
00580                 G_Printf(S_COLOR_YELLOW "Waypoint number %i does not exist\n", afterindex);
00581                 return 0;
00582         }
00583 
00584         while (i < gWPNum)
00585         {
00586                 if (gWPArray[i] && gWPArray[i]->inuse && gWPArray[i]->index == afterindex)
00587                 {
00588                         foundindex = i;
00589                         foundanindex = 1;
00590                         break;
00591                 }
00592 
00593                 i++;
00594         }
00595 
00596         if (!foundanindex)
00597         {
00598                 G_Printf(S_COLOR_YELLOW "Waypoint index %i should exist, but does not (?)\n", afterindex);
00599                 return 0;
00600         }
00601 
00602         i = gWPNum;
00603 
00604         while (i >= 0)
00605         {
00606                 if (gWPArray[i] && gWPArray[i]->inuse && gWPArray[i]->index != foundindex)
00607                 {
00608                         TransferWPData(i, i+1);
00609                 }
00610                 else if (gWPArray[i] && gWPArray[i]->inuse && gWPArray[i]->index == foundindex)
00611                 {
00612                         i++;
00613 
00614                         if (!gWPArray[i])
00615                         {
00616                                 gWPArray[i] = (wpobject_t *)B_Alloc(sizeof(wpobject_t));
00617                         }
00618 
00619                         gWPArray[i]->flags = flags;
00620                         gWPArray[i]->weight = 0; //calculated elsewhere
00621                         gWPArray[i]->associated_entity = ENTITYNUM_NONE; //set elsewhere
00622                         gWPArray[i]->disttonext = 0; //calculated elsewhere
00623                         gWPArray[i]->forceJumpTo = 0;
00624                         gWPArray[i]->index = i;
00625                         gWPArray[i]->inuse = 1;
00626                         VectorCopy(origin, gWPArray[i]->origin);
00627                         gWPNum++;
00628                         break;
00629                 }
00630 
00631                 i--;
00632         }
00633 
00634         return 1;
00635 }
00636 
00637 int CreateNewWP_InsertUnder(vec3_t origin, int flags, int afterindex)
00638 {
00639         int foundindex;
00640         int foundanindex;
00641         int i;
00642 
00643         foundindex = 0;
00644         foundanindex = 0;
00645         i = 0;
00646 
00647         if (gWPNum >= MAX_WPARRAY_SIZE)
00648         {
00649                 if (!g_RMG.integer)
00650                 {
00651                         G_Printf(S_COLOR_YELLOW "Warning: Waypoint limit hit (%i)\n", MAX_WPARRAY_SIZE);
00652                 }
00653                 return 0;
00654         }
00655 
00656         if (afterindex < 0 || afterindex >= gWPNum)
00657         {
00658                 G_Printf(S_COLOR_YELLOW "Waypoint number %i does not exist\n", afterindex);
00659                 return 0;
00660         }
00661 
00662         while (i < gWPNum)
00663         {
00664                 if (gWPArray[i] && gWPArray[i]->inuse && gWPArray[i]->index == afterindex)
00665                 {
00666                         foundindex = i;
00667                         foundanindex = 1;
00668                         break;
00669                 }
00670 
00671                 i++;
00672         }
00673 
00674         if (!foundanindex)
00675         {
00676                 G_Printf(S_COLOR_YELLOW "Waypoint index %i should exist, but does not (?)\n", afterindex);
00677                 return 0;
00678         }
00679 
00680         i = gWPNum;
00681 
00682         while (i >= 0)
00683         {
00684                 if (gWPArray[i] && gWPArray[i]->inuse && gWPArray[i]->index != foundindex)
00685                 {
00686                         TransferWPData(i, i+1);
00687                 }
00688                 else if (gWPArray[i] && gWPArray[i]->inuse && gWPArray[i]->index == foundindex)
00689                 {
00690                         //i++;
00691                         TransferWPData(i, i+1);
00692 
00693                         if (!gWPArray[i])
00694                         {
00695                                 gWPArray[i] = (wpobject_t *)B_Alloc(sizeof(wpobject_t));
00696                         }
00697 
00698                         gWPArray[i]->flags = flags;
00699                         gWPArray[i]->weight = 0; //calculated elsewhere
00700                         gWPArray[i]->associated_entity = ENTITYNUM_NONE; //set elsewhere
00701                         gWPArray[i]->disttonext = 0; //calculated elsewhere
00702                         gWPArray[i]->forceJumpTo = 0;
00703                         gWPArray[i]->index = i;
00704                         gWPArray[i]->inuse = 1;
00705                         VectorCopy(origin, gWPArray[i]->origin);
00706                         gWPNum++;
00707                         break;
00708                 }
00709 
00710                 i--;
00711         }
00712 
00713         return 1;
00714 }
00715 
00716 void TeleportToWP(gentity_t *pl, int afterindex)
00717 {
00718         int foundindex;
00719         int foundanindex;
00720         int i;
00721 
00722         if (!pl || !pl->client)
00723         {
00724                 return;
00725         }
00726 
00727         foundindex = 0;
00728         foundanindex = 0;
00729         i = 0;
00730 
00731         if (afterindex < 0 || afterindex >= gWPNum)
00732         {
00733                 G_Printf(S_COLOR_YELLOW "Waypoint number %i does not exist\n", afterindex);
00734                 return;
00735         }
00736 
00737         while (i < gWPNum)
00738         {
00739                 if (gWPArray[i] && gWPArray[i]->inuse && gWPArray[i]->index == afterindex)
00740                 {
00741                         foundindex = i;
00742                         foundanindex = 1;
00743                         break;
00744                 }
00745 
00746                 i++;
00747         }
00748 
00749         if (!foundanindex)
00750         {
00751                 G_Printf(S_COLOR_YELLOW "Waypoint index %i should exist, but does not (?)\n", afterindex);
00752                 return;
00753         }
00754 
00755         VectorCopy(gWPArray[foundindex]->origin, pl->client->ps.origin);
00756 
00757         return;
00758 }
00759 
00760 void WPFlagsModify(int wpnum, int flags)
00761 {
00762         if (wpnum < 0 || wpnum >= gWPNum || !gWPArray[wpnum] || !gWPArray[wpnum]->inuse)
00763         {
00764                 G_Printf(S_COLOR_YELLOW "WPFlagsModify: Waypoint %i does not exist\n", wpnum);
00765                 return;
00766         }
00767 
00768         gWPArray[wpnum]->flags = flags;
00769 }
00770 
00771 static int NotWithinRange(int base, int extent)
00772 {
00773         if (extent > base && base+5 >= extent)
00774         {
00775                 return 0;
00776         }
00777 
00778         if (extent < base && base-5 <= extent)
00779         {
00780                 return 0;
00781         }
00782 
00783         return 1;
00784 }
00785 
00786 #ifndef _XBOX
00787 int NodeHere(vec3_t spot)
00788 {
00789         int i;
00790 
00791         i = 0;
00792 
00793         while (i < nodenum)
00794         {
00795                 if ((int)nodetable[i].origin[0] == (int)spot[0] &&
00796                         (int)nodetable[i].origin[1] == (int)spot[1])
00797                 {
00798                         if ((int)nodetable[i].origin[2] == (int)spot[2] ||
00799                                 ((int)nodetable[i].origin[2] < (int)spot[2] && (int)nodetable[i].origin[2]+5 > (int)spot[2]) ||
00800                                 ((int)nodetable[i].origin[2] > (int)spot[2] && (int)nodetable[i].origin[2]-5 < (int)spot[2]))
00801                         {
00802                                 return 1;
00803                         }
00804                 }
00805                 i++;
00806         }
00807 
00808         return 0;
00809 }
00810 #endif
00811 
00812 int CanGetToVector(vec3_t org1, vec3_t org2, vec3_t mins, vec3_t maxs)
00813 {
00814         trace_t tr;
00815 
00816         trap_Trace(&tr, org1, mins, maxs, org2, ENTITYNUM_NONE, MASK_SOLID);
00817 
00818         if (tr.fraction == 1 && !tr.startsolid && !tr.allsolid)
00819         {
00820                 return 1;
00821         }
00822 
00823         return 0;
00824 }
00825 
00826 #if 0
00827 int CanGetToVectorTravel(vec3_t org1, vec3_t org2, vec3_t mins, vec3_t maxs)
00828 {
00829         trace_t tr;
00830         vec3_t a, ang, fwd;
00831         vec3_t midpos, dmid;
00832         float startheight, midheight, fLen;
00833 
00834         mins[2] = -13;
00835         maxs[2] = 13;
00836 
00837         trap_Trace(&tr, org1, mins, maxs, org2, ENTITYNUM_NONE, MASK_SOLID);
00838 
00839         if (tr.fraction != 1 || tr.startsolid || tr.allsolid)
00840         {
00841                 return 0;
00842         }
00843 
00844         VectorSubtract(org2, org1, a);
00845 
00846         vectoangles(a, ang);
00847 
00848         AngleVectors(ang, fwd, NULL, NULL);
00849 
00850         fLen = VectorLength(a)/2;
00851 
00852         midpos[0] = org1[0] + fwd[0]*fLen;
00853         midpos[1] = org1[1] + fwd[1]*fLen;
00854         midpos[2] = org1[2] + fwd[2]*fLen;
00855 
00856         VectorCopy(org1, dmid);
00857         dmid[2] -= 1024;
00858 
00859         trap_Trace(&tr, midpos, NULL, NULL, dmid, ENTITYNUM_NONE, MASK_SOLID);
00860 
00861         startheight = org1[2] - tr.endpos[2];
00862 
00863         VectorCopy(midpos, dmid);
00864         dmid[2] -= 1024;
00865 
00866         trap_Trace(&tr, midpos, NULL, NULL, dmid, ENTITYNUM_NONE, MASK_SOLID);
00867 
00868         if (tr.startsolid || tr.allsolid)
00869         {
00870                 return 1;
00871         }
00872 
00873         midheight = midpos[2] - tr.endpos[2];
00874 
00875         if (midheight > startheight*2)
00876         {
00877                 return 0; //too steep of a drop.. can't go on
00878         }
00879 
00880         return 1;
00881 }
00882 #else
00883 int CanGetToVectorTravel(vec3_t org1, vec3_t moveTo, vec3_t mins, vec3_t maxs)
00884 //int ExampleAnimEntMove(gentity_t *self, vec3_t moveTo, float stepSize)
00885 {
00886         trace_t tr;
00887         vec3_t stepTo;
00888         vec3_t stepSub;
00889         vec3_t stepGoal;
00890         vec3_t workingOrg;
00891         vec3_t lastIncrement;
00892         vec3_t finalMeasure;
00893         float stepSize = 0;
00894         float measureLength = 0;
00895         int didMove = 0;
00896         int traceMask = MASK_PLAYERSOLID;
00897         qboolean initialDone = qfalse;
00898 
00899         VectorCopy(org1, workingOrg);
00900         VectorCopy(org1, lastIncrement);
00901 
00902         VectorCopy(moveTo, stepTo);
00903         stepTo[2] = workingOrg[2];
00904 
00905         VectorSubtract(stepTo, workingOrg, stepSub);
00906         stepSize = VectorLength(stepSub); //make the step size the length of the original positions without Z
00907 
00908         VectorNormalize(stepSub);
00909 
00910         while (!initialDone || didMove)
00911         {
00912                 initialDone = qtrue;
00913                 didMove = 0;
00914 
00915                 stepGoal[0] = workingOrg[0] + stepSub[0]*stepSize;
00916                 stepGoal[1] = workingOrg[1] + stepSub[1]*stepSize;
00917                 stepGoal[2] = workingOrg[2] + stepSub[2]*stepSize;
00918 
00919                 trap_Trace(&tr, workingOrg, mins, maxs, stepGoal, ENTITYNUM_NONE, traceMask);
00920 
00921                 if (!tr.startsolid && !tr.allsolid && tr.fraction)
00922                 {
00923                         vec3_t vecSub;
00924                         VectorSubtract(workingOrg, tr.endpos, vecSub);
00925 
00926                         if (VectorLength(vecSub) > (stepSize/2))
00927                         {
00928                                 workingOrg[0] = tr.endpos[0];
00929                                 workingOrg[1] = tr.endpos[1];
00930                                 //trap_LinkEntity(self);
00931                                 didMove = 1;
00932                         }
00933                 }
00934                 
00935                 if (didMove != 1)
00936                 { //stair check
00937                         vec3_t trFrom;
00938                         vec3_t trTo;
00939                         vec3_t trDir;
00940                         vec3_t vecMeasure;
00941 
00942                         VectorCopy(tr.endpos, trFrom);
00943                         trFrom[2] += 16;
00944 
00945                         VectorSubtract(/*tr.endpos*/stepGoal, workingOrg, trDir);
00946                         VectorNormalize(trDir);
00947                         trTo[0] = tr.endpos[0] + trDir[0]*2;
00948                         trTo[1] = tr.endpos[1] + trDir[1]*2;
00949                         trTo[2] = tr.endpos[2] + trDir[2]*2;
00950                         trTo[2] += 16;
00951 
00952                         VectorSubtract(trFrom, trTo, vecMeasure);
00953 
00954                         if (VectorLength(vecMeasure) > 1)
00955                         {
00956                                 trap_Trace(&tr, trFrom, mins, maxs, trTo, ENTITYNUM_NONE, traceMask);
00957 
00958                                 if (!tr.startsolid && !tr.allsolid && tr.fraction == 1)
00959                                 { //clear trace here, probably up a step
00960                                         vec3_t trDown;
00961                                         vec3_t trUp;
00962                                         VectorCopy(tr.endpos, trUp);
00963                                         VectorCopy(tr.endpos, trDown);
00964                                         trDown[2] -= 16;
00965 
00966                                         trap_Trace(&tr, trFrom, mins, maxs, trTo, ENTITYNUM_NONE, traceMask);
00967 
00968                                         if (!tr.startsolid && !tr.allsolid)
00969                                         { //plop us down on the step after moving up
00970                                                 VectorCopy(tr.endpos, workingOrg);
00971                                                 //trap_LinkEntity(self);
00972                                                 didMove = 1;
00973                                         }
00974                                 }
00975                         }
00976                 }
00977 
00978                 VectorSubtract(lastIncrement, workingOrg, finalMeasure);
00979                 measureLength = VectorLength(finalMeasure);
00980 
00981                 if (!measureLength)
00982                 { //no progress, break out. If last movement was a sucess didMove will equal 1.
00983                         break;
00984                 }
00985 
00986                 stepSize -= measureLength; //subtract the progress distance from the step size so we don't overshoot the mark.
00987                 if (stepSize <= 0)
00988                 {
00989                         break;
00990                 }
00991 
00992                 VectorCopy(workingOrg, lastIncrement);
00993         }
00994 
00995         return didMove;
00996 }
00997 #endif
00998 
00999 #ifndef _XBOX
01000 int ConnectTrail(int startindex, int endindex, qboolean behindTheScenes)
01001 {
01002         int foundit;
01003         int cancontinue;
01004         int i;
01005         int failsafe;
01006         int successnodeindex;
01007         int insertindex;
01008         int prenodestart;
01009         byte extendednodes[MAX_NODETABLE_SIZE]; //for storing checked nodes and not trying to extend them each a bazillion times
01010         float fvecmeas;
01011         float baseheight;
01012         float branchDistance;
01013         float maxDistFactor = 256;
01014         vec3_t a;
01015         vec3_t startplace, starttrace;
01016         vec3_t mins, maxs;
01017         vec3_t testspot;
01018         vec3_t validspotpos;
01019         trace_t tr;
01020 
01021         if (g_RMG.integer)
01022         { //this might be temporary. Or not.
01023                 if (!(gWPArray[startindex]->flags & WPFLAG_NEVERONEWAY) &&
01024                         !(gWPArray[endindex]->flags & WPFLAG_NEVERONEWAY))
01025                 {
01026                         gWPArray[startindex]->flags |= WPFLAG_ONEWAY_FWD;
01027                         gWPArray[endindex]->flags |= WPFLAG_ONEWAY_BACK;
01028                 }
01029                 return 0;
01030         }
01031 
01032         if (!g_RMG.integer)
01033         {
01034                 branchDistance = TABLE_BRANCH_DISTANCE;
01035         }
01036         else
01037         {
01038                 branchDistance = 512; //be less precise here, terrain is fairly broad, and we don't want to take an hour precalculating
01039         }
01040 
01041         if (g_RMG.integer)
01042         {
01043                 maxDistFactor = 700;
01044         }
01045 
01046         mins[0] = -15;
01047         mins[1] = -15;
01048         mins[2] = 0;
01049         maxs[0] = 15;
01050         maxs[1] = 15;
01051         maxs[2] = 0;
01052 
01053         nodenum = 0;
01054         foundit = 0;
01055 
01056         i = 0;
01057 
01058         successnodeindex = 0;
01059 
01060         while (i < MAX_NODETABLE_SIZE) //clear it out before using it
01061         {
01062                 nodetable[i].flags = 0;
01063 //              nodetable[i].index = 0;
01064                 nodetable[i].inuse = 0;
01065                 nodetable[i].neighbornum = 0;
01066                 nodetable[i].origin[0] = 0;
01067                 nodetable[i].origin[1] = 0;
01068                 nodetable[i].origin[2] = 0;
01069                 nodetable[i].weight = 0;
01070 
01071                 extendednodes[i] = 0;
01072 
01073                 i++;
01074         }
01075 
01076         i = 0;
01077 
01078         if (!behindTheScenes)
01079         {
01080                 G_Printf(S_COLOR_YELLOW "Point %i is not connected to %i - Repairing...\n", startindex, endindex);
01081         }
01082 
01083         VectorCopy(gWPArray[startindex]->origin, startplace);
01084 
01085         VectorCopy(startplace, starttrace);
01086 
01087         starttrace[2] -= 4096;
01088 
01089         trap_Trace(&tr, startplace, NULL, NULL, starttrace, ENTITYNUM_NONE, MASK_SOLID);
01090 
01091         baseheight = startplace[2] - tr.endpos[2];
01092 
01093         cancontinue = 1;
01094 
01095         VectorCopy(startplace, nodetable[nodenum].origin);
01096         nodetable[nodenum].weight = 1;
01097         nodetable[nodenum].inuse = 1;
01098 //      nodetable[nodenum].index = nodenum;
01099         nodenum++;
01100 
01101         while (nodenum < MAX_NODETABLE_SIZE && !foundit && cancontinue)
01102         {
01103                 if (g_RMG.integer)
01104                 { //adjust the branch distance dynamically depending on the distance from the start and end points.
01105                         vec3_t startDist;
01106                         vec3_t endDist;
01107                         float startDistf;
01108                         float endDistf;
01109 
01110                         VectorSubtract(nodetable[nodenum-1].origin, gWPArray[startindex]->origin, startDist);
01111                         VectorSubtract(nodetable[nodenum-1].origin, gWPArray[endindex]->origin, endDist);
01112 
01113                         startDistf = VectorLength(startDist);
01114                         endDistf = VectorLength(endDist);
01115 
01116                         if (startDistf < 64 || endDistf < 64)
01117                         {
01118                                 branchDistance = 64;
01119                         }
01120                         else if (startDistf < 128 || endDistf < 128)
01121                         {
01122                                 branchDistance = 128;
01123                         }
01124                         else if (startDistf < 256 || endDistf < 256)
01125                         {
01126                                 branchDistance = 256;
01127                         }
01128                         else if (startDistf < 512 || endDistf < 512)
01129                         {
01130                                 branchDistance = 512;
01131                         }
01132                         else
01133                         {
01134                                 branchDistance = 800;
01135                         }
01136                 }
01137                 cancontinue = 0;
01138                 i = 0;
01139                 prenodestart = nodenum;
01140 
01141                 while (i < prenodestart)
01142                 {
01143                         if (extendednodes[i] != 1)
01144                         {
01145                                 VectorSubtract(gWPArray[endindex]->origin, nodetable[i].origin, a);
01146                                 fvecmeas = VectorLength(a);
01147 
01148                                 if (fvecmeas < 128 && CanGetToVector(gWPArray[endindex]->origin, nodetable[i].origin, mins, maxs))
01149                                 {
01150                                         foundit = 1;
01151                                         successnodeindex = i;
01152                                         break;
01153                                 }
01154 
01155                                 VectorCopy(nodetable[i].origin, testspot);
01156                                 testspot[0] += branchDistance;
01157 
01158                                 VectorCopy(testspot, starttrace);
01159 
01160                                 starttrace[2] -= 4096;
01161 
01162                                 trap_Trace(&tr, testspot, NULL, NULL, starttrace, ENTITYNUM_NONE, MASK_SOLID);
01163 
01164                                 testspot[2] = tr.endpos[2]+baseheight;
01165 
01166                                 if (!NodeHere(testspot) && !tr.startsolid && !tr.allsolid && CanGetToVector(nodetable[i].origin, testspot, mins, maxs))
01167                                 {
01168                                         VectorCopy(testspot, nodetable[nodenum].origin);
01169                                         nodetable[nodenum].inuse = 1;
01170 //                                      nodetable[nodenum].index = nodenum;
01171                                         nodetable[nodenum].weight = nodetable[i].weight+1;
01172                                         nodetable[nodenum].neighbornum = i;
01173                                         if ((nodetable[i].origin[2] - nodetable[nodenum].origin[2]) > 50)
01174                                         { //if there's a big drop, make sure we know we can't just magically fly back up
01175                                                 nodetable[nodenum].flags = WPFLAG_ONEWAY_FWD;
01176                                         }
01177                                         nodenum++;
01178                                         cancontinue = 1;
01179                                 }
01180 
01181                                 if (nodenum >= MAX_NODETABLE_SIZE)
01182                                 {
01183                                         break; //failure
01184                                 }
01185 
01186                                 VectorCopy(nodetable[i].origin, testspot);
01187                                 testspot[0] -= branchDistance;
01188 
01189                                 VectorCopy(testspot, starttrace);
01190 
01191                                 starttrace[2] -= 4096;
01192 
01193                                 trap_Trace(&tr, testspot, NULL, NULL, starttrace, ENTITYNUM_NONE, MASK_SOLID);
01194 
01195                                 testspot[2] = tr.endpos[2]+baseheight;
01196 
01197                                 if (!NodeHere(testspot) && !tr.startsolid && !tr.allsolid && CanGetToVector(nodetable[i].origin, testspot, mins, maxs))
01198                                 {
01199                                         VectorCopy(testspot, nodetable[nodenum].origin);
01200                                         nodetable[nodenum].inuse = 1;
01201 //                                      nodetable[nodenum].index = nodenum;
01202                                         nodetable[nodenum].weight = nodetable[i].weight+1;
01203                                         nodetable[nodenum].neighbornum = i;
01204                                         if ((nodetable[i].origin[2] - nodetable[nodenum].origin[2]) > 50)
01205                                         { //if there's a big drop, make sure we know we can't just magically fly back up
01206                                                 nodetable[nodenum].flags = WPFLAG_ONEWAY_FWD;
01207                                         }
01208                                         nodenum++;
01209                                         cancontinue = 1;
01210                                 }
01211 
01212                                 if (nodenum >= MAX_NODETABLE_SIZE)
01213                                 {
01214                                         break; //failure
01215                                 }
01216 
01217                                 VectorCopy(nodetable[i].origin, testspot);
01218                                 testspot[1] += branchDistance;
01219 
01220                                 VectorCopy(testspot, starttrace);
01221 
01222                                 starttrace[2] -= 4096;
01223 
01224                                 trap_Trace(&tr, testspot, NULL, NULL, starttrace, ENTITYNUM_NONE, MASK_SOLID);
01225 
01226                                 testspot[2] = tr.endpos[2]+baseheight;
01227 
01228                                 if (!NodeHere(testspot) && !tr.startsolid && !tr.allsolid && CanGetToVector(nodetable[i].origin, testspot, mins, maxs))
01229                                 {
01230                                         VectorCopy(testspot, nodetable[nodenum].origin);
01231                                         nodetable[nodenum].inuse = 1;
01232 //                                      nodetable[nodenum].index = nodenum;
01233                                         nodetable[nodenum].weight = nodetable[i].weight+1;
01234                                         nodetable[nodenum].neighbornum = i;
01235                                         if ((nodetable[i].origin[2] - nodetable[nodenum].origin[2]) > 50)
01236                                         { //if there's a big drop, make sure we know we can't just magically fly back up
01237                                                 nodetable[nodenum].flags = WPFLAG_ONEWAY_FWD;
01238                                         }
01239                                         nodenum++;
01240                                         cancontinue = 1;
01241                                 }
01242 
01243                                 if (nodenum >= MAX_NODETABLE_SIZE)
01244                                 {
01245                                         break; //failure
01246                                 }
01247 
01248                                 VectorCopy(nodetable[i].origin, testspot);
01249                                 testspot[1] -= branchDistance;
01250 
01251                                 VectorCopy(testspot, starttrace);
01252 
01253                                 starttrace[2] -= 4096;
01254 
01255                                 trap_Trace(&tr, testspot, NULL, NULL, starttrace, ENTITYNUM_NONE, MASK_SOLID);
01256 
01257                                 testspot[2] = tr.endpos[2]+baseheight;
01258 
01259                                 if (!NodeHere(testspot) && !tr.startsolid && !tr.allsolid && CanGetToVector(nodetable[i].origin, testspot, mins, maxs))
01260                                 {
01261                                         VectorCopy(testspot, nodetable[nodenum].origin);
01262                                         nodetable[nodenum].inuse = 1;
01263 //                                      nodetable[nodenum].index = nodenum;
01264                                         nodetable[nodenum].weight = nodetable[i].weight+1;
01265                                         nodetable[nodenum].neighbornum = i;
01266                                         if ((nodetable[i].origin[2] - nodetable[nodenum].origin[2]) > 50)
01267                                         { //if there's a big drop, make sure we know we can't just magically fly back up
01268                                                 nodetable[nodenum].flags = WPFLAG_ONEWAY_FWD;
01269                                         }
01270                                         nodenum++;
01271                                         cancontinue = 1;
01272                                 }
01273 
01274                                 if (nodenum >= MAX_NODETABLE_SIZE)
01275                                 {
01276                                         break; //failure
01277                                 }
01278 
01279                                 extendednodes[i] = 1;
01280                         }
01281 
01282                         i++;
01283                 }
01284         }
01285 
01286         if (!foundit)
01287         {
01288 #ifndef _DEBUG //if debug just always print this.
01289                 if (!behindTheScenes)
01290 #endif
01291                 {
01292                         G_Printf(S_COLOR_RED "Could not link %i to %i, unreachable by node branching.\n", startindex, endindex);
01293                 }
01294                 gWPArray[startindex]->flags |= WPFLAG_ONEWAY_FWD;
01295                 gWPArray[endindex]->flags |= WPFLAG_ONEWAY_BACK;
01296                 if (!behindTheScenes)
01297                 {
01298                         G_Printf(S_COLOR_YELLOW "Since points cannot be connected, point %i has been flagged as only-forward and point %i has been flagged as only-backward.\n", startindex, endindex);
01299                 }
01300 
01301                 /*while (nodenum >= 0)
01302                 {
01303                         if (nodetable[nodenum].origin[0] || nodetable[nodenum].origin[1] || nodetable[nodenum].origin[2])
01304                         {
01305                                 CreateNewWP(nodetable[nodenum].origin, nodetable[nodenum].flags);
01306                         }
01307 
01308                         nodenum--;
01309                 }*/
01310                 //The above code transfers nodes into the "rendered" waypoint array. Strictly for debugging.
01311 
01312                 if (!behindTheScenes)
01313                 { //just use what we have if we're auto-pathing the level
01314                         return 0;
01315                 }
01316                 else
01317                 {
01318                         vec3_t endDist;
01319                         int nCount = 0;
01320                         int idealNode = -1;
01321                         float bestDist = 0;
01322                         float testDist;
01323 
01324                         if (nodenum <= 10)
01325                         { //not enough to even really bother.
01326                                 return 0;
01327                         }
01328 
01329                         //Since it failed, find whichever node is closest to the desired end.
01330                         while (nCount < nodenum)
01331                         {
01332                                 VectorSubtract(nodetable[nCount].origin, gWPArray[endindex]->origin, endDist);
01333                                 testDist = VectorLength(endDist);
01334                                 if (idealNode == -1)
01335                                 {
01336                                         idealNode = nCount;
01337                                         bestDist = testDist;
01338                                         nCount++;
01339                                         continue;
01340                                 }
01341 
01342                                 if (testDist < bestDist)
01343                                 {
01344                                         idealNode = nCount;
01345                                         bestDist = testDist;
01346                                 }
01347 
01348                                 nCount++;
01349                         }
01350 
01351                         if (idealNode == -1)
01352                         {
01353                                 return 0;
01354                         }
01355 
01356                         successnodeindex = idealNode;
01357                 }
01358         }
01359 
01360         i = successnodeindex;
01361         insertindex = startindex;
01362         failsafe = 0;
01363         VectorCopy(gWPArray[startindex]->origin, validspotpos);
01364 
01365         while (failsafe < MAX_NODETABLE_SIZE && i < MAX_NODETABLE_SIZE && i >= 0)
01366         {
01367                 VectorSubtract(validspotpos, nodetable[i].origin, a);
01368                 if (!nodetable[nodetable[i].neighbornum].inuse || !CanGetToVectorTravel(validspotpos, /*nodetable[nodetable[i].neighbornum].origin*/nodetable[i].origin, mins, maxs) || VectorLength(a) > maxDistFactor || (!CanGetToVectorTravel(validspotpos, gWPArray[endindex]->origin, mins, maxs) && CanGetToVectorTravel(nodetable[i].origin, gWPArray[endindex]->origin, mins, maxs)) )
01369                 {
01370                         nodetable[i].flags |= WPFLAG_CALCULATED;
01371                         if (!CreateNewWP_InTrail(nodetable[i].origin, nodetable[i].flags, insertindex))
01372                         {
01373                                 if (!behindTheScenes)
01374                                 {
01375                                         G_Printf(S_COLOR_RED "Could not link %i to %i, waypoint limit hit.\n", startindex, endindex);
01376                                 }
01377                                 return 0;
01378                         }
01379 
01380                         VectorCopy(nodetable[i].origin, validspotpos);
01381                 }
01382 
01383                 if (i == 0)
01384                 {
01385                         break;
01386                 }
01387 
01388                 i = nodetable[i].neighbornum;
01389 
01390                 failsafe++;
01391         }
01392 
01393         if (!behindTheScenes)
01394         {
01395                 G_Printf(S_COLOR_YELLOW "Finished connecting %i to %i.\n", startindex, endindex);
01396         }
01397 
01398         return 1;
01399 }
01400 #endif
01401 
01402 int OpposingEnds(int start, int end)
01403 {
01404         if (!gWPArray[start] || !gWPArray[start]->inuse || !gWPArray[end] || !gWPArray[end]->inuse)
01405         {
01406                 return 0;
01407         }
01408 
01409         if ((gWPArray[start]->flags & WPFLAG_ONEWAY_FWD) &&
01410                 (gWPArray[end]->flags & WPFLAG_ONEWAY_BACK))
01411         {
01412                 return 1;
01413         }
01414 
01415         return 0;
01416 }
01417 
01418 int DoorBlockingSection(int start, int end)
01419 { //if a door blocks the trail, we'll just have to assume the points on each side are in visibility when it's open
01420         trace_t tr;
01421         gentity_t *testdoor;
01422         int start_trace_index;
01423 
01424         if (!gWPArray[start] || !gWPArray[start]->inuse || !gWPArray[end] || !gWPArray[end]->inuse)
01425         {
01426                 return 0;
01427         }
01428 
01429         trap_Trace(&tr, gWPArray[start]->origin, NULL, NULL, gWPArray[end]->origin, ENTITYNUM_NONE, MASK_SOLID);
01430 
01431         if (tr.fraction == 1)
01432         {
01433                 return 0;
01434         }
01435 
01436         testdoor = &g_entities[tr.entityNum];
01437 
01438         if (!testdoor)
01439         {
01440                 return 0;
01441         }
01442 
01443         if (!strstr(testdoor->classname, "func_"))
01444         {
01445                 return 0;
01446         }
01447 
01448         start_trace_index = tr.entityNum;
01449 
01450         trap_Trace(&tr, gWPArray[end]->origin, NULL, NULL, gWPArray[start]->origin, ENTITYNUM_NONE, MASK_SOLID);
01451 
01452         if (tr.fraction == 1)
01453         {
01454                 return 0;
01455         }
01456 
01457         if (start_trace_index == tr.entityNum)
01458         {
01459                 return 1;
01460         }
01461 
01462         return 0;
01463 }
01464 
01465 #ifndef _XBOX
01466 int RepairPaths(qboolean behindTheScenes)
01467 {
01468         int i;
01469         int preAmount = 0;
01470         int ctRet;
01471         vec3_t a;
01472         float maxDistFactor = 400;
01473 
01474         if (!gWPNum)
01475         {
01476                 return 0;
01477         }
01478 
01479         if (g_RMG.integer)
01480         {
01481                 maxDistFactor = 800; //higher tolerance here.
01482         }
01483 
01484         i = 0;
01485 
01486         preAmount = gWPNum;
01487 
01488         trap_Cvar_Update(&bot_wp_distconnect);
01489         trap_Cvar_Update(&bot_wp_visconnect);
01490 
01491         while (i < gWPNum)
01492         {
01493                 if (gWPArray[i] && gWPArray[i]->inuse && gWPArray[i+1] && gWPArray[i+1]->inuse)
01494                 {
01495                         VectorSubtract(gWPArray[i]->origin, gWPArray[i+1]->origin, a);
01496 
01497                         if (!(gWPArray[i+1]->flags & WPFLAG_NOVIS) &&
01498                                 !(gWPArray[i+1]->flags & WPFLAG_JUMP) && //don't calculate on jump points because they might not always want to be visible (in cases of force jumping)
01499                                 !(gWPArray[i]->flags & WPFLAG_CALCULATED) && //don't calculate it again
01500                                 !OpposingEnds(i, i+1) &&
01501                                 ((bot_wp_distconnect.value && VectorLength(a) > maxDistFactor) || (!OrgVisible(gWPArray[i]->origin, gWPArray[i+1]->origin, ENTITYNUM_NONE) && bot_wp_visconnect.value) ) &&
01502                                 !DoorBlockingSection(i, i+1))
01503                         {
01504                                 ctRet = ConnectTrail(i, i+1, behindTheScenes);
01505 
01506                                 if (gWPNum >= MAX_WPARRAY_SIZE)
01507                                 { //Bad!
01508                                         gWPNum = MAX_WPARRAY_SIZE;
01509                                         break;
01510                                 }
01511 
01512                                 /*if (!ctRet)
01513                                 {
01514                                         return 0;
01515                                 }*/ //we still want to write it..
01516                         }
01517                 }
01518 
01519                 i++;
01520         }
01521 
01522         return 1;
01523 }
01524 #endif
01525 
01526 int OrgVisibleCurve(vec3_t org1, vec3_t mins, vec3_t maxs, vec3_t org2, int ignore)
01527 {
01528         trace_t tr;
01529         vec3_t evenorg1;
01530 
01531         VectorCopy(org1, evenorg1);
01532         evenorg1[2] = org2[2];
01533 
01534         trap_Trace(&tr, evenorg1, mins, maxs, org2, ignore, MASK_SOLID);
01535 
01536         if (tr.fraction == 1 && !tr.startsolid && !tr.allsolid)
01537         {
01538                 trap_Trace(&tr, evenorg1, mins, maxs, org1, ignore, MASK_SOLID);
01539 
01540                 if (tr.fraction == 1 && !tr.startsolid && !tr.allsolid)
01541                 {
01542                         return 1;
01543                 }
01544         }
01545 
01546         return 0;
01547 }
01548 
01549 int CanForceJumpTo(int baseindex, int testingindex, float distance)
01550 {
01551         float heightdif;
01552         vec3_t xy_base, xy_test, v, mins, maxs;
01553         wpobject_t *wpBase = gWPArray[baseindex];
01554         wpobject_t *wpTest = gWPArray[testingindex];
01555 
01556         mins[0] = -15;
01557         mins[1] = -15;
01558         mins[2] = -15; //-1
01559         maxs[0] = 15;
01560         maxs[1] = 15;
01561         maxs[2] = 15; //1
01562 
01563         if (!wpBase || !wpBase->inuse || !wpTest || !wpTest->inuse)
01564         {
01565                 return 0;
01566         }
01567 
01568         if (distance > 400)
01569         {
01570                 return 0;
01571         }
01572 
01573         VectorCopy(wpBase->origin, xy_base);
01574         VectorCopy(wpTest->origin, xy_test);
01575 
01576         xy_base[2] = xy_test[2];
01577 
01578         VectorSubtract(xy_base, xy_test, v);
01579 
01580         if (VectorLength(v) > MAX_NEIGHBOR_LINK_DISTANCE)
01581         {
01582                 return 0;
01583         }
01584 
01585         if ((int)wpBase->origin[2] < (int)wpTest->origin[2])
01586         {
01587                 heightdif = wpTest->origin[2] - wpBase->origin[2];
01588         }
01589         else
01590         {
01591                 return 0; //err..
01592         }
01593 
01594         if (heightdif < 128)
01595         { //don't bother..
01596                 return 0;
01597         }
01598 
01599         if (heightdif > 512)
01600         { //too high
01601                 return 0;
01602         }
01603 
01604         if (!OrgVisibleCurve(wpBase->origin, mins, maxs, wpTest->origin, ENTITYNUM_NONE))
01605         {
01606                 return 0;
01607         }
01608 
01609         if (heightdif > 400)
01610         {
01611                 return 3;
01612         }
01613         else if (heightdif > 256)
01614         {
01615                 return 2;
01616         }
01617         else
01618         {
01619                 return 1;
01620         }
01621 }
01622 
01623 void CalculatePaths(void)
01624 {
01625         int i;
01626         int c;
01627         int forceJumpable;
01628         int maxNeighborDist = MAX_NEIGHBOR_LINK_DISTANCE;
01629         float nLDist;
01630         vec3_t a;
01631         vec3_t mins, maxs;
01632 
01633         if (!gWPNum)
01634         {
01635                 return;
01636         }
01637 
01638         if (g_RMG.integer)
01639         {
01640                 maxNeighborDist = DEFAULT_GRID_SPACING + (DEFAULT_GRID_SPACING*0.5);
01641         }
01642 
01643         mins[0] = -15;
01644         mins[1] = -15;
01645         mins[2] = -15; //-1
01646         maxs[0] = 15;
01647         maxs[1] = 15;
01648         maxs[2] = 15; //1
01649 
01650         //now clear out all the neighbor data before we recalculate
01651         i = 0;
01652 
01653         while (i < gWPNum)
01654         {
01655                 if (gWPArray[i] && gWPArray[i]->inuse && gWPArray[i]->neighbornum)
01656                 {
01657                         while (gWPArray[i]->neighbornum >= 0)
01658                         {
01659                                 gWPArray[i]->neighbors[gWPArray[i]->neighbornum].num = 0;
01660                                 gWPArray[i]->neighbors[gWPArray[i]->neighbornum].forceJumpTo = 0;
01661                                 gWPArray[i]->neighbornum--;
01662                         }
01663                         gWPArray[i]->neighbornum = 0;
01664                 }
01665 
01666                 i++;
01667         }
01668 
01669         i = 0;
01670 
01671         while (i < gWPNum)
01672         {
01673                 if (gWPArray[i] && gWPArray[i]->inuse)
01674                 {
01675                         c = 0;
01676 
01677                         while (c < gWPNum)
01678                         {
01679                                 if (gWPArray[c] && gWPArray[c]->inuse && i != c &&
01680                                         NotWithinRange(i, c))
01681                                 {
01682                                         VectorSubtract(gWPArray[i]->origin, gWPArray[c]->origin, a);
01683 
01684                                         nLDist = VectorLength(a);
01685                                         forceJumpable = CanForceJumpTo(i, c, nLDist);
01686 
01687                                         if ((nLDist < maxNeighborDist || forceJumpable) &&
01688                                                 ((int)gWPArray[i]->origin[2] == (int)gWPArray[c]->origin[2] || forceJumpable) &&
01689                                                 (OrgVisibleBox(gWPArray[i]->origin, mins, maxs, gWPArray[c]->origin, ENTITYNUM_NONE) || forceJumpable))
01690                                         {
01691                                                 gWPArray[i]->neighbors[gWPArray[i]->neighbornum].num = c;
01692                                                 if (forceJumpable && ((int)gWPArray[i]->origin[2] != (int)gWPArray[c]->origin[2] || nLDist < maxNeighborDist))
01693                                                 {
01694                                                         gWPArray[i]->neighbors[gWPArray[i]->neighbornum].forceJumpTo = 999;//forceJumpable; //FJSR
01695                                                 }
01696                                                 else
01697                                                 {
01698                                                         gWPArray[i]->neighbors[gWPArray[i]->neighbornum].forceJumpTo = 0;
01699                                                 }
01700                                                 gWPArray[i]->neighbornum++;
01701                                         }
01702 
01703                                         if (gWPArray[i]->neighbornum >= MAX_NEIGHBOR_SIZE)
01704                                         {
01705                                                 break;
01706                                         }
01707                                 }
01708                                 c++;
01709                         }
01710                 }
01711                 i++;
01712         }
01713 }
01714 
01715 gentity_t *GetObjectThatTargets(gentity_t *ent)
01716 {
01717         gentity_t *next = NULL;
01718 
01719         if (!ent->targetname)
01720         {
01721                 return NULL;
01722         }
01723 
01724         next = G_Find( next, FOFS(target), ent->targetname );
01725 
01726         if (next)
01727         {
01728                 return next;
01729         }
01730 
01731         return NULL;
01732 }
01733 
01734 void CalculateSiegeGoals(void)
01735 {
01736         int i = 0;
01737         int looptracker = 0;
01738         int wpindex = 0;
01739         vec3_t dif;
01740         gentity_t *ent;
01741         gentity_t *tent = NULL, *t2ent = NULL;
01742 
01743         while (i < level.num_entities)
01744         {
01745                 ent = &g_entities[i];
01746 
01747                 tent = NULL;
01748 
01749                 if (ent && ent->classname && strcmp(ent->classname, "info_siege_objective") == 0)
01750                 {
01751                         tent = ent;
01752                         t2ent = GetObjectThatTargets(tent);
01753                         looptracker = 0;
01754 
01755                         while (t2ent && looptracker < 2048)
01756                         { //looptracker keeps us from getting stuck in case something is set up weird on this map
01757                                 tent = t2ent;
01758                                 t2ent = GetObjectThatTargets(tent);
01759                                 looptracker++;
01760                         }
01761 
01762                         if (looptracker >= 2048)
01763                         { //something unpleasent has happened
01764                                 tent = NULL;
01765                                 break;
01766                         }
01767                 }
01768 
01769                 if (tent && ent && tent != ent)
01770                 { //tent should now be the object attached to the mission objective
01771                         dif[0] = (tent->r.absmax[0]+tent->r.absmin[0])/2;
01772                         dif[1] = (tent->r.absmax[1]+tent->r.absmin[1])/2;
01773                         dif[2] = (tent->r.absmax[2]+tent->r.absmin[2])/2;
01774 
01775                         wpindex = GetNearestVisibleWP(dif, tent->s.number);
01776 
01777                         if (wpindex != -1 && gWPArray[wpindex] && gWPArray[wpindex]->inuse)
01778                         { //found the waypoint nearest the center of this objective-related object
01779                                 if (ent->side == SIEGETEAM_TEAM1)
01780                                 {
01781                                         gWPArray[wpindex]->flags |= WPFLAG_SIEGE_IMPERIALOBJ;
01782                                 }
01783                                 else
01784                                 {
01785                                         gWPArray[wpindex]->flags |= WPFLAG_SIEGE_REBELOBJ;
01786                                 }
01787 
01788                                 gWPArray[wpindex]->associated_entity = tent->s.number;
01789                         }
01790                 }
01791 
01792                 i++;
01793         }
01794 }
01795 
01796 float botGlobalNavWeaponWeights[WP_NUM_WEAPONS] =
01797 {
01798         0,//WP_NONE,
01799 
01800         0,//WP_STUN_BATON,
01801         0,//WP_MELEE
01802         0,//WP_SABER,                            // NOTE: lots of code assumes this is the first weapon (... which is crap) so be careful -Ste.
01803         0,//WP_BRYAR_PISTOL,
01804         3,//WP_BLASTER,
01805         5,//WP_DISRUPTOR,
01806         4,//WP_BOWCASTER,
01807         6,//WP_REPEATER,
01808         7,//WP_DEMP2,
01809         8,//WP_FLECHETTE,
01810         9,//WP_ROCKET_LAUNCHER,
01811         3,//WP_THERMAL,
01812         3,//WP_TRIP_MINE,
01813         3,//WP_DET_PACK,
01814         0//WP_EMPLACED_GUN,
01815 };
01816 
01817 int GetNearestVisibleWPToItem(vec3_t org, int ignore)
01818 {
01819         int i;
01820         float bestdist;
01821         float flLen;
01822         int bestindex;
01823         vec3_t a, mins, maxs;
01824 
01825         i = 0;
01826         bestdist = 64; //has to be less than 64 units to the item or it isn't safe enough
01827         bestindex = -1;
01828 
01829         mins[0] = -15;
01830         mins[1] = -15;
01831         mins[2] = 0;
01832         maxs[0] = 15;
01833         maxs[1] = 15;
01834         maxs[2] = 0;
01835 
01836         while (i < gWPNum)
01837         {
01838                 if (gWPArray[i] && gWPArray[i]->inuse &&
01839                         gWPArray[i]->origin[2]-15 < org[2] &&
01840                         gWPArray[i]->origin[2]+15 > org[2])
01841                 {
01842                         VectorSubtract(org, gWPArray[i]->origin, a);
01843                         flLen = VectorLength(a);
01844 
01845                         if (flLen < bestdist && trap_InPVS(org, gWPArray[i]->origin) && OrgVisibleBox(org, mins, maxs, gWPArray[i]->origin, ignore))
01846                         {
01847                                 bestdist = flLen;
01848                                 bestindex = i;
01849                         }
01850                 }
01851 
01852                 i++;
01853         }
01854 
01855         return bestindex;
01856 }
01857 
01858 void CalculateWeightGoals(void)
01859 { //set waypoint weights depending on weapon and item placement
01860         int i = 0;
01861         int wpindex = 0;
01862         gentity_t *ent;
01863         float weight;
01864 
01865         trap_Cvar_Update(&bot_wp_clearweight);
01866 
01867         if (bot_wp_clearweight.integer)
01868         { //if set then flush out all weight/goal values before calculating them again
01869                 while (i < gWPNum)
01870                 {
01871                         if (gWPArray[i] && gWPArray[i]->inuse)
01872                         {
01873                                 gWPArray[i]->weight = 0;
01874 
01875                                 if (gWPArray[i]->flags & WPFLAG_GOALPOINT)
01876                                 {
01877                                         gWPArray[i]->flags -= WPFLAG_GOALPOINT;
01878                                 }
01879                         }
01880 
01881                         i++;
01882                 }
01883         }
01884 
01885         i = 0;
01886 
01887         while (i < level.num_entities)
01888         {
01889                 ent = &g_entities[i];
01890 
01891                 weight = 0;
01892 
01893                 if (ent && ent->classname)
01894                 {
01895                         if (strcmp(ent->classname, "item_seeker") == 0)
01896                         {
01897                                 weight = 2;
01898                         }
01899                         else if (strcmp(ent->classname, "item_shield") == 0)
01900                         {
01901                                 weight = 2;
01902                         }
01903                         else if (strcmp(ent->classname, "item_medpac") == 0)
01904                         {
01905                                 weight = 2;
01906                         }
01907                         else if (strcmp(ent->classname, "item_sentry_gun") == 0)
01908                         {
01909                                 weight = 2;
01910                         }
01911                         else if (strcmp(ent->classname, "item_force_enlighten_dark") == 0)
01912                         {
01913                                 weight = 5;
01914                         }
01915                         else if (strcmp(ent->classname, "item_force_enlighten_light") == 0)
01916                         {
01917                                 weight = 5;
01918                         }
01919                         else if (strcmp(ent->classname, "item_force_boon") == 0)
01920                         {
01921                                 weight = 5;
01922                         }
01923                         else if (strcmp(ent->classname, "item_ysalimari") == 0)
01924                         {
01925                                 weight = 2;
01926                         }
01927                         else if (strstr(ent->classname, "weapon_") && ent->item)
01928                         {
01929                                 weight = botGlobalNavWeaponWeights[ent->item->giTag];
01930                         }
01931                         else if (ent->item && ent->item->giType == IT_AMMO)
01932                         {
01933                                 weight = 3;
01934                         }
01935                 }
01936 
01937                 if (ent && weight)
01938                 {
01939                         wpindex = GetNearestVisibleWPToItem(ent->s.pos.trBase, ent->s.number);
01940 
01941                         if (wpindex != -1 && gWPArray[wpindex] && gWPArray[wpindex]->inuse)
01942                         { //found the waypoint nearest the center of this object
01943                                 gWPArray[wpindex]->weight = weight;
01944                                 gWPArray[wpindex]->flags |= WPFLAG_GOALPOINT;
01945                                 gWPArray[wpindex]->associated_entity = ent->s.number;
01946                         }
01947                 }
01948 
01949                 i++;
01950         }
01951 }
01952 
01953 void CalculateJumpRoutes(void)
01954 {
01955         int i = 0;
01956         float nheightdif = 0;
01957         float pheightdif = 0;
01958 
01959         while (i < gWPNum)
01960         {
01961                 if (gWPArray[i] && gWPArray[i]->inuse)
01962                 {
01963                         if (gWPArray[i]->flags & WPFLAG_JUMP)
01964                         {
01965                                 nheightdif = 0;
01966                                 pheightdif = 0;
01967 
01968                                 gWPArray[i]->forceJumpTo = 0;
01969 
01970                                 if (gWPArray[i-1] && gWPArray[i-1]->inuse && (gWPArray[i-1]->origin[2]+16) < gWPArray[i]->origin[2])
01971                                 {
01972                                         nheightdif = (gWPArray[i]->origin[2] - gWPArray[i-1]->origin[2]);
01973                                 }
01974 
01975                                 if (gWPArray[i+1] && gWPArray[i+1]->inuse && (gWPArray[i+1]->origin[2]+16) < gWPArray[i]->origin[2])
01976                                 {
01977                                         pheightdif = (gWPArray[i]->origin[2] - gWPArray[i+1]->origin[2]);
01978                                 }
01979 
01980                                 if (nheightdif > pheightdif)
01981                                 {
01982                                         pheightdif = nheightdif;
01983                                 }
01984 
01985                                 if (pheightdif)
01986                                 {
01987                                         if (pheightdif > 500)
01988                                         {
01989                                                 gWPArray[i]->forceJumpTo = 999; //FORCE_LEVEL_3; //FJSR
01990                                         }
01991                                         else if (pheightdif > 256)
01992                                         {
01993                                                 gWPArray[i]->forceJumpTo = 999; //FORCE_LEVEL_2; //FJSR
01994                                         }
01995                                         else if (pheightdif > 128)
01996                                         {
01997                                                 gWPArray[i]->forceJumpTo = 999; //FORCE_LEVEL_1; //FJSR
01998                                         }
01999                                 }
02000                         }
02001                 }
02002 
02003                 i++;
02004         }
02005 }
02006 
02007 int LoadPathData(const char *filename)
02008 {
02009         fileHandle_t f;
02010         char *fileString;
02011         char *currentVar;
02012         char *routePath;
02013         wpobject_t thiswp;
02014         int len;
02015         int i, i_cv;
02016         int nei_num;
02017 
02018         i = 0;
02019         i_cv = 0;
02020 
02021         routePath = (char *)B_TempAlloc(1024);
02022 
02023         Com_sprintf(routePath, 1024, "botroutes/%s.wnt\0", filename);
02024 
02025         len = trap_FS_FOpenFile(routePath, &f, FS_READ);
02026 
02027         B_TempFree(1024); //routePath
02028 
02029         if (!f)
02030         {
02031                 G_Printf(S_COLOR_YELLOW "Bot route data not found for %s\n", filename);
02032                 return 2;
02033         }
02034 
02035         if (len >= 524288)
02036         {
02037                 G_Printf(S_COLOR_RED "Route file exceeds maximum length\n");
02038                 return 0;
02039         }
02040 
02041         fileString = (char *)B_TempAlloc(524288);
02042         currentVar = (char *)B_TempAlloc(2048);
02043 
02044         trap_FS_Read(fileString, len, f);
02045 
02046         if (fileString[i] == 'l')
02047         { //contains a "levelflags" entry..
02048                 char readLFlags[64];
02049                 i_cv = 0;
02050 
02051                 while (fileString[i] != ' ')
02052                 {
02053                         i++;
02054                 }
02055                 i++;
02056                 while (fileString[i] != '\n')
02057                 {
02058                         readLFlags[i_cv] = fileString[i];
02059                         i_cv++;
02060                         i++;
02061                 }
02062                 readLFlags[i_cv] = 0;
02063                 i++;
02064 
02065                 gLevelFlags = atoi(readLFlags);
02066         }
02067         else
02068         {
02069                 gLevelFlags = 0;
02070         }
02071 
02072         while (i < len)
02073         {
02074                 i_cv = 0;
02075 
02076                 thiswp.index = 0;
02077                 thiswp.flags = 0;
02078                 thiswp.inuse = 0;
02079                 thiswp.neighbornum = 0;
02080                 thiswp.origin[0] = 0;
02081                 thiswp.origin[1] = 0;
02082                 thiswp.origin[2] = 0;
02083                 thiswp.weight = 0;
02084                 thiswp.associated_entity = ENTITYNUM_NONE;
02085                 thiswp.forceJumpTo = 0;
02086                 thiswp.disttonext = 0;
02087                 nei_num = 0;
02088 
02089                 while (nei_num < MAX_NEIGHBOR_SIZE)
02090                 {
02091                         thiswp.neighbors[nei_num].num = 0;
02092                         thiswp.neighbors[nei_num].forceJumpTo = 0;
02093 
02094                         nei_num++;
02095                 }
02096                 
02097                 while (fileString[i] != ' ')
02098                 {
02099                         currentVar[i_cv] = fileString[i];
02100                         i_cv++;
02101                         i++;
02102                 }
02103                 currentVar[i_cv] = '\0';
02104 
02105                 thiswp.index = atoi(currentVar);
02106 
02107                 i_cv = 0;
02108                 i++;
02109 
02110                 while (fileString[i] != ' ')
02111                 {
02112                         currentVar[i_cv] = fileString[i];
02113                         i_cv++;
02114                         i++;
02115                 }
02116                 currentVar[i_cv] = '\0';
02117 
02118                 thiswp.flags = atoi(currentVar);
02119 
02120                 i_cv = 0;
02121                 i++;
02122 
02123                 while (fileString[i] != ' ')
02124                 {
02125                         currentVar[i_cv] = fileString[i];
02126                         i_cv++;
02127                         i++;
02128                 }
02129                 currentVar[i_cv] = '\0';
02130 
02131                 thiswp.weight = atof(currentVar);
02132 
02133                 i_cv = 0;
02134                 i++;
02135                 i++;
02136 
02137                 while (fileString[i] != ' ')
02138                 {
02139                         currentVar[i_cv] = fileString[i];
02140                         i_cv++;
02141                         i++;
02142                 }
02143                 currentVar[i_cv] = '\0';
02144 
02145                 thiswp.origin[0] = atof(currentVar);
02146 
02147                 i_cv = 0;
02148                 i++;
02149 
02150                 while (fileString[i] != ' ')
02151                 {
02152                         currentVar[i_cv] = fileString[i];
02153                         i_cv++;
02154                         i++;
02155                 }
02156                 currentVar[i_cv] = '\0';
02157 
02158                 thiswp.origin[1] = atof(currentVar);
02159 
02160                 i_cv = 0;
02161                 i++;
02162 
02163                 while (fileString[i] != ')')
02164                 {
02165                         currentVar[i_cv] = fileString[i];
02166                         i_cv++;
02167                         i++;
02168                 }
02169                 currentVar[i_cv] = '\0';
02170 
02171                 thiswp.origin[2] = atof(currentVar);
02172 
02173                 i += 4;
02174 
02175                 while (fileString[i] != '}')
02176                 {
02177                         i_cv = 0;
02178                         while (fileString[i] != ' ' && fileString[i] != '-')
02179                         {
02180                                 currentVar[i_cv] = fileString[i];
02181                                 i_cv++;
02182                                 i++;
02183                         }
02184                         currentVar[i_cv] = '\0';
02185 
02186                         thiswp.neighbors[thiswp.neighbornum].num = atoi(currentVar);
02187 
02188                         if (fileString[i] == '-')
02189                         {
02190                                 i_cv = 0;
02191                                 i++;
02192 
02193                                 while (fileString[i] != ' ')
02194                                 {
02195                                         currentVar[i_cv] = fileString[i];
02196                                         i_cv++;
02197                                         i++;
02198                                 }
02199                                 currentVar[i_cv] = '\0';
02200 
02201                                 thiswp.neighbors[thiswp.neighbornum].forceJumpTo = 999; //atoi(currentVar); //FJSR
02202                         }
02203                         else
02204                         {
02205                                 thiswp.neighbors[thiswp.neighbornum].forceJumpTo = 0;
02206                         }
02207 
02208                         thiswp.neighbornum++;
02209 
02210                         i++;
02211                 }
02212 
02213                 i_cv = 0;
02214                 i++;
02215                 i++;
02216 
02217                 while (fileString[i] != '\n')
02218                 {
02219                         currentVar[i_cv] = fileString[i];
02220                         i_cv++;
02221                         i++;
02222                 }
02223                 currentVar[i_cv] = '\0';
02224 
02225                 thiswp.disttonext = atof(currentVar);
02226 
02227                 CreateNewWP_FromObject(&thiswp);
02228                 i++;
02229         }
02230 
02231         B_TempFree(524288); //fileString
02232         B_TempFree(2048); //currentVar
02233 
02234         trap_FS_FCloseFile(f);
02235 
02236         if (g_gametype.integer == GT_SIEGE)
02237         {
02238                 CalculateSiegeGoals();
02239         }
02240 
02241         CalculateWeightGoals();
02242         //calculate weights for idle activity goals when
02243         //the bot has absolutely nothing else to do
02244 
02245         CalculateJumpRoutes();
02246         //Look at jump points and mark them as requiring
02247         //force jumping as needed
02248 
02249         return 1;
02250 }
02251 
02252 void FlagObjects(void)
02253 {
02254         int i = 0, bestindex = 0, found = 0;
02255         float bestdist = 999999, tlen = 0;
02256         gentity_t *flag_red, *flag_blue, *ent;
02257         vec3_t a, mins, maxs;
02258         trace_t tr;
02259 
02260         flag_red = NULL;
02261         flag_blue = NULL;
02262 
02263         mins[0] = -15;
02264         mins[1] = -15;
02265         mins[2] = -5;
02266         maxs[0] = 15;
02267         maxs[1] = 15;
02268         maxs[2] = 5;
02269 
02270         while (i < level.num_entities)
02271         {
02272                 ent = &g_entities[i];
02273 
02274                 if (ent && ent->inuse && ent->classname)
02275                 {
02276                         if (!flag_red && strcmp(ent->classname, "team_CTF_redflag") == 0)
02277                         {
02278                                 flag_red = ent;
02279                         }
02280                         else if (!flag_blue && strcmp(ent->classname, "team_CTF_blueflag") == 0)
02281                         {
02282                                 flag_blue = ent;
02283                         }
02284 
02285                         if (flag_red && flag_blue)
02286                         {
02287                                 break;
02288                         }
02289                 }
02290 
02291                 i++;
02292         }
02293 
02294         i = 0;
02295 
02296         if (!flag_red || !flag_blue)
02297         {
02298                 return;
02299         }
02300 
02301         while (i < gWPNum)
02302         {
02303                 if (gWPArray[i] && gWPArray[i]->inuse)
02304                 {
02305                         VectorSubtract(flag_red->s.pos.trBase, gWPArray[i]->origin, a);
02306                         tlen = VectorLength(a);
02307 
02308                         if (tlen < bestdist)
02309                         {
02310                                 trap_Trace(&tr, flag_red->s.pos.trBase, mins, maxs, gWPArray[i]->origin, flag_red->s.number, MASK_SOLID);
02311 
02312                                 if (tr.fraction == 1 || tr.entityNum == flag_red->s.number)
02313                                 {
02314                                         bestdist = tlen;
02315                                         bestindex = i;
02316                                         found = 1;
02317                                 }
02318                         }
02319 
02320                 }
02321 
02322                 i++;
02323         }
02324 
02325         if (found)
02326         {
02327                 gWPArray[bestindex]->flags |= WPFLAG_RED_FLAG;
02328                 flagRed = gWPArray[bestindex];
02329                 oFlagRed = flagRed;
02330                 eFlagRed = flag_red;
02331         }
02332 
02333         bestdist = 999999;
02334         bestindex = 0;
02335         found = 0;
02336         i = 0;
02337 
02338         while (i < gWPNum)
02339         {
02340                 if (gWPArray[i] && gWPArray[i]->inuse)
02341                 {
02342                         VectorSubtract(flag_blue->s.pos.trBase, gWPArray[i]->origin, a);
02343                         tlen = VectorLength(a);
02344 
02345                         if (tlen < bestdist)
02346                         {
02347                                 trap_Trace(&tr, flag_blue->s.pos.trBase, mins, maxs, gWPArray[i]->origin, flag_blue->s.number, MASK_SOLID);
02348 
02349                                 if (tr.fraction == 1 || tr.entityNum == flag_blue->s.number)
02350                                 {
02351                                         bestdist = tlen;
02352                                         bestindex = i;
02353                                         found = 1;
02354                                 }
02355                         }
02356 
02357                 }
02358 
02359                 i++;
02360         }
02361 
02362         if (found)
02363         {
02364                 gWPArray[bestindex]->flags |= WPFLAG_BLUE_FLAG;
02365                 flagBlue = gWPArray[bestindex];
02366                 oFlagBlue = flagBlue;
02367                 eFlagBlue = flag_blue;
02368         }
02369 }
02370 
02371 #ifndef _XBOX
02372 int SavePathData(const char *filename)
02373 {
02374         fileHandle_t f;
02375         char *fileString;
02376         char *storeString;
02377         char *routePath;
02378         vec3_t a;
02379         float flLen;
02380         int i, s, n;
02381 
02382         fileString = NULL;
02383         i = 0;
02384         s = 0;
02385 
02386         if (!gWPNum)
02387         {
02388                 return 0;
02389         }
02390 
02391         routePath = (char *)B_TempAlloc(1024);
02392 
02393         Com_sprintf(routePath, 1024, "botroutes/%s.wnt\0", filename);
02394 
02395         trap_FS_FOpenFile(routePath, &f, FS_WRITE);
02396 
02397         B_TempFree(1024); //routePath
02398 
02399         if (!f)
02400         {
02401                 G_Printf(S_COLOR_RED "ERROR: Could not open file to write path data\n");
02402                 return 0;
02403         }
02404 
02405         if (!RepairPaths(qfalse)) //check if we can see all waypoints from the last. If not, try to branch over.
02406         {
02407                 trap_FS_FCloseFile(f);
02408                 return 0;
02409         }
02410 
02411         CalculatePaths(); //make everything nice and connected before saving
02412 
02413         FlagObjects(); //currently only used for flagging waypoints nearest CTF flags
02414 
02415         fileString = (char *)B_TempAlloc(524288);
02416         storeString = (char *)B_TempAlloc(4096);
02417 
02418         Com_sprintf(fileString, 524288, "%i %i %f (%f %f %f) { ", gWPArray[i]->index, gWPArray[i]->flags, gWPArray[i]->weight, gWPArray[i]->origin[0], gWPArray[i]->origin[1], gWPArray[i]->origin[2]);
02419 
02420         n = 0;
02421 
02422         while (n < gWPArray[i]->neighbornum)
02423         {
02424                 if (gWPArray[i]->neighbors[n].forceJumpTo)
02425                 {
02426                         Com_sprintf(storeString, 4096, "%s%i-%i ", storeString, gWPArray[i]->neighbors[n].num, gWPArray[i]->neighbors[n].forceJumpTo);
02427                 }
02428                 else
02429                 {
02430                         Com_sprintf(storeString, 4096, "%s%i ", storeString, gWPArray[i]->neighbors[n].num);
02431                 }
02432                 n++;
02433         }
02434 
02435         if (gWPArray[i+1] && gWPArray[i+1]->inuse && gWPArray[i+1]->index)
02436         {
02437                 VectorSubtract(gWPArray[i]->origin, gWPArray[i+1]->origin, a);
02438                 flLen = VectorLength(a);
02439         }
02440         else
02441         {
02442                 flLen = 0;
02443         }
02444 
02445         gWPArray[i]->disttonext = flLen;
02446 
02447         Com_sprintf(fileString, 524288, "%s} %f\n", fileString, flLen);
02448 
02449         i++;
02450 
02451         while (i < gWPNum)
02452         {
02453                 //sprintf(fileString, "%s%i %i %f (%f %f %f) { ", fileString, gWPArray[i]->index, gWPArray[i]->flags, gWPArray[i]->weight, gWPArray[i]->origin[0], gWPArray[i]->origin[1], gWPArray[i]->origin[2]);
02454                 Com_sprintf(storeString, 4096, "%i %i %f (%f %f %f) { ", gWPArray[i]->index, gWPArray[i]->flags, gWPArray[i]->weight, gWPArray[i]->origin[0], gWPArray[i]->origin[1], gWPArray[i]->origin[2]);
02455 
02456                 n = 0;
02457 
02458                 while (n < gWPArray[i]->neighbornum)
02459                 {
02460                         if (gWPArray[i]->neighbors[n].forceJumpTo)
02461                         {
02462                                 Com_sprintf(storeString, 4096, "%s%i-%i ", storeString, gWPArray[i]->neighbors[n].num, gWPArray[i]->neighbors[n].forceJumpTo);
02463                         }
02464                         else
02465                         {
02466                                 Com_sprintf(storeString, 4096, "%s%i ", storeString, gWPArray[i]->neighbors[n].num);
02467                         }
02468                         n++;
02469                 }
02470 
02471                 if (gWPArray[i+1] && gWPArray[i+1]->inuse && gWPArray[i+1]->index)
02472                 {
02473                         VectorSubtract(gWPArray[i]->origin, gWPArray[i+1]->origin, a);
02474                         flLen = VectorLength(a);
02475                 }
02476                 else
02477                 {
02478                         flLen = 0;
02479                 }
02480 
02481                 gWPArray[i]->disttonext = flLen;
02482 
02483                 Com_sprintf(storeString, 4096, "%s} %f\n", storeString, flLen);
02484 
02485                 strcat(fileString, storeString);
02486 
02487                 i++;
02488         }
02489 
02490         trap_FS_Write(fileString, strlen(fileString), f);
02491 
02492         B_TempFree(524288); //fileString
02493         B_TempFree(4096); //storeString
02494 
02495         trap_FS_FCloseFile(f);
02496 
02497         G_Printf("Path data has been saved and updated. You may need to restart the level for some things to be properly calculated.\n");
02498 
02499         return 1;
02500 }
02501 #endif
02502 
02503 //#define PAINFULLY_DEBUGGING_THROUGH_VM
02504 
02505 #define MAX_SPAWNPOINT_ARRAY 64
02506 int gSpawnPointNum = 0;
02507 gentity_t *gSpawnPoints[MAX_SPAWNPOINT_ARRAY];
02508 
02509 #ifndef _XBOX
02510 int G_NearestNodeToPoint(vec3_t point)
02511 { //gets the node on the entire grid which is nearest to the specified coordinates.
02512         vec3_t vSub;
02513         int bestIndex = -1;
02514         int i = 0;
02515         float bestDist = 0;
02516         float testDist = 0;
02517 
02518         while (i < nodenum)
02519         {
02520                 VectorSubtract(nodetable[i].origin, point, vSub);
02521                 testDist = VectorLength(vSub);
02522 
02523                 if (bestIndex == -1)
02524                 {
02525                         bestIndex = i;
02526                         bestDist = testDist;
02527 
02528                         i++;
02529                         continue;
02530                 }
02531 
02532                 if (testDist < bestDist)
02533                 {
02534                         bestIndex = i;
02535                         bestDist = testDist;
02536                 }
02537                 i++;
02538         }
02539 
02540         return bestIndex;
02541 }
02542 #endif
02543 
02544 #ifndef _XBOX
02545 void G_NodeClearForNext(void)
02546 { //reset nodes for the next trail connection.
02547         int i = 0;
02548 
02549         while (i < nodenum)
02550         {
02551                 nodetable[i].flags = 0;
02552                 nodetable[i].weight = 99999;
02553 
02554                 i++;
02555         }
02556 }
02557 
02558 void G_NodeClearFlags(void)
02559 { //only clear out flags so nodes can be reused.
02560         int i = 0;
02561 
02562         while (i < nodenum)
02563         {
02564                 nodetable[i].flags = 0;
02565 
02566                 i++;
02567         }
02568 }
02569 
02570 int G_NodeMatchingXY(float x, float y)
02571 { //just get the first unflagged node with the matching x,y coordinates.
02572         int i = 0;
02573 
02574         while (i < nodenum)
02575         {
02576                 if (nodetable[i].origin[0] == x &&
02577                         nodetable[i].origin[1] == y &&
02578                         !nodetable[i].flags)
02579                 {
02580                         return i;
02581                 }
02582 
02583                 i++;
02584         }
02585 
02586         return -1;
02587 }
02588 
02589 int G_NodeMatchingXY_BA(int x, int y, int final)
02590 { //return the node with the lowest weight that matches the specified x,y coordinates.
02591         int i = 0;
02592         int bestindex = -1;
02593         float bestWeight = 9999;
02594 
02595         while (i < nodenum)
02596         {
02597                 if ((int)nodetable[i].origin[0] == x &&
02598                         (int)nodetable[i].origin[1] == y &&
02599                         !nodetable[i].flags &&
02600                         ((nodetable[i].weight < bestWeight) || (i == final)))
02601                 {
02602                         if (i == final)
02603                         {
02604                                 return i;
02605                         }
02606                         bestindex = i;
02607                         bestWeight = nodetable[i].weight;
02608                 }
02609 
02610                 i++;
02611         }
02612 
02613         return bestindex;
02614 }
02615 
02616 int G_RecursiveConnection(int start, int end, int weight, qboolean traceCheck, float baseHeight)
02617 {
02618         int indexDirections[4]; //0 == down, 1 == up, 2 == left, 3 == right
02619         int recursiveIndex = -1;
02620         int i = 0;
02621         int passWeight = weight;
02622         vec2_t givenXY;
02623         trace_t tr;
02624 
02625         passWeight++;
02626         nodetable[start].weight = passWeight;
02627 
02628         givenXY[0] = nodetable[start].origin[0];
02629         givenXY[1] = nodetable[start].origin[1];
02630         givenXY[0] -= DEFAULT_GRID_SPACING;
02631         indexDirections[0] = G_NodeMatchingXY(givenXY[0], givenXY[1]);
02632 
02633         givenXY[0] = nodetable[start].origin[0];
02634         givenXY[1] = nodetable[start].origin[1];
02635         givenXY[0] += DEFAULT_GRID_SPACING;
02636         indexDirections[1] = G_NodeMatchingXY(givenXY[0], givenXY[1]);
02637 
02638         givenXY[0] = nodetable[start].origin[0];
02639         givenXY[1] = nodetable[start].origin[1];
02640         givenXY[1] -= DEFAULT_GRID_SPACING;
02641         indexDirections[2] = G_NodeMatchingXY(givenXY[0], givenXY[1]);
02642 
02643         givenXY[0] = nodetable[start].origin[0];
02644         givenXY[1] = nodetable[start].origin[1];
02645         givenXY[1] += DEFAULT_GRID_SPACING;
02646         indexDirections[3] = G_NodeMatchingXY(givenXY[0], givenXY[1]);
02647 
02648         i = 0;
02649         while (i < 4)
02650         {
02651                 if (indexDirections[i] == end)
02652                 { //we've connected all the way to the destination.
02653                         return indexDirections[i];
02654                 }
02655 
02656                 if (indexDirections[i] != -1 && nodetable[indexDirections[i]].flags)
02657                 { //this point is already used, so it's not valid.
02658                         indexDirections[i] = -1;
02659                 }
02660                 else if (indexDirections[i] != -1)
02661                 { //otherwise mark it as used.
02662                         nodetable[indexDirections[i]].flags = 1;
02663                 }
02664 
02665                 if (indexDirections[i] != -1 && traceCheck)
02666                 { //if we care about trace visibility between nodes, perform the check and mark as not valid if the trace isn't clear.
02667                         trap_Trace(&tr, nodetable[start].origin, NULL, NULL, nodetable[indexDirections[i]].origin, ENTITYNUM_NONE, CONTENTS_SOLID);
02668 
02669                         if (tr.fraction != 1)
02670                         {
02671                                 indexDirections[i] = -1;
02672                         }
02673                 }
02674 
02675                 if (indexDirections[i] != -1)
02676                 { //it's still valid, so keep connecting via this point.
02677                         recursiveIndex = G_RecursiveConnection(indexDirections[i], end, passWeight, traceCheck, baseHeight);
02678                 }
02679 
02680                 if (recursiveIndex != -1)
02681                 { //the result of the recursive check was valid, so return it.
02682                         return recursiveIndex;
02683                 }
02684 
02685                 i++;
02686         }
02687 
02688         return recursiveIndex;
02689 }
02690 
02691 #ifdef DEBUG_NODE_FILE
02692 void G_DebugNodeFile()
02693 {
02694         fileHandle_t f;
02695         int i = 0;
02696         float placeX;
02697         char fileString[131072];
02698         gentity_t *terrain = G_Find( NULL, FOFS(classname), "terrain" );
02699 
02700         fileString[0] = 0;
02701 
02702         placeX = terrain->r.absmin[0];
02703 
02704         while (i < nodenum)
02705         {
02706                 strcat(fileString, va("%i-%f ", i, nodetable[i].weight));
02707                 placeX += DEFAULT_GRID_SPACING;
02708 
02709                 if (placeX >= terrain->r.absmax[0])
02710                 {
02711                         strcat(fileString, "\n");
02712                         placeX = terrain->r.absmin[0];
02713                 }
02714                 i++;
02715         }
02716 
02717         trap_FS_FOpenFile("ROUTEDEBUG.txt", &f, FS_WRITE);
02718         trap_FS_Write(fileString, strlen(fileString), f);
02719         trap_FS_FCloseFile(f);
02720 }
02721 #endif
02722 
02723 #endif
02724 //#define ASCII_ART_DEBUG
02725 //#define ASCII_ART_NODE_DEBUG
02726 
02727 #ifdef ASCII_ART_DEBUG
02728 
02729 #define ALLOWABLE_DEBUG_FILE_SIZE 1048576
02730 
02731 void CreateAsciiTableRepresentation()
02732 { //Draw a text grid of the entire waypoint array (useful for debugging final waypoint placement)
02733         fileHandle_t f;
02734         int i = 0;
02735         int sP = 0;
02736         int placeX;
02737         int placeY;
02738         int oldX;
02739         int oldY;
02740         char fileString[ALLOWABLE_DEBUG_FILE_SIZE];
02741         char bChr = '+';
02742         gentity_t *terrain = G_Find( NULL, FOFS(classname), "terrain" );
02743 
02744         placeX = terrain->r.absmin[0];
02745         placeY = terrain->r.absmin[1];
02746 
02747         oldX = placeX-1;
02748         oldY = placeY-1;
02749 
02750         while (placeY < terrain->r.absmax[1])
02751         {
02752                 while (placeX < terrain->r.absmax[0])
02753                 {
02754                         qboolean gotit = qfalse;
02755 
02756                         i = 0;
02757                         while (i < gWPNum)
02758                         {
02759                                 if (((int)gWPArray[i]->origin[0] <= placeX && (int)gWPArray[i]->origin[0] > oldX) &&
02760                                         ((int)gWPArray[i]->origin[1] <= placeY && (int)gWPArray[i]->origin[1] > oldY))
02761                                 {
02762                                         gotit = qtrue;
02763                                         break;
02764                                 }
02765                                 i++;
02766                         }
02767 
02768                         if (gotit)
02769                         {
02770                                 if (gWPArray[i]->flags & WPFLAG_ONEWAY_FWD)
02771                                 {
02772                                         bChr = 'F';
02773                                 }
02774                                 else if (gWPArray[i]->flags & WPFLAG_ONEWAY_BACK)
02775                                 {
02776                                         bChr = 'B';
02777                                 }
02778                                 else
02779                                 {
02780                                         bChr = '+';
02781                                 }
02782 
02783                                 if (gWPArray[i]->index < 10)
02784                                 {
02785                                         fileString[sP] = bChr;
02786                                         fileString[sP+1] = '0';
02787                                         fileString[sP+2] = '0';
02788                                         fileString[sP+3] = va("%i", gWPArray[i]->index)[0];
02789                                 }
02790                                 else if (gWPArray[i]->index < 100)
02791                                 {
02792                                         char *vastore = va("%i", gWPArray[i]->index);
02793 
02794                                         fileString[sP] = bChr;
02795                                         fileString[sP+1] = '0';
02796                                         fileString[sP+2] = vastore[0];
02797                                         fileString[sP+3] = vastore[1];
02798                                 }
02799                                 else if (gWPArray[i]->index < 1000)
02800                                 {
02801                                         char *vastore = va("%i", gWPArray[i]->index);
02802 
02803                                         fileString[sP] = bChr;
02804                                         fileString[sP+1] = vastore[0];
02805                                         fileString[sP+2] = vastore[1];
02806                                         fileString[sP+3] = vastore[2];
02807                                 }
02808                                 else
02809                                 {
02810                                         fileString[sP] = 'X';
02811                                         fileString[sP+1] = 'X';
02812                                         fileString[sP+2] = 'X';
02813                                         fileString[sP+3] = 'X';
02814                                 }
02815                         }
02816                         else
02817                         {
02818                                 fileString[sP] = '-';
02819                                 fileString[sP+1] = '-';
02820                                 fileString[sP+2] = '-';
02821                                 fileString[sP+3] = '-';
02822                         }
02823 
02824                         sP += 4;
02825 
02826                         if (sP >= ALLOWABLE_DEBUG_FILE_SIZE-16)
02827                         {
02828                                 break;
02829                         }
02830                         oldX = placeX;
02831                         placeX += DEFAULT_GRID_SPACING;
02832                 }
02833 
02834                 placeX = terrain->r.absmin[0];
02835                 oldX = placeX-1;
02836                 fileString[sP] = '\n';
02837                 sP++;
02838 
02839                 if (sP >= ALLOWABLE_DEBUG_FILE_SIZE-16)
02840                 {
02841                         break;
02842                 }
02843 
02844                 oldY = placeY;
02845                 placeY += DEFAULT_GRID_SPACING;
02846         }
02847 
02848         fileString[sP] = 0;
02849 
02850         trap_FS_FOpenFile("ROUTEDRAWN.txt", &f, FS_WRITE);
02851         trap_FS_Write(fileString, strlen(fileString), f);
02852         trap_FS_FCloseFile(f);
02853 }
02854 
02855 void CreateAsciiNodeTableRepresentation(int start, int end)
02856 { //draw a text grid of a single node path, from point A to Z.
02857         fileHandle_t f;
02858         int i = 0;
02859         int sP = 0;
02860         int placeX;
02861         int placeY;
02862         int oldX;
02863         int oldY;
02864         char fileString[ALLOWABLE_DEBUG_FILE_SIZE];
02865         gentity_t *terrain = G_Find( NULL, FOFS(classname), "terrain" );
02866 
02867         placeX = terrain->r.absmin[0];
02868         placeY = terrain->r.absmin[1];
02869 
02870         oldX = placeX-1;
02871         oldY = placeY-1;
02872 
02873         while (placeY < terrain->r.absmax[1])
02874         {
02875                 while (placeX < terrain->r.absmax[0])
02876                 {
02877                         qboolean gotit = qfalse;
02878 
02879                         i = 0;
02880                         while (i < nodenum)
02881                         {
02882                                 if (((int)nodetable[i].origin[0] <= placeX && (int)nodetable[i].origin[0] > oldX) &&
02883                                         ((int)nodetable[i].origin[1] <= placeY && (int)nodetable[i].origin[1] > oldY))
02884                                 {
02885                                         gotit = qtrue;
02886                                         break;
02887                                 }
02888                                 i++;
02889                         }
02890 
02891                         if (gotit)
02892                         {
02893                                 if (i == start)
02894                                 { //beginning of the node trail
02895                                         fileString[sP] = 'A';
02896                                         fileString[sP+1] = 'A';
02897                                         fileString[sP+2] = 'A';
02898                                         fileString[sP+3] = 'A';
02899                                 }
02900                                 else if (i == end)
02901                                 { //destination of the node trail
02902                                         fileString[sP] = 'Z';
02903                                         fileString[sP+1] = 'Z';
02904                                         fileString[sP+2] = 'Z';
02905                                         fileString[sP+3] = 'Z';
02906                                 }
02907                                 else if (nodetable[i].weight < 10)
02908                                 {
02909                                         fileString[sP] = '+';
02910                                         fileString[sP+1] = '0';
02911                                         fileString[sP+2] = '0';
02912                                         fileString[sP+3] = va("%f", nodetable[i].weight)[0];
02913                                 }
02914                                 else if (nodetable[i].weight < 100)
02915                                 {
02916                                         char *vastore = va("%f", nodetable[i].weight);
02917 
02918                                         fileString[sP] = '+';
02919                                         fileString[sP+1] = '0';
02920                                         fileString[sP+2] = vastore[0];
02921                                         fileString[sP+3] = vastore[1];
02922                                 }
02923                                 else if (nodetable[i].weight < 1000)
02924                                 {
02925                                         char *vastore = va("%f", nodetable[i].weight);
02926 
02927                                         fileString[sP] = '+';
02928                                         fileString[sP+1] = vastore[0];
02929                                         fileString[sP+2] = vastore[1];
02930                                         fileString[sP+3] = vastore[2];
02931                                 }
02932                                 else
02933                                 {
02934                                         fileString[sP] = 'X';
02935                                         fileString[sP+1] = 'X';
02936                                         fileString[sP+2] = 'X';
02937                                         fileString[sP+3] = 'X';
02938                                 }
02939                         }
02940                         else
02941                         {
02942                                 fileString[sP] = '-';
02943                                 fileString[sP+1] = '-';
02944                                 fileString[sP+2] = '-';
02945                                 fileString[sP+3] = '-';
02946                         }
02947 
02948                         sP += 4;
02949 
02950                         if (sP >= ALLOWABLE_DEBUG_FILE_SIZE-16)
02951                         {
02952                                 break;
02953                         }
02954                         oldX = placeX;
02955                         placeX += DEFAULT_GRID_SPACING;
02956                 }
02957 
02958                 placeX = terrain->r.absmin[0];
02959                 oldX = placeX-1;
02960                 fileString[sP] = '\n';
02961                 sP++;
02962 
02963                 if (sP >= ALLOWABLE_DEBUG_FILE_SIZE-16)
02964                 {
02965                         break;
02966                 }
02967 
02968                 oldY = placeY;
02969                 placeY += DEFAULT_GRID_SPACING;
02970         }
02971 
02972         fileString[sP] = 0;
02973 
02974         trap_FS_FOpenFile("ROUTEDRAWN.txt", &f, FS_WRITE);
02975         trap_FS_Write(fileString, strlen(fileString), f);
02976         trap_FS_FCloseFile(f);
02977 }
02978 #endif
02979 
02980 #ifndef _XBOX
02981 qboolean G_BackwardAttachment(int start, int finalDestination, int insertAfter)
02982 { //After creating a node path between 2 points, this function links the 2 points with actual waypoint data.
02983         int indexDirections[4]; //0 == down, 1 == up, 2 == left, 3 == right
02984         int i = 0;
02985         int lowestWeight = 9999;
02986         int desiredIndex = -1;
02987         vec2_t givenXY;
02988 
02989         givenXY[0] = nodetable[start].origin[0];
02990         givenXY[1] = nodetable[start].origin[1];
02991         givenXY[0] -= DEFAULT_GRID_SPACING;
02992         indexDirections[0] = G_NodeMatchingXY_BA(givenXY[0], givenXY[1], finalDestination);
02993 
02994         givenXY[0] = nodetable[start].origin[0];
02995         givenXY[1] = nodetable[start].origin[1];
02996         givenXY[0] += DEFAULT_GRID_SPACING;
02997         indexDirections[1] = G_NodeMatchingXY_BA(givenXY[0], givenXY[1], finalDestination);
02998 
02999         givenXY[0] = nodetable[start].origin[0];
03000         givenXY[1] = nodetable[start].origin[1];
03001         givenXY[1] -= DEFAULT_GRID_SPACING;
03002         indexDirections[2] = G_NodeMatchingXY_BA(givenXY[0], givenXY[1], finalDestination);
03003 
03004         givenXY[0] = nodetable[start].origin[0];
03005         givenXY[1] = nodetable[start].origin[1];
03006         givenXY[1] += DEFAULT_GRID_SPACING;
03007         indexDirections[3] = G_NodeMatchingXY_BA(givenXY[0], givenXY[1], finalDestination);
03008 
03009         while (i < 4)
03010         {
03011                 if (indexDirections[i] != -1)
03012                 {
03013                         if (indexDirections[i] == finalDestination)
03014                         { //hooray, we've found the original point and linked all the way back to it.
03015                                 CreateNewWP_InsertUnder(nodetable[start].origin, 0, insertAfter);
03016                                 CreateNewWP_InsertUnder(nodetable[indexDirections[i]].origin, 0, insertAfter);
03017                                 return qtrue;
03018                         }
03019 
03020                         if (nodetable[indexDirections[i]].weight < lowestWeight && nodetable[indexDirections[i]].weight && !nodetable[indexDirections[i]].flags /*&& (nodetable[indexDirections[i]].origin[2]-64 < nodetable[start].origin[2])*/)
03021                         {
03022                                 desiredIndex = indexDirections[i];
03023                                 lowestWeight = nodetable[indexDirections[i]].weight;
03024                         }
03025                 }
03026                 i++;
03027         }
03028 
03029         if (desiredIndex != -1)
03030         { //Create a waypoint here, and then recursively call this function for the next neighbor with the lowest weight.
03031                 if (gWPNum < 3900)
03032                 {
03033                         CreateNewWP_InsertUnder(nodetable[start].origin, 0, insertAfter);
03034                 }
03035                 else
03036                 {
03037 #ifdef PAINFULLY_DEBUGGING_THROUGH_VM
03038                         Com_Printf("WAYPOINTS FULL\n");
03039 #endif
03040                         return qfalse;
03041                 }
03042 
03043                 nodetable[start].flags = 1;
03044                 return G_BackwardAttachment(desiredIndex, finalDestination, insertAfter);
03045         }
03046 
03047         return qfalse;
03048 }
03049 
03050 
03051 #ifdef _DEBUG
03052 #define PATH_TIME_DEBUG
03053 #endif
03054 
03055 void G_RMGPathing(void)
03056 { //Generate waypoint information on-the-fly for the random mission.
03057         float placeX, placeY, placeZ;
03058         int i = 0;
03059         int gridSpacing = DEFAULT_GRID_SPACING;
03060         int nearestIndex = 0;
03061         int nearestIndexForNext = 0;
03062 #ifdef PATH_TIME_DEBUG
03063         int startTime = 0;
03064         int endTime = 0;
03065 #endif
03066         vec3_t downVec, trMins, trMaxs;
03067         trace_t tr;
03068         gentity_t *terrain = G_Find( NULL, FOFS(classname), "terrain" );
03069 
03070         if (!terrain || !terrain->inuse || terrain->s.eType != ET_TERRAIN)
03071         {
03072                 G_Printf("Error: RMG with no terrain!\n");
03073                 return;
03074         }
03075 
03076 #ifdef PATH_TIME_DEBUG
03077         startTime = trap_Milliseconds();
03078 #endif
03079 
03080         nodenum = 0;
03081         memset(&nodetable, 0, sizeof(nodetable));
03082 
03083         VectorSet(trMins, -15, -15, DEFAULT_MINS_2);
03084         VectorSet(trMaxs, 15, 15, DEFAULT_MAXS_2);
03085 
03086         placeX = terrain->r.absmin[0];
03087         placeY = terrain->r.absmin[1];
03088         placeZ = terrain->r.absmax[2]-400;
03089 
03090         //skim through the entirety of the terrain limits and drop nodes, removing
03091         //nodes that start in solid or fall too high on the terrain.
03092         while (placeY < terrain->r.absmax[1])
03093         {
03094                 if (nodenum >= MAX_NODETABLE_SIZE)
03095                 {
03096                         break;
03097                 }
03098 
03099                 while (placeX < terrain->r.absmax[0])
03100                 {
03101                         if (nodenum >= MAX_NODETABLE_SIZE)
03102                         {
03103                                 break;
03104                         }
03105 
03106                         nodetable[nodenum].origin[0] = placeX;
03107                         nodetable[nodenum].origin[1] = placeY;
03108                         nodetable[nodenum].origin[2] = placeZ;
03109 
03110                         VectorCopy(nodetable[nodenum].origin, downVec);
03111                         downVec[2] -= 3000;
03112                         trap_Trace(&tr, nodetable[nodenum].origin, trMins, trMaxs, downVec, ENTITYNUM_NONE, MASK_SOLID);
03113 
03114                         if ((tr.entityNum >= ENTITYNUM_WORLD || g_entities[tr.entityNum].s.eType == ET_TERRAIN) && tr.endpos[2] < terrain->r.absmin[2]+750)
03115                         { //only drop nodes on terrain directly
03116                                 VectorCopy(tr.endpos, nodetable[nodenum].origin);
03117                                 nodenum++;
03118                         }
03119                         else
03120                         {
03121                                 VectorClear(nodetable[nodenum].origin);
03122                         }
03123 
03124                         placeX += gridSpacing;
03125                 }
03126 
03127                 placeX = terrain->r.absmin[0];
03128                 placeY += gridSpacing;
03129         }
03130 
03131 #ifdef PAINFULLY_DEBUGGING_THROUGH_VM
03132         Com_Printf("NODE GRID PLACED ON TERRAIN\n");
03133 #endif
03134 
03135         G_NodeClearForNext();
03136 
03137         //The grid has been placed down, now use it to connect the points in the level.
03138         while (i < gSpawnPointNum-1)
03139         {
03140                 if (!gSpawnPoints[i] || !gSpawnPoints[i]->inuse || !gSpawnPoints[i+1] || !gSpawnPoints[i+1]->inuse)
03141                 {
03142                         i++;
03143                         continue;
03144                 }
03145 
03146                 nearestIndex = G_NearestNodeToPoint(gSpawnPoints[i]->s.origin);
03147                 nearestIndexForNext = G_NearestNodeToPoint(gSpawnPoints[i+1]->s.origin);
03148 
03149 #ifdef PAINFULLY_DEBUGGING_THROUGH_VM
03150                 Com_Printf("%i GOT %i INDEX WITH %i INDEX FOR NEXT\n", nearestIndex, nearestIndexForNext);
03151 #endif
03152 
03153                 if (nearestIndex == -1 || nearestIndexForNext == -1)
03154                 { //Looks like there is no grid data near one of the points. Ideally, this will never happen.
03155                         i++;
03156                         continue;
03157                 }
03158 
03159                 if (nearestIndex == nearestIndexForNext)
03160                 { //Two spawn points on top of each other? We don't need to do both points, keep going until the next differs.
03161                         i++;
03162                         continue;
03163                 }
03164 
03165                 //So, nearestIndex is now the node for the spawn point we're on, and nearestIndexForNext is the
03166                 //node we want to get to from here.
03167 
03168                 //For now I am going to branch out mindlessly, but I will probably want to use some sort of A* algorithm
03169                 //here to lessen the time taken.
03170                 if (G_RecursiveConnection(nearestIndex, nearestIndexForNext, 0, qtrue, terrain->r.absmin[2]) != nearestIndexForNext)
03171                 { //failed to branch to where we want. Oh well, try it without trace checks.
03172                         G_NodeClearForNext();
03173 
03174 #ifdef PAINFULLY_DEBUGGING_THROUGH_VM
03175                         Com_Printf("FAILED RECURSIVE WITH TRACES\n");
03176 #endif
03177 
03178                         if (G_RecursiveConnection(nearestIndex, nearestIndexForNext, 0, qfalse, terrain->r.absmin[2]) != nearestIndexForNext)
03179                         { //still failed somehow. Just disregard this point.
03180 #ifdef PAINFULLY_DEBUGGING_THROUGH_VM
03181                                 Com_Printf("FAILED RECURSIVE -WITHOUT- TRACES (?!?!)\n");
03182 #endif
03183                                 G_NodeClearForNext();
03184                                 i++;
03185                                 continue;
03186                         }
03187                 }
03188 
03189                 //Now our node array is set up so that highest reasonable weight is the destination node, and 2 is next to the original index,
03190                 //so trace back to that point.
03191                 G_NodeClearFlags();
03192 
03193 #ifdef ASCII_ART_DEBUG
03194 #ifdef ASCII_ART_NODE_DEBUG
03195                 CreateAsciiNodeTableRepresentation(nearestIndex, nearestIndexForNext);
03196 #endif
03197 #endif
03198                 if (G_BackwardAttachment(nearestIndexForNext, nearestIndex, gWPNum-1))
03199                 { //successfully connected the trail from nearestIndex to nearestIndexForNext
03200                         if (gSpawnPoints[i+1]->inuse && gSpawnPoints[i+1]->item &&
03201                                 gSpawnPoints[i+1]->item->giType == IT_TEAM)
03202                         { //This point is actually a CTF flag.
03203                                 if (gSpawnPoints[i+1]->item->giTag == PW_REDFLAG || gSpawnPoints[i+1]->item->giTag == PW_BLUEFLAG)
03204                                 { //Place a waypoint on the flag next in the trail, so the nearest grid point will link to it.
03205                                         CreateNewWP_InsertUnder(gSpawnPoints[i+1]->s.origin, WPFLAG_NEVERONEWAY, gWPNum-1);
03206                                 }
03207                         }
03208 
03209 #ifdef PAINFULLY_DEBUGGING_THROUGH_VM
03210                         Com_Printf("BACKWARD ATTACHMENT %i SUCCESS\n", i);
03211 #endif
03212                 }
03213                 else
03214                 {
03215 #ifdef PAINFULLY_DEBUGGING_THROUGH_VM
03216                         Com_Printf("BACKWARD ATTACHMENT FAILED\n");
03217 #endif
03218                         break;
03219                 }
03220 
03221 #ifdef DEBUG_NODE_FILE
03222                 G_DebugNodeFile();
03223 #endif
03224 
03225                 G_NodeClearForNext();
03226                 i++;
03227         }
03228 
03229 #ifdef PAINFULLY_DEBUGGING_THROUGH_VM
03230         Com_Printf("FINISHED RMG AUTOPATH\n");
03231 #endif
03232 
03233 #ifdef PAINFULLY_DEBUGGING_THROUGH_VM
03234         Com_Printf("BEGINNING PATH REPAIR...\n");
03235 #endif
03236         RepairPaths(qtrue); //this has different behaviour for RMG and will just flag all points one way that don't trace to each other.
03237 #ifdef PAINFULLY_DEBUGGING_THROUGH_VM
03238         Com_Printf("FINISHED PATH REPAIR.\n");
03239 #endif
03240 
03241 #ifdef PATH_TIME_DEBUG
03242         endTime = trap_Milliseconds();
03243 
03244         G_Printf("Total routing time taken: %ims\n", (endTime - startTime));
03245 #endif
03246 
03247 #ifdef ASCII_ART_DEBUG
03248         CreateAsciiTableRepresentation();
03249 #endif
03250 }
03251 #endif
03252 
03253 #ifndef _XBOX
03254 void BeginAutoPathRoutine(void)
03255 { //Called for RMG levels.
03256         int i = 0;
03257         gentity_t *ent = NULL;
03258         vec3_t v;
03259 
03260         gSpawnPointNum = 0;
03261 
03262         CreateNewWP(vec3_origin, 0); //create a dummy waypoint to insert under
03263 
03264         while (i < level.num_entities)
03265         {
03266                 ent = &g_entities[i];
03267 
03268                 if (ent && ent->inuse && ent->classname && ent->classname[0] && !Q_stricmp(ent->classname, "info_player_deathmatch"))
03269                 {
03270                         if (ent->s.origin[2] < 1280)
03271                         { //h4x
03272                                 gSpawnPoints[gSpawnPointNum] = ent;
03273                                 gSpawnPointNum++;
03274                         }
03275                 }
03276                 else if (ent && ent->inuse && ent->item && ent->item->giType == IT_TEAM &&
03277                         (ent->item->giTag == PW_REDFLAG || ent->item->giTag == PW_BLUEFLAG))
03278                 { //also make it path to flags in CTF.
03279                         gSpawnPoints[gSpawnPointNum] = ent;
03280                         gSpawnPointNum++;
03281                 }
03282 
03283                 i++;
03284         }
03285 
03286         if (gSpawnPointNum < 1)
03287         {
03288                 return;
03289         }
03290 
03291         G_RMGPathing();
03292 #ifdef PAINFULLY_DEBUGGING_THROUGH_VM
03293         Com_Printf("LINKING PATHS...\n");
03294 #endif
03295         
03296         //rww - Using a faster in-engine version because we're having to wait for this stuff to get done as opposed to just saving it once.
03297         trap_Bot_UpdateWaypoints(gWPNum, gWPArray);
03298         trap_Bot_CalculatePaths(g_RMG.integer);
03299         //CalculatePaths(); //make everything nice and connected
03300 
03301 #ifdef PAINFULLY_DEBUGGING_THROUGH_VM
03302         Com_Printf("FINISHED LINKING PATHS.\n");
03303 #endif
03304 
03305 #ifdef PAINFULLY_DEBUGGING_THROUGH_VM
03306         Com_Printf("FLAGGING OBJECTS...\n");
03307 #endif
03308         FlagObjects(); //currently only used for flagging waypoints nearest CTF flags
03309 #ifdef PAINFULLY_DEBUGGING_THROUGH_VM
03310         Com_Printf("FINISHED FLAGGING OBJECTS.\n");
03311 #endif
03312 
03313 #ifdef PAINFULLY_DEBUGGING_THROUGH_VM
03314         Com_Printf("CALCULATING WAYPOINT DISTANCES...\n");
03315 #endif
03316         i = 0;
03317 
03318         while (i < gWPNum-1)
03319         { //disttonext is normally set on save, and when a file is loaded. For RMG we must do it after calc'ing.
03320                 VectorSubtract(gWPArray[i]->origin, gWPArray[i+1]->origin, v);
03321                 gWPArray[i]->disttonext = VectorLength(v);
03322                 i++;
03323         }
03324 #ifdef PAINFULLY_DEBUGGING_THROUGH_VM
03325         Com_Printf("FINISHED CALCULATING.\n");
03326 #endif
03327 
03328 #ifdef PAINFULLY_DEBUGGING_THROUGH_VM
03329         Com_Printf("FINAL STEP...\n");
03330 #endif
03331         RemoveWP(); //remove the dummy point at the end of the trail
03332 #ifdef PAINFULLY_DEBUGGING_THROUGH_VM
03333         Com_Printf("COMPLETE.\n");
03334 #endif
03335 
03336 #ifdef PAINFULLY_DEBUGGING_THROUGH_VM
03337         if (gWPNum >= 4096-1)
03338         {
03339                 Com_Printf("%i waypoints say that YOU ARE A TERRIBLE MAN.\n", gWPNum);
03340         }
03341 #endif
03342 }
03343 
03344 #endif
03345 extern vmCvar_t bot_normgpath;
03346 
03347 void LoadPath_ThisLevel(void)
03348 {
03349         vmCvar_t        mapname;
03350         int                     i = 0;
03351         gentity_t       *ent = NULL;
03352 
03353         trap_Cvar_Register( &mapname, "mapname", "", CVAR_SERVERINFO | CVAR_ROM );
03354 
03355         if (g_RMG.integer)
03356         { //If RMG, generate the path on-the-fly
03357 #ifdef _XBOX
03358                 assert(0);
03359 #else
03360                 trap_Cvar_Register(&bot_normgpath, "bot_normgpath", "1", CVAR_CHEAT);
03361                 //note: This is disabled for now as I'm using standard bot nav
03362                 //on premade terrain levels.
03363 
03364                 if (!bot_normgpath.integer)
03365                 { //autopath the random map
03366                         BeginAutoPathRoutine();
03367                 }
03368                 else
03369                 { //try loading standard nav data
03370                         LoadPathData(mapname.string);
03371                 }
03372 
03373                 gLevelFlags |= LEVELFLAG_NOPOINTPREDICTION;
03374 #endif
03375         }
03376         else
03377         {
03378                 if (LoadPathData(mapname.string) == 2)
03379                 {
03380                         //enter "edit" mode if cheats enabled?
03381                 }
03382         }
03383 
03384         trap_Cvar_Update(&bot_wp_edit);
03385 
03386         if (bot_wp_edit.value)
03387         {
03388                 gBotEdit = 1;
03389         }
03390         else
03391         {
03392                 gBotEdit = 0;
03393         }
03394 
03395         //set the flag entities
03396         while (i < level.num_entities)
03397         {
03398                 ent = &g_entities[i];
03399 
03400                 if (ent && ent->inuse && ent->classname)
03401                 {
03402                         if (!eFlagRed && strcmp(ent->classname, "team_CTF_redflag") == 0)
03403                         {
03404                                 eFlagRed = ent;
03405                         }
03406                         else if (!eFlagBlue && strcmp(ent->classname, "team_CTF_blueflag") == 0)
03407                         {
03408                                 eFlagBlue = ent;
03409                         }
03410 
03411                         if (eFlagRed && eFlagBlue)
03412                         {
03413                                 break;
03414                         }
03415                 }
03416 
03417                 i++;
03418         }
03419 
03420 #ifdef PAINFULLY_DEBUGGING_THROUGH_VM
03421         Com_Printf("BOT PATHING IS COMPLETE.\n");
03422 #endif
03423 }
03424 
03425 gentity_t *GetClosestSpawn(gentity_t *ent)
03426 {
03427         gentity_t       *spawn;
03428         gentity_t       *closestSpawn = NULL;
03429         float           closestDist = -1;
03430         int                     i = MAX_CLIENTS;
03431 
03432         spawn = NULL;
03433 
03434         while (i < level.num_entities)
03435         {
03436                 spawn = &g_entities[i];
03437 
03438                 if (spawn && spawn->inuse && (!Q_stricmp(spawn->classname, "info_player_start") || !Q_stricmp(spawn->classname, "info_player_deathmatch")) )
03439                 {
03440                         float checkDist;
03441                         vec3_t vSub;
03442 
03443                         VectorSubtract(ent->client->ps.origin, spawn->r.currentOrigin, vSub);
03444                         checkDist = VectorLength(vSub);
03445 
03446                         if (closestDist == -1 || checkDist < closestDist)
03447                         {
03448                                 closestSpawn = spawn;
03449                                 closestDist = checkDist;
03450                         }
03451                 }
03452 
03453                 i++;
03454         }
03455 
03456         return closestSpawn;
03457 }
03458 
03459 gentity_t *GetNextSpawnInIndex(gentity_t *currentSpawn)
03460 {
03461         gentity_t       *spawn;
03462         gentity_t       *nextSpawn = NULL;
03463         int                     i = currentSpawn->s.number+1;
03464 
03465         spawn = NULL;
03466 
03467         while (i < level.num_entities)
03468         {
03469                 spawn = &g_entities[i];
03470 
03471                 if (spawn && spawn->inuse && (!Q_stricmp(spawn->classname, "info_player_start") || !Q_stricmp(spawn->classname, "info_player_deathmatch")) )
03472                 {
03473                         nextSpawn = spawn;
03474                         break;
03475                 }
03476 
03477                 i++;
03478         }
03479 
03480         if (!nextSpawn)
03481         { //loop back around to 0
03482                 i = MAX_CLIENTS;
03483 
03484                 while (i < level.num_entities)
03485                 {
03486                         spawn = &g_entities[i];
03487 
03488                         if (spawn && spawn->inuse && (!Q_stricmp(spawn->classname, "info_player_start") || !Q_stricmp(spawn->classname, "info_player_deathmatch")) )
03489                         {
03490                                 nextSpawn = spawn;
03491                                 break;
03492                         }
03493 
03494                         i++;
03495                 }
03496         }
03497 
03498         return nextSpawn;
03499 }
03500 
03501 int AcceptBotCommand(char *cmd, gentity_t *pl)
03502 {
03503         int OptionalArgument, i;
03504         int FlagsFromArgument;
03505         char *OptionalSArgument, *RequiredSArgument;
03506 #ifndef _XBOX
03507         vmCvar_t mapname;
03508 #endif
03509 
03510         if (!gBotEdit)
03511         {
03512                 return 0;
03513         }
03514 
03515         OptionalArgument = 0;
03516         i = 0;
03517         FlagsFromArgument = 0;
03518         OptionalSArgument = NULL;
03519         RequiredSArgument = NULL;
03520 
03521         //if a waypoint editing related command is issued, bots will deactivate.
03522         //once bot_wp_save is issued and the trail is recalculated, bots will activate again.
03523 
03524         if (!pl || !pl->client)
03525         {
03526                 return 0;
03527         }
03528 
03529         if (Q_stricmp (cmd, "bot_wp_cmdlist") == 0) //lists all the bot waypoint commands.
03530         {
03531                 G_Printf(S_COLOR_YELLOW "bot_wp_add" S_COLOR_WHITE " - Add a waypoint (optional int parameter will insert the point after the specified waypoint index in a trail)\n\n");
03532                 G_Printf(S_COLOR_YELLOW "bot_wp_rem" S_COLOR_WHITE " - Remove a waypoint (removes last unless waypoint index is specified as a parameter)\n\n");
03533                 G_Printf(S_COLOR_YELLOW "bot_wp_addflagged" S_COLOR_WHITE " - Same as wp_add, but adds a flagged point (type bot_wp_addflagged for help)\n\n");
03534                 G_Printf(S_COLOR_YELLOW "bot_wp_switchflags" S_COLOR_WHITE " - Switches flags on an existing waypoint (type bot_wp_switchflags for help)\n\n");
03535                 G_Printf(S_COLOR_YELLOW "bot_wp_tele" S_COLOR_WHITE " - Teleport yourself to the specified waypoint's location\n");
03536                 G_Printf(S_COLOR_YELLOW "bot_wp_killoneways" S_COLOR_WHITE " - Removes oneway (backward and forward) flags on all waypoints in the level\n\n");
03537                 G_Printf(S_COLOR_YELLOW "bot_wp_save" S_COLOR_WHITE " - Saves all waypoint data into a file for later use\n");
03538 
03539                 return 1;
03540         }
03541 
03542         if (Q_stricmp (cmd, "bot_wp_add") == 0)
03543         {
03544                 gDeactivated = 1;
03545                 OptionalSArgument = ConcatArgs( 1 );
03546 
03547                 if (OptionalSArgument)
03548                 {
03549                         OptionalArgument = atoi(OptionalSArgument);
03550                 }
03551 
03552                 if (OptionalSArgument && OptionalSArgument[0])
03553                 {
03554                         CreateNewWP_InTrail(pl->client->ps.origin, 0, OptionalArgument);
03555                 }
03556                 else
03557                 {
03558                         CreateNewWP(pl->client->ps.origin, 0);
03559                 }
03560                 return 1;
03561         }
03562 
03563         if (Q_stricmp (cmd, "bot_wp_rem") == 0)
03564         {
03565                 gDeactivated = 1;
03566 
03567                 OptionalSArgument = ConcatArgs( 1 );
03568 
03569                 if (OptionalSArgument)
03570                 {
03571                         OptionalArgument = atoi(OptionalSArgument);
03572                 }
03573 
03574                 if (OptionalSArgument && OptionalSArgument[0])
03575                 {
03576                         RemoveWP_InTrail(OptionalArgument);
03577                 }
03578                 else
03579                 {
03580                         RemoveWP();
03581                 }
03582 
03583                 return 1;
03584         }
03585 
03586         if (Q_stricmp (cmd, "bot_wp_tele") == 0)
03587         {
03588                 gDeactivated = 1;
03589                 OptionalSArgument = ConcatArgs( 1 );
03590 
03591                 if (OptionalSArgument)
03592                 {
03593                         OptionalArgument = atoi(OptionalSArgument);
03594                 }
03595 
03596                 if (OptionalSArgument && OptionalSArgument[0])
03597                 {
03598                         TeleportToWP(pl, OptionalArgument);
03599                 }
03600                 else
03601                 {
03602                         G_Printf(S_COLOR_YELLOW "You didn't specify an index. Assuming last.\n");
03603                         TeleportToWP(pl, gWPNum-1);
03604                 }
03605                 return 1;
03606         }
03607 
03608         if (Q_stricmp (cmd, "bot_wp_spawntele") == 0)
03609         {
03610                 gentity_t *closestSpawn = GetClosestSpawn(pl);
03611 
03612                 if (!closestSpawn)
03613                 { //There should always be a spawn point..
03614                         return 1;
03615                 }
03616 
03617                 closestSpawn = GetNextSpawnInIndex(closestSpawn);
03618 
03619                 if (closestSpawn)
03620                 {
03621                         VectorCopy(closestSpawn->r.currentOrigin, pl->client->ps.origin);
03622                 }
03623                 return 1;
03624         }
03625 
03626         if (Q_stricmp (cmd, "bot_wp_addflagged") == 0)
03627         {
03628                 gDeactivated = 1;
03629 
03630                 RequiredSArgument = ConcatArgs( 1 );
03631 
03632                 if (!RequiredSArgument || !RequiredSArgument[0])
03633                 {
03634                         G_Printf(S_COLOR_YELLOW "Flag string needed for bot_wp_addflagged\nj - Jump point\nd - Duck point\nc - Snipe or camp standing\nf - Wait for func\nm - Do not move to when func is under\ns - Snipe or camp\nx - Oneway, forward\ny - Oneway, back\ng - Mission goal\nn - No visibility\nExample (for a point the bot would jump at, and reverse on when traveling a trail backwards):\nbot_wp_addflagged jx\n");
03635                         return 1;
03636                 }
03637 
03638                 while (RequiredSArgument[i])
03639                 {
03640                         if (RequiredSArgument[i] == 'j')
03641                         {
03642                                 FlagsFromArgument |= WPFLAG_JUMP;
03643                         }
03644                         else if (RequiredSArgument[i] == 'd')
03645                         {
03646                                 FlagsFromArgument |= WPFLAG_DUCK;
03647                         }
03648                         else if (RequiredSArgument[i] == 'c')
03649                         {
03650                                 FlagsFromArgument |= WPFLAG_SNIPEORCAMPSTAND;
03651                         }
03652                         else if (RequiredSArgument[i] == 'f')
03653                         {
03654                                 FlagsFromArgument |= WPFLAG_WAITFORFUNC;
03655                         }
03656                         else if (RequiredSArgument[i] == 's')
03657                         {
03658                                 FlagsFromArgument |= WPFLAG_SNIPEORCAMP;
03659                         }
03660                         else if (RequiredSArgument[i] == 'x')
03661                         {
03662                                 FlagsFromArgument |= WPFLAG_ONEWAY_FWD;
03663                         }
03664                         else if (RequiredSArgument[i] == 'y')
03665                         {
03666                                 FlagsFromArgument |= WPFLAG_ONEWAY_BACK;
03667                         }
03668                         else if (RequiredSArgument[i] == 'g')
03669                         {
03670                                 FlagsFromArgument |= WPFLAG_GOALPOINT;
03671                         }
03672                         else if (RequiredSArgument[i] == 'n')
03673                         {
03674                                 FlagsFromArgument |= WPFLAG_NOVIS;
03675                         }
03676                         else if (RequiredSArgument[i] == 'm')
03677                         {
03678                                 FlagsFromArgument |= WPFLAG_NOMOVEFUNC;
03679                         }
03680 
03681                         i++;
03682                 }
03683 
03684                 OptionalSArgument = ConcatArgs( 2 );
03685 
03686                 if (OptionalSArgument)
03687                 {
03688                         OptionalArgument = atoi(OptionalSArgument);
03689                 }
03690 
03691                 if (OptionalSArgument && OptionalSArgument[0])
03692                 {
03693                         CreateNewWP_InTrail(pl->client->ps.origin, FlagsFromArgument, OptionalArgument);
03694                 }
03695                 else
03696                 {
03697                         CreateNewWP(pl->client->ps.origin, FlagsFromArgument);
03698                 }
03699                 return 1;
03700         }
03701 
03702         if (Q_stricmp (cmd, "bot_wp_switchflags") == 0)
03703         {
03704                 gDeactivated = 1;
03705 
03706                 RequiredSArgument = ConcatArgs( 1 );
03707 
03708                 if (!RequiredSArgument || !RequiredSArgument[0])
03709                 {
03710                         G_Printf(S_COLOR_YELLOW "Flag string needed for bot_wp_switchflags\nType bot_wp_addflagged for a list of flags and their corresponding characters, or use 0 for no flags.\nSyntax: bot_wp_switchflags <flags> <n>\n");
03711                         return 1;
03712                 }
03713 
03714                 while (RequiredSArgument[i])
03715                 {
03716                         if (RequiredSArgument[i] == 'j')
03717                         {
03718                                 FlagsFromArgument |= WPFLAG_JUMP;
03719                         }
03720                         else if (RequiredSArgument[i] == 'd')
03721                         {
03722                                 FlagsFromArgument |= WPFLAG_DUCK;
03723                         }
03724                         else if (RequiredSArgument[i] == 'c')
03725                         {
03726                                 FlagsFromArgument |= WPFLAG_SNIPEORCAMPSTAND;
03727                         }
03728                         else if (RequiredSArgument[i] == 'f')
03729                         {
03730                                 FlagsFromArgument |= WPFLAG_WAITFORFUNC;
03731                         }
03732                         else if (RequiredSArgument[i] == 's')
03733                         {
03734                                 FlagsFromArgument |= WPFLAG_SNIPEORCAMP;
03735                         }
03736                         else if (RequiredSArgument[i] == 'x')
03737                         {
03738                                 FlagsFromArgument |= WPFLAG_ONEWAY_FWD;
03739                         }
03740                         else if (RequiredSArgument[i] == 'y')
03741                         {
03742                                 FlagsFromArgument |= WPFLAG_ONEWAY_BACK;
03743                         }
03744                         else if (RequiredSArgument[i] == 'g')
03745                         {
03746                                 FlagsFromArgument |= WPFLAG_GOALPOINT;
03747                         }
03748                         else if (RequiredSArgument[i] == 'n')
03749                         {
03750                                 FlagsFromArgument |= WPFLAG_NOVIS;
03751                         }
03752                         else if (RequiredSArgument[i] == 'm')
03753                         {
03754                                 FlagsFromArgument |= WPFLAG_NOMOVEFUNC;
03755                         }
03756 
03757                         i++;
03758                 }
03759 
03760                 OptionalSArgument = ConcatArgs( 2 );
03761 
03762                 if (OptionalSArgument)
03763                 {
03764                         OptionalArgument = atoi(OptionalSArgument);
03765                 }
03766 
03767                 if (OptionalSArgument && OptionalSArgument[0])
03768                 {
03769                         WPFlagsModify(OptionalArgument, FlagsFromArgument);
03770                 }
03771                 else
03772                 {
03773                         G_Printf(S_COLOR_YELLOW "Waypoint number (to modify) needed for bot_wp_switchflags\nSyntax: bot_wp_switchflags <flags> <n>\n");
03774                 }
03775                 return 1;
03776         }
03777 
03778         if (Q_stricmp (cmd, "bot_wp_killoneways") == 0)
03779         {
03780                 i = 0;
03781 
03782                 while (i < gWPNum)
03783                 {
03784                         if (gWPArray[i] && gWPArray[i]->inuse)
03785                         {
03786                                 if (gWPArray[i]->flags & WPFLAG_ONEWAY_FWD)
03787                                 {
03788                                         gWPArray[i]->flags -= WPFLAG_ONEWAY_FWD;
03789                                 }
03790                                 if (gWPArray[i]->flags & WPFLAG_ONEWAY_BACK)
03791                                 {
03792                                         gWPArray[i]->flags -= WPFLAG_ONEWAY_BACK;
03793                                 }
03794                         }
03795 
03796                         i++;
03797                 }
03798 
03799                 return 1;
03800         }
03801 
03802 #ifndef _XBOX
03803         if (Q_stricmp (cmd, "bot_wp_save") == 0)
03804         {
03805                 gDeactivated = 0;
03806                 trap_Cvar_Register( &mapname, "mapname", "", CVAR_SERVERINFO | CVAR_ROM );
03807                 SavePathData(mapname.string);
03808                 return 1;
03809         }
03810 #endif
03811 
03812         return 0;
03813 }