PageRenderTime 291ms CodeModel.GetById 3ms app.highlight 150ms RepoModel.GetById 86ms app.codeStats 4ms

/game/ai_wpnav.c

https://bitbucket.org/bshaw/jk3game_sdk_mingw
C | 3813 lines | 3083 code | 670 blank | 60 comment | 777 complexity | 87627928173391e4577879087fa43f7e MD5 | raw file
   1#include "g_local.h"
   2#include "q_shared.h"
   3#include "botlib.h"
   4#include "ai_main.h"
   5
   6float gWPRenderTime = 0;
   7float gDeactivated = 0;
   8float gBotEdit = 0;
   9int gWPRenderedFrame = 0;
  10
  11#include "../namespace_begin.h"
  12wpobject_t *gWPArray[MAX_WPARRAY_SIZE];
  13int gWPNum = 0;
  14#include "../namespace_end.h"
  15
  16int gLastPrintedIndex = -1;
  17
  18#ifndef _XBOX
  19nodeobject_t nodetable[MAX_NODETABLE_SIZE];
  20int nodenum; //so we can connect broken trails
  21#endif
  22
  23int gLevelFlags = 0;
  24
  25char *GetFlagStr( int flags )
  26{
  27	char *flagstr;
  28	int i;
  29
  30	flagstr = (char *)B_TempAlloc(128);
  31	i = 0;
  32
  33	if (!flags)
  34	{
  35		strcpy(flagstr, "none\0");
  36		goto fend;
  37	}
  38
  39	if (flags & WPFLAG_JUMP)
  40	{
  41		flagstr[i] = 'j';
  42		i++;
  43	}
  44
  45	if (flags & WPFLAG_DUCK)
  46	{
  47		flagstr[i] = 'd';
  48		i++;
  49	}
  50
  51	if (flags & WPFLAG_SNIPEORCAMPSTAND)
  52	{
  53		flagstr[i] = 'c';
  54		i++;
  55	}
  56
  57	if (flags & WPFLAG_WAITFORFUNC)
  58	{
  59		flagstr[i] = 'f';
  60		i++;
  61	}
  62
  63	if (flags & WPFLAG_SNIPEORCAMP)
  64	{
  65		flagstr[i] = 's';
  66		i++;
  67	}
  68
  69	if (flags & WPFLAG_ONEWAY_FWD)
  70	{
  71		flagstr[i] = 'x';
  72		i++;
  73	}
  74
  75	if (flags & WPFLAG_ONEWAY_BACK)
  76	{
  77		flagstr[i] = 'y';
  78		i++;
  79	}
  80
  81	if (flags & WPFLAG_GOALPOINT)
  82	{
  83		flagstr[i] = 'g';
  84		i++;
  85	}
  86
  87	if (flags & WPFLAG_NOVIS)
  88	{
  89		flagstr[i] = 'n';
  90		i++;
  91	}
  92
  93	if (flags & WPFLAG_NOMOVEFUNC)
  94	{
  95		flagstr[i] = 'm';
  96		i++;
  97	}
  98
  99	if (flags & WPFLAG_RED_FLAG)
 100	{
 101		if (i)
 102		{
 103			flagstr[i] = ' ';
 104			i++;
 105		}
 106		flagstr[i] = 'r';
 107		i++;
 108		flagstr[i] = 'e';
 109		i++;
 110		flagstr[i] = 'd';
 111		i++;
 112		flagstr[i] = ' ';
 113		i++;
 114		flagstr[i] = 'f';
 115		i++;
 116		flagstr[i] = 'l';
 117		i++;
 118		flagstr[i] = 'a';
 119		i++;
 120		flagstr[i] = 'g';
 121		i++;
 122	}
 123
 124	if (flags & WPFLAG_BLUE_FLAG)
 125	{
 126		if (i)
 127		{
 128			flagstr[i] = ' ';
 129			i++;
 130		}
 131		flagstr[i] = 'b';
 132		i++;
 133		flagstr[i] = 'l';
 134		i++;
 135		flagstr[i] = 'u';
 136		i++;
 137		flagstr[i] = 'e';
 138		i++;
 139		flagstr[i] = ' ';
 140		i++;
 141		flagstr[i] = 'f';
 142		i++;
 143		flagstr[i] = 'l';
 144		i++;
 145		flagstr[i] = 'a';
 146		i++;
 147		flagstr[i] = 'g';
 148		i++;
 149	}
 150
 151	if (flags & WPFLAG_SIEGE_IMPERIALOBJ)
 152	{
 153		if (i)
 154		{
 155			flagstr[i] = ' ';
 156			i++;
 157		}
 158		flagstr[i] = 's';
 159		i++;
 160		flagstr[i] = 'a';
 161		i++;
 162		flagstr[i] = 'g';
 163		i++;
 164		flagstr[i] = 'a';
 165		i++;
 166		flagstr[i] = '_';
 167		i++;
 168		flagstr[i] = 'i';
 169		i++;
 170		flagstr[i] = 'm';
 171		i++;
 172		flagstr[i] = 'p';
 173		i++;
 174	}
 175
 176	if (flags & WPFLAG_SIEGE_REBELOBJ)
 177	{
 178		if (i)
 179		{
 180			flagstr[i] = ' ';
 181			i++;
 182		}
 183		flagstr[i] = 's';
 184		i++;
 185		flagstr[i] = 'a';
 186		i++;
 187		flagstr[i] = 'g';
 188		i++;
 189		flagstr[i] = 'a';
 190		i++;
 191		flagstr[i] = '_';
 192		i++;
 193		flagstr[i] = 'r';
 194		i++;
 195		flagstr[i] = 'e';
 196		i++;
 197		flagstr[i] = 'b';
 198		i++;
 199	}
 200
 201	flagstr[i] = '\0';
 202
 203	if (i == 0)
 204	{
 205		strcpy(flagstr, "unknown\0");
 206	}
 207
 208fend:
 209	return flagstr;
 210}
 211
 212void G_TestLine(vec3_t start, vec3_t end, int color, int time)
 213{
 214	gentity_t *te;
 215
 216	te = G_TempEntity( start, EV_TESTLINE );
 217	VectorCopy(start, te->s.origin);
 218	VectorCopy(end, te->s.origin2);
 219	te->s.time2 = time;
 220	te->s.weapon = color;
 221	te->r.svFlags |= SVF_BROADCAST;
 222}
 223
 224void BotWaypointRender(void)
 225{
 226	int i, n;
 227	int inc_checker;
 228	int bestindex;
 229	int gotbestindex;
 230	float bestdist;
 231	float checkdist;
 232	gentity_t *plum;
 233	gentity_t *viewent;
 234	char *flagstr;
 235	vec3_t a;
 236
 237	if (!gBotEdit)
 238	{
 239		return;
 240	}
 241
 242	bestindex = 0;
 243
 244	if (gWPRenderTime > level.time)
 245	{
 246		goto checkprint;
 247	}
 248
 249	gWPRenderTime = level.time + 100;
 250
 251	i = gWPRenderedFrame;
 252	inc_checker = gWPRenderedFrame;
 253
 254	while (i < gWPNum)
 255	{
 256		if (gWPArray[i] && gWPArray[i]->inuse)
 257		{
 258			plum = G_TempEntity( gWPArray[i]->origin, EV_SCOREPLUM );
 259			plum->r.svFlags |= SVF_BROADCAST;
 260			plum->s.time = i;
 261			
 262			n = 0;
 263
 264			while (n < gWPArray[i]->neighbornum)
 265			{
 266				if (gWPArray[i]->neighbors[n].forceJumpTo && gWPArray[gWPArray[i]->neighbors[n].num])
 267				{
 268					G_TestLine(gWPArray[i]->origin, gWPArray[gWPArray[i]->neighbors[n].num]->origin, 0x0000ff, 5000);
 269				}
 270				n++;
 271			}
 272
 273			gWPRenderedFrame++;
 274		}
 275		else
 276		{
 277			gWPRenderedFrame = 0;
 278			break;
 279		}
 280
 281		if ((i - inc_checker) > 4)
 282		{
 283			break; //don't render too many at once
 284		}
 285		i++;
 286	}
 287
 288	if (i >= gWPNum)
 289	{
 290		gWPRenderTime = level.time + 1500; //wait a bit after we finish doing the whole trail
 291		gWPRenderedFrame = 0;
 292	}
 293
 294checkprint:
 295
 296	if (!bot_wp_info.value)
 297	{
 298		return;
 299	}
 300
 301	viewent = &g_entities[0]; //only show info to the first client
 302
 303	if (!viewent || !viewent->client)
 304	{ //client isn't in the game yet?
 305		return;
 306	}
 307
 308	bestdist = 256; //max distance for showing point info
 309	gotbestindex = 0;
 310
 311	i = 0;
 312
 313	while (i < gWPNum)
 314	{
 315		if (gWPArray[i] && gWPArray[i]->inuse)
 316		{
 317			VectorSubtract(viewent->client->ps.origin, gWPArray[i]->origin, a);
 318
 319			checkdist = VectorLength(a);
 320
 321			if (checkdist < bestdist)
 322			{
 323				bestdist = checkdist;
 324				bestindex = i;
 325				gotbestindex = 1;
 326			}
 327		}
 328		i++;
 329	}
 330
 331	if (gotbestindex && bestindex != gLastPrintedIndex)
 332	{
 333		flagstr = GetFlagStr(gWPArray[bestindex]->flags);
 334		gLastPrintedIndex = bestindex;
 335		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]));
 336		//GetFlagStr allocates 128 bytes for this, if it's changed then obviously this must be as well
 337		B_TempFree(128); //flagstr
 338
 339		plum = G_TempEntity( gWPArray[bestindex]->origin, EV_SCOREPLUM );
 340		plum->r.svFlags |= SVF_BROADCAST;
 341		plum->s.time = bestindex; //render it once
 342	}
 343	else if (!gotbestindex)
 344	{
 345		gLastPrintedIndex = -1;
 346	}
 347}
 348
 349void TransferWPData(int from, int to)
 350{
 351	if (!gWPArray[to])
 352	{
 353		gWPArray[to] = (wpobject_t *)B_Alloc(sizeof(wpobject_t));
 354	}
 355
 356	if (!gWPArray[to])
 357	{
 358		G_Printf(S_COLOR_RED "FATAL ERROR: Could not allocated memory for waypoint\n");
 359	}
 360
 361	gWPArray[to]->flags = gWPArray[from]->flags;
 362	gWPArray[to]->weight = gWPArray[from]->weight;
 363	gWPArray[to]->associated_entity = gWPArray[from]->associated_entity;
 364	gWPArray[to]->disttonext = gWPArray[from]->disttonext;
 365	gWPArray[to]->forceJumpTo = gWPArray[from]->forceJumpTo;
 366	gWPArray[to]->index = to;
 367	gWPArray[to]->inuse = gWPArray[from]->inuse;
 368	VectorCopy(gWPArray[from]->origin, gWPArray[to]->origin);
 369}
 370
 371void CreateNewWP(vec3_t origin, int flags)
 372{
 373	if (gWPNum >= MAX_WPARRAY_SIZE)
 374	{
 375		if (!g_RMG.integer)
 376		{
 377			G_Printf(S_COLOR_YELLOW "Warning: Waypoint limit hit (%i)\n", MAX_WPARRAY_SIZE);
 378		}
 379		return;
 380	}
 381
 382	if (!gWPArray[gWPNum])
 383	{
 384		gWPArray[gWPNum] = (wpobject_t *)B_Alloc(sizeof(wpobject_t));
 385	}
 386
 387	if (!gWPArray[gWPNum])
 388	{
 389		G_Printf(S_COLOR_RED "ERROR: Could not allocated memory for waypoint\n");
 390	}
 391
 392	gWPArray[gWPNum]->flags = flags;
 393	gWPArray[gWPNum]->weight = 0; //calculated elsewhere
 394	gWPArray[gWPNum]->associated_entity = ENTITYNUM_NONE; //set elsewhere
 395	gWPArray[gWPNum]->forceJumpTo = 0;
 396	gWPArray[gWPNum]->disttonext = 0; //calculated elsewhere
 397	gWPArray[gWPNum]->index = gWPNum;
 398	gWPArray[gWPNum]->inuse = 1;
 399	VectorCopy(origin, gWPArray[gWPNum]->origin);
 400	gWPNum++;
 401}
 402
 403void CreateNewWP_FromObject(wpobject_t *wp)
 404{
 405	int i;
 406
 407	if (gWPNum >= MAX_WPARRAY_SIZE)
 408	{
 409		return;
 410	}
 411
 412	if (!gWPArray[gWPNum])
 413	{
 414		gWPArray[gWPNum] = (wpobject_t *)B_Alloc(sizeof(wpobject_t));
 415	}
 416
 417	if (!gWPArray[gWPNum])
 418	{
 419		G_Printf(S_COLOR_RED "ERROR: Could not allocated memory for waypoint\n");
 420	}
 421
 422	gWPArray[gWPNum]->flags = wp->flags;
 423	gWPArray[gWPNum]->weight = wp->weight;
 424	gWPArray[gWPNum]->associated_entity = wp->associated_entity;
 425	gWPArray[gWPNum]->disttonext = wp->disttonext;
 426	gWPArray[gWPNum]->forceJumpTo = wp->forceJumpTo;
 427	gWPArray[gWPNum]->index = gWPNum;
 428	gWPArray[gWPNum]->inuse = 1;
 429	VectorCopy(wp->origin, gWPArray[gWPNum]->origin);
 430	gWPArray[gWPNum]->neighbornum = wp->neighbornum;
 431
 432	i = wp->neighbornum;
 433
 434	while (i >= 0)
 435	{
 436		gWPArray[gWPNum]->neighbors[i].num = wp->neighbors[i].num;
 437		gWPArray[gWPNum]->neighbors[i].forceJumpTo = wp->neighbors[i].forceJumpTo;
 438
 439		i--;
 440	}
 441
 442	if (gWPArray[gWPNum]->flags & WPFLAG_RED_FLAG)
 443	{
 444		flagRed = gWPArray[gWPNum];
 445		oFlagRed = flagRed;
 446	}
 447	else if (gWPArray[gWPNum]->flags & WPFLAG_BLUE_FLAG)
 448	{
 449		flagBlue = gWPArray[gWPNum];
 450		oFlagBlue = flagBlue;
 451	}
 452
 453	gWPNum++;
 454}
 455
 456void RemoveWP(void)
 457{
 458	if (gWPNum <= 0)
 459	{
 460		return;
 461	}
 462
 463	gWPNum--;
 464
 465	if (!gWPArray[gWPNum] || !gWPArray[gWPNum]->inuse)
 466	{
 467		return;
 468	}
 469
 470	//B_Free((wpobject_t *)gWPArray[gWPNum]);
 471	if (gWPArray[gWPNum])
 472	{
 473		memset( gWPArray[gWPNum], 0, sizeof(gWPArray[gWPNum]) );
 474	}
 475
 476	//gWPArray[gWPNum] = NULL;
 477
 478	if (gWPArray[gWPNum])
 479	{
 480		gWPArray[gWPNum]->inuse = 0;
 481	}
 482}
 483
 484void RemoveAllWP(void)
 485{
 486	while(gWPNum) {
 487		RemoveWP();
 488	}
 489}
 490
 491void RemoveWP_InTrail(int afterindex)
 492{
 493	int foundindex;
 494	int foundanindex;
 495	int didchange;
 496	int i;
 497
 498	foundindex = 0;
 499	foundanindex = 0;
 500	didchange = 0;
 501	i = 0;
 502
 503	if (afterindex < 0 || afterindex >= gWPNum)
 504	{
 505		G_Printf(S_COLOR_YELLOW "Waypoint number %i does not exist\n", afterindex);
 506		return;
 507	}
 508
 509	while (i < gWPNum)
 510	{
 511		if (gWPArray[i] && gWPArray[i]->inuse && gWPArray[i]->index == afterindex)
 512		{
 513			foundindex = i;
 514			foundanindex = 1;
 515			break;
 516		}
 517
 518		i++;
 519	}
 520
 521	if (!foundanindex)
 522	{
 523		G_Printf(S_COLOR_YELLOW "Waypoint index %i should exist, but does not (?)\n", afterindex);
 524		return;
 525	}
 526
 527	i = 0;
 528
 529	while (i <= gWPNum)
 530	{
 531		if (gWPArray[i] && gWPArray[i]->index == foundindex)
 532		{
 533			//B_Free(gWPArray[i]);
 534
 535			//Keep reusing the memory
 536			memset( gWPArray[i], 0, sizeof(gWPArray[i]) );
 537
 538			//gWPArray[i] = NULL;
 539			gWPArray[i]->inuse = 0;
 540			didchange = 1;
 541		}
 542		else if (gWPArray[i] && didchange)
 543		{
 544			TransferWPData(i, i-1);
 545			//B_Free(gWPArray[i]);
 546
 547			//Keep reusing the memory
 548			memset( gWPArray[i], 0, sizeof(gWPArray[i]) );
 549
 550			//gWPArray[i] = NULL;
 551			gWPArray[i]->inuse = 0;
 552		}
 553
 554		i++;
 555	}
 556	gWPNum--;
 557}
 558
 559int CreateNewWP_InTrail(vec3_t origin, int flags, int afterindex)
 560{
 561	int foundindex;
 562	int foundanindex;
 563	int i;
 564
 565	foundindex = 0;
 566	foundanindex = 0;
 567	i = 0;
 568
 569	if (gWPNum >= MAX_WPARRAY_SIZE)
 570	{
 571		if (!g_RMG.integer)
 572		{
 573			G_Printf(S_COLOR_YELLOW "Warning: Waypoint limit hit (%i)\n", MAX_WPARRAY_SIZE);
 574		}
 575		return 0;
 576	}
 577
 578	if (afterindex < 0 || afterindex >= gWPNum)
 579	{
 580		G_Printf(S_COLOR_YELLOW "Waypoint number %i does not exist\n", afterindex);
 581		return 0;
 582	}
 583
 584	while (i < gWPNum)
 585	{
 586		if (gWPArray[i] && gWPArray[i]->inuse && gWPArray[i]->index == afterindex)
 587		{
 588			foundindex = i;
 589			foundanindex = 1;
 590			break;
 591		}
 592
 593		i++;
 594	}
 595
 596	if (!foundanindex)
 597	{
 598		G_Printf(S_COLOR_YELLOW "Waypoint index %i should exist, but does not (?)\n", afterindex);
 599		return 0;
 600	}
 601
 602	i = gWPNum;
 603
 604	while (i >= 0)
 605	{
 606		if (gWPArray[i] && gWPArray[i]->inuse && gWPArray[i]->index != foundindex)
 607		{
 608			TransferWPData(i, i+1);
 609		}
 610		else if (gWPArray[i] && gWPArray[i]->inuse && gWPArray[i]->index == foundindex)
 611		{
 612			i++;
 613
 614			if (!gWPArray[i])
 615			{
 616				gWPArray[i] = (wpobject_t *)B_Alloc(sizeof(wpobject_t));
 617			}
 618
 619			gWPArray[i]->flags = flags;
 620			gWPArray[i]->weight = 0; //calculated elsewhere
 621			gWPArray[i]->associated_entity = ENTITYNUM_NONE; //set elsewhere
 622			gWPArray[i]->disttonext = 0; //calculated elsewhere
 623			gWPArray[i]->forceJumpTo = 0;
 624			gWPArray[i]->index = i;
 625			gWPArray[i]->inuse = 1;
 626			VectorCopy(origin, gWPArray[i]->origin);
 627			gWPNum++;
 628			break;
 629		}
 630
 631		i--;
 632	}
 633
 634	return 1;
 635}
 636
 637int CreateNewWP_InsertUnder(vec3_t origin, int flags, int afterindex)
 638{
 639	int foundindex;
 640	int foundanindex;
 641	int i;
 642
 643	foundindex = 0;
 644	foundanindex = 0;
 645	i = 0;
 646
 647	if (gWPNum >= MAX_WPARRAY_SIZE)
 648	{
 649		if (!g_RMG.integer)
 650		{
 651			G_Printf(S_COLOR_YELLOW "Warning: Waypoint limit hit (%i)\n", MAX_WPARRAY_SIZE);
 652		}
 653		return 0;
 654	}
 655
 656	if (afterindex < 0 || afterindex >= gWPNum)
 657	{
 658		G_Printf(S_COLOR_YELLOW "Waypoint number %i does not exist\n", afterindex);
 659		return 0;
 660	}
 661
 662	while (i < gWPNum)
 663	{
 664		if (gWPArray[i] && gWPArray[i]->inuse && gWPArray[i]->index == afterindex)
 665		{
 666			foundindex = i;
 667			foundanindex = 1;
 668			break;
 669		}
 670
 671		i++;
 672	}
 673
 674	if (!foundanindex)
 675	{
 676		G_Printf(S_COLOR_YELLOW "Waypoint index %i should exist, but does not (?)\n", afterindex);
 677		return 0;
 678	}
 679
 680	i = gWPNum;
 681
 682	while (i >= 0)
 683	{
 684		if (gWPArray[i] && gWPArray[i]->inuse && gWPArray[i]->index != foundindex)
 685		{
 686			TransferWPData(i, i+1);
 687		}
 688		else if (gWPArray[i] && gWPArray[i]->inuse && gWPArray[i]->index == foundindex)
 689		{
 690			//i++;
 691			TransferWPData(i, i+1);
 692
 693			if (!gWPArray[i])
 694			{
 695				gWPArray[i] = (wpobject_t *)B_Alloc(sizeof(wpobject_t));
 696			}
 697
 698			gWPArray[i]->flags = flags;
 699			gWPArray[i]->weight = 0; //calculated elsewhere
 700			gWPArray[i]->associated_entity = ENTITYNUM_NONE; //set elsewhere
 701			gWPArray[i]->disttonext = 0; //calculated elsewhere
 702			gWPArray[i]->forceJumpTo = 0;
 703			gWPArray[i]->index = i;
 704			gWPArray[i]->inuse = 1;
 705			VectorCopy(origin, gWPArray[i]->origin);
 706			gWPNum++;
 707			break;
 708		}
 709
 710		i--;
 711	}
 712
 713	return 1;
 714}
 715
 716void TeleportToWP(gentity_t *pl, int afterindex)
 717{
 718	int foundindex;
 719	int foundanindex;
 720	int i;
 721
 722	if (!pl || !pl->client)
 723	{
 724		return;
 725	}
 726
 727	foundindex = 0;
 728	foundanindex = 0;
 729	i = 0;
 730
 731	if (afterindex < 0 || afterindex >= gWPNum)
 732	{
 733		G_Printf(S_COLOR_YELLOW "Waypoint number %i does not exist\n", afterindex);
 734		return;
 735	}
 736
 737	while (i < gWPNum)
 738	{
 739		if (gWPArray[i] && gWPArray[i]->inuse && gWPArray[i]->index == afterindex)
 740		{
 741			foundindex = i;
 742			foundanindex = 1;
 743			break;
 744		}
 745
 746		i++;
 747	}
 748
 749	if (!foundanindex)
 750	{
 751		G_Printf(S_COLOR_YELLOW "Waypoint index %i should exist, but does not (?)\n", afterindex);
 752		return;
 753	}
 754
 755	VectorCopy(gWPArray[foundindex]->origin, pl->client->ps.origin);
 756
 757	return;
 758}
 759
 760void WPFlagsModify(int wpnum, int flags)
 761{
 762	if (wpnum < 0 || wpnum >= gWPNum || !gWPArray[wpnum] || !gWPArray[wpnum]->inuse)
 763	{
 764		G_Printf(S_COLOR_YELLOW "WPFlagsModify: Waypoint %i does not exist\n", wpnum);
 765		return;
 766	}
 767
 768	gWPArray[wpnum]->flags = flags;
 769}
 770
 771static int NotWithinRange(int base, int extent)
 772{
 773	if (extent > base && base+5 >= extent)
 774	{
 775		return 0;
 776	}
 777
 778	if (extent < base && base-5 <= extent)
 779	{
 780		return 0;
 781	}
 782
 783	return 1;
 784}
 785
 786#ifndef _XBOX
 787int NodeHere(vec3_t spot)
 788{
 789	int i;
 790
 791	i = 0;
 792
 793	while (i < nodenum)
 794	{
 795		if ((int)nodetable[i].origin[0] == (int)spot[0] &&
 796			(int)nodetable[i].origin[1] == (int)spot[1])
 797		{
 798			if ((int)nodetable[i].origin[2] == (int)spot[2] ||
 799				((int)nodetable[i].origin[2] < (int)spot[2] && (int)nodetable[i].origin[2]+5 > (int)spot[2]) ||
 800				((int)nodetable[i].origin[2] > (int)spot[2] && (int)nodetable[i].origin[2]-5 < (int)spot[2]))
 801			{
 802				return 1;
 803			}
 804		}
 805		i++;
 806	}
 807
 808	return 0;
 809}
 810#endif
 811
 812int CanGetToVector(vec3_t org1, vec3_t org2, vec3_t mins, vec3_t maxs)
 813{
 814	trace_t tr;
 815
 816	trap_Trace(&tr, org1, mins, maxs, org2, ENTITYNUM_NONE, MASK_SOLID);
 817
 818	if (tr.fraction == 1 && !tr.startsolid && !tr.allsolid)
 819	{
 820		return 1;
 821	}
 822
 823	return 0;
 824}
 825
 826#if 0
 827int CanGetToVectorTravel(vec3_t org1, vec3_t org2, vec3_t mins, vec3_t maxs)
 828{
 829	trace_t tr;
 830	vec3_t a, ang, fwd;
 831	vec3_t midpos, dmid;
 832	float startheight, midheight, fLen;
 833
 834	mins[2] = -13;
 835	maxs[2] = 13;
 836
 837	trap_Trace(&tr, org1, mins, maxs, org2, ENTITYNUM_NONE, MASK_SOLID);
 838
 839	if (tr.fraction != 1 || tr.startsolid || tr.allsolid)
 840	{
 841		return 0;
 842	}
 843
 844	VectorSubtract(org2, org1, a);
 845
 846	vectoangles(a, ang);
 847
 848	AngleVectors(ang, fwd, NULL, NULL);
 849
 850	fLen = VectorLength(a)/2;
 851
 852	midpos[0] = org1[0] + fwd[0]*fLen;
 853	midpos[1] = org1[1] + fwd[1]*fLen;
 854	midpos[2] = org1[2] + fwd[2]*fLen;
 855
 856	VectorCopy(org1, dmid);
 857	dmid[2] -= 1024;
 858
 859	trap_Trace(&tr, midpos, NULL, NULL, dmid, ENTITYNUM_NONE, MASK_SOLID);
 860
 861	startheight = org1[2] - tr.endpos[2];
 862
 863	VectorCopy(midpos, dmid);
 864	dmid[2] -= 1024;
 865
 866	trap_Trace(&tr, midpos, NULL, NULL, dmid, ENTITYNUM_NONE, MASK_SOLID);
 867
 868	if (tr.startsolid || tr.allsolid)
 869	{
 870		return 1;
 871	}
 872
 873	midheight = midpos[2] - tr.endpos[2];
 874
 875	if (midheight > startheight*2)
 876	{
 877		return 0; //too steep of a drop.. can't go on
 878	}
 879
 880	return 1;
 881}
 882#else
 883int CanGetToVectorTravel(vec3_t org1, vec3_t moveTo, vec3_t mins, vec3_t maxs)
 884//int ExampleAnimEntMove(gentity_t *self, vec3_t moveTo, float stepSize)
 885{
 886	trace_t tr;
 887	vec3_t stepTo;
 888	vec3_t stepSub;
 889	vec3_t stepGoal;
 890	vec3_t workingOrg;
 891	vec3_t lastIncrement;
 892	vec3_t finalMeasure;
 893	float stepSize = 0;
 894	float measureLength = 0;
 895	int didMove = 0;
 896	int traceMask = MASK_PLAYERSOLID;
 897	qboolean initialDone = qfalse;
 898
 899	VectorCopy(org1, workingOrg);
 900	VectorCopy(org1, lastIncrement);
 901
 902	VectorCopy(moveTo, stepTo);
 903	stepTo[2] = workingOrg[2];
 904
 905	VectorSubtract(stepTo, workingOrg, stepSub);
 906	stepSize = VectorLength(stepSub); //make the step size the length of the original positions without Z
 907
 908	VectorNormalize(stepSub);
 909
 910	while (!initialDone || didMove)
 911	{
 912		initialDone = qtrue;
 913		didMove = 0;
 914
 915		stepGoal[0] = workingOrg[0] + stepSub[0]*stepSize;
 916		stepGoal[1] = workingOrg[1] + stepSub[1]*stepSize;
 917		stepGoal[2] = workingOrg[2] + stepSub[2]*stepSize;
 918
 919		trap_Trace(&tr, workingOrg, mins, maxs, stepGoal, ENTITYNUM_NONE, traceMask);
 920
 921		if (!tr.startsolid && !tr.allsolid && tr.fraction)
 922		{
 923			vec3_t vecSub;
 924			VectorSubtract(workingOrg, tr.endpos, vecSub);
 925
 926			if (VectorLength(vecSub) > (stepSize/2))
 927			{
 928				workingOrg[0] = tr.endpos[0];
 929				workingOrg[1] = tr.endpos[1];
 930				//trap_LinkEntity(self);
 931				didMove = 1;
 932			}
 933		}
 934		
 935		if (didMove != 1)
 936		{ //stair check
 937			vec3_t trFrom;
 938			vec3_t trTo;
 939			vec3_t trDir;
 940			vec3_t vecMeasure;
 941
 942			VectorCopy(tr.endpos, trFrom);
 943			trFrom[2] += 16;
 944
 945			VectorSubtract(/*tr.endpos*/stepGoal, workingOrg, trDir);
 946			VectorNormalize(trDir);
 947			trTo[0] = tr.endpos[0] + trDir[0]*2;
 948			trTo[1] = tr.endpos[1] + trDir[1]*2;
 949			trTo[2] = tr.endpos[2] + trDir[2]*2;
 950			trTo[2] += 16;
 951
 952			VectorSubtract(trFrom, trTo, vecMeasure);
 953
 954			if (VectorLength(vecMeasure) > 1)
 955			{
 956				trap_Trace(&tr, trFrom, mins, maxs, trTo, ENTITYNUM_NONE, traceMask);
 957
 958				if (!tr.startsolid && !tr.allsolid && tr.fraction == 1)
 959				{ //clear trace here, probably up a step
 960					vec3_t trDown;
 961					vec3_t trUp;
 962					VectorCopy(tr.endpos, trUp);
 963					VectorCopy(tr.endpos, trDown);
 964					trDown[2] -= 16;
 965
 966					trap_Trace(&tr, trFrom, mins, maxs, trTo, ENTITYNUM_NONE, traceMask);
 967
 968					if (!tr.startsolid && !tr.allsolid)
 969					{ //plop us down on the step after moving up
 970						VectorCopy(tr.endpos, workingOrg);
 971						//trap_LinkEntity(self);
 972						didMove = 1;
 973					}
 974				}
 975			}
 976		}
 977
 978		VectorSubtract(lastIncrement, workingOrg, finalMeasure);
 979		measureLength = VectorLength(finalMeasure);
 980
 981		if (!measureLength)
 982		{ //no progress, break out. If last movement was a sucess didMove will equal 1.
 983			break;
 984		}
 985
 986		stepSize -= measureLength; //subtract the progress distance from the step size so we don't overshoot the mark.
 987		if (stepSize <= 0)
 988		{
 989			break;
 990		}
 991
 992		VectorCopy(workingOrg, lastIncrement);
 993	}
 994
 995	return didMove;
 996}
 997#endif
 998
 999#ifndef _XBOX
1000int ConnectTrail(int startindex, int endindex, qboolean behindTheScenes)
1001{
1002	int foundit;
1003	int cancontinue;
1004	int i;
1005	int failsafe;
1006	int successnodeindex;
1007	int insertindex;
1008	int prenodestart;
1009	byte extendednodes[MAX_NODETABLE_SIZE]; //for storing checked nodes and not trying to extend them each a bazillion times
1010	float fvecmeas;
1011	float baseheight;
1012	float branchDistance;
1013	float maxDistFactor = 256;
1014	vec3_t a;
1015	vec3_t startplace, starttrace;
1016	vec3_t mins, maxs;
1017	vec3_t testspot;
1018	vec3_t validspotpos;
1019	trace_t tr;
1020
1021	if (g_RMG.integer)
1022	{ //this might be temporary. Or not.
1023		if (!(gWPArray[startindex]->flags & WPFLAG_NEVERONEWAY) &&
1024			!(gWPArray[endindex]->flags & WPFLAG_NEVERONEWAY))
1025		{
1026			gWPArray[startindex]->flags |= WPFLAG_ONEWAY_FWD;
1027			gWPArray[endindex]->flags |= WPFLAG_ONEWAY_BACK;
1028		}
1029		return 0;
1030	}
1031
1032	if (!g_RMG.integer)
1033	{
1034		branchDistance = TABLE_BRANCH_DISTANCE;
1035	}
1036	else
1037	{
1038		branchDistance = 512; //be less precise here, terrain is fairly broad, and we don't want to take an hour precalculating
1039	}
1040
1041	if (g_RMG.integer)
1042	{
1043		maxDistFactor = 700;
1044	}
1045
1046	mins[0] = -15;
1047	mins[1] = -15;
1048	mins[2] = 0;
1049	maxs[0] = 15;
1050	maxs[1] = 15;
1051	maxs[2] = 0;
1052
1053	nodenum = 0;
1054	foundit = 0;
1055
1056	i = 0;
1057
1058	successnodeindex = 0;
1059
1060	while (i < MAX_NODETABLE_SIZE) //clear it out before using it
1061	{
1062		nodetable[i].flags = 0;
1063//		nodetable[i].index = 0;
1064		nodetable[i].inuse = 0;
1065		nodetable[i].neighbornum = 0;
1066		nodetable[i].origin[0] = 0;
1067		nodetable[i].origin[1] = 0;
1068		nodetable[i].origin[2] = 0;
1069		nodetable[i].weight = 0;
1070
1071		extendednodes[i] = 0;
1072
1073		i++;
1074	}
1075
1076	i = 0;
1077
1078	if (!behindTheScenes)
1079	{
1080		G_Printf(S_COLOR_YELLOW "Point %i is not connected to %i - Repairing...\n", startindex, endindex);
1081	}
1082
1083	VectorCopy(gWPArray[startindex]->origin, startplace);
1084
1085	VectorCopy(startplace, starttrace);
1086
1087	starttrace[2] -= 4096;
1088
1089	trap_Trace(&tr, startplace, NULL, NULL, starttrace, ENTITYNUM_NONE, MASK_SOLID);
1090
1091	baseheight = startplace[2] - tr.endpos[2];
1092
1093	cancontinue = 1;
1094
1095	VectorCopy(startplace, nodetable[nodenum].origin);
1096	nodetable[nodenum].weight = 1;
1097	nodetable[nodenum].inuse = 1;
1098//	nodetable[nodenum].index = nodenum;
1099	nodenum++;
1100
1101	while (nodenum < MAX_NODETABLE_SIZE && !foundit && cancontinue)
1102	{
1103		if (g_RMG.integer)
1104		{ //adjust the branch distance dynamically depending on the distance from the start and end points.
1105			vec3_t startDist;
1106			vec3_t endDist;
1107			float startDistf;
1108			float endDistf;
1109
1110			VectorSubtract(nodetable[nodenum-1].origin, gWPArray[startindex]->origin, startDist);
1111			VectorSubtract(nodetable[nodenum-1].origin, gWPArray[endindex]->origin, endDist);
1112
1113			startDistf = VectorLength(startDist);
1114			endDistf = VectorLength(endDist);
1115
1116			if (startDistf < 64 || endDistf < 64)
1117			{
1118				branchDistance = 64;
1119			}
1120			else if (startDistf < 128 || endDistf < 128)
1121			{
1122				branchDistance = 128;
1123			}
1124			else if (startDistf < 256 || endDistf < 256)
1125			{
1126				branchDistance = 256;
1127			}
1128			else if (startDistf < 512 || endDistf < 512)
1129			{
1130				branchDistance = 512;
1131			}
1132			else
1133			{
1134				branchDistance = 800;
1135			}
1136		}
1137		cancontinue = 0;
1138		i = 0;
1139		prenodestart = nodenum;
1140
1141		while (i < prenodestart)
1142		{
1143			if (extendednodes[i] != 1)
1144			{
1145				VectorSubtract(gWPArray[endindex]->origin, nodetable[i].origin, a);
1146				fvecmeas = VectorLength(a);
1147
1148				if (fvecmeas < 128 && CanGetToVector(gWPArray[endindex]->origin, nodetable[i].origin, mins, maxs))
1149				{
1150					foundit = 1;
1151					successnodeindex = i;
1152					break;
1153				}
1154
1155				VectorCopy(nodetable[i].origin, testspot);
1156				testspot[0] += branchDistance;
1157
1158				VectorCopy(testspot, starttrace);
1159
1160				starttrace[2] -= 4096;
1161
1162				trap_Trace(&tr, testspot, NULL, NULL, starttrace, ENTITYNUM_NONE, MASK_SOLID);
1163
1164				testspot[2] = tr.endpos[2]+baseheight;
1165
1166				if (!NodeHere(testspot) && !tr.startsolid && !tr.allsolid && CanGetToVector(nodetable[i].origin, testspot, mins, maxs))
1167				{
1168					VectorCopy(testspot, nodetable[nodenum].origin);
1169					nodetable[nodenum].inuse = 1;
1170//					nodetable[nodenum].index = nodenum;
1171					nodetable[nodenum].weight = nodetable[i].weight+1;
1172					nodetable[nodenum].neighbornum = i;
1173					if ((nodetable[i].origin[2] - nodetable[nodenum].origin[2]) > 50)
1174					{ //if there's a big drop, make sure we know we can't just magically fly back up
1175						nodetable[nodenum].flags = WPFLAG_ONEWAY_FWD;
1176					}
1177					nodenum++;
1178					cancontinue = 1;
1179				}
1180
1181				if (nodenum >= MAX_NODETABLE_SIZE)
1182				{
1183					break; //failure
1184				}
1185
1186				VectorCopy(nodetable[i].origin, testspot);
1187				testspot[0] -= branchDistance;
1188
1189				VectorCopy(testspot, starttrace);
1190
1191				starttrace[2] -= 4096;
1192
1193				trap_Trace(&tr, testspot, NULL, NULL, starttrace, ENTITYNUM_NONE, MASK_SOLID);
1194
1195				testspot[2] = tr.endpos[2]+baseheight;
1196
1197				if (!NodeHere(testspot) && !tr.startsolid && !tr.allsolid && CanGetToVector(nodetable[i].origin, testspot, mins, maxs))
1198				{
1199					VectorCopy(testspot, nodetable[nodenum].origin);
1200					nodetable[nodenum].inuse = 1;
1201//					nodetable[nodenum].index = nodenum;
1202					nodetable[nodenum].weight = nodetable[i].weight+1;
1203					nodetable[nodenum].neighbornum = i;
1204					if ((nodetable[i].origin[2] - nodetable[nodenum].origin[2]) > 50)
1205					{ //if there's a big drop, make sure we know we can't just magically fly back up
1206						nodetable[nodenum].flags = WPFLAG_ONEWAY_FWD;
1207					}
1208					nodenum++;
1209					cancontinue = 1;
1210				}
1211
1212				if (nodenum >= MAX_NODETABLE_SIZE)
1213				{
1214					break; //failure
1215				}
1216
1217				VectorCopy(nodetable[i].origin, testspot);
1218				testspot[1] += branchDistance;
1219
1220				VectorCopy(testspot, starttrace);
1221
1222				starttrace[2] -= 4096;
1223
1224				trap_Trace(&tr, testspot, NULL, NULL, starttrace, ENTITYNUM_NONE, MASK_SOLID);
1225
1226				testspot[2] = tr.endpos[2]+baseheight;
1227
1228				if (!NodeHere(testspot) && !tr.startsolid && !tr.allsolid && CanGetToVector(nodetable[i].origin, testspot, mins, maxs))
1229				{
1230					VectorCopy(testspot, nodetable[nodenum].origin);
1231					nodetable[nodenum].inuse = 1;
1232//					nodetable[nodenum].index = nodenum;
1233					nodetable[nodenum].weight = nodetable[i].weight+1;
1234					nodetable[nodenum].neighbornum = i;
1235					if ((nodetable[i].origin[2] - nodetable[nodenum].origin[2]) > 50)
1236					{ //if there's a big drop, make sure we know we can't just magically fly back up
1237						nodetable[nodenum].flags = WPFLAG_ONEWAY_FWD;
1238					}
1239					nodenum++;
1240					cancontinue = 1;
1241				}
1242
1243				if (nodenum >= MAX_NODETABLE_SIZE)
1244				{
1245					break; //failure
1246				}
1247
1248				VectorCopy(nodetable[i].origin, testspot);
1249				testspot[1] -= branchDistance;
1250
1251				VectorCopy(testspot, starttrace);
1252
1253				starttrace[2] -= 4096;
1254
1255				trap_Trace(&tr, testspot, NULL, NULL, starttrace, ENTITYNUM_NONE, MASK_SOLID);
1256
1257				testspot[2] = tr.endpos[2]+baseheight;
1258
1259				if (!NodeHere(testspot) && !tr.startsolid && !tr.allsolid && CanGetToVector(nodetable[i].origin, testspot, mins, maxs))
1260				{
1261					VectorCopy(testspot, nodetable[nodenum].origin);
1262					nodetable[nodenum].inuse = 1;
1263//					nodetable[nodenum].index = nodenum;
1264					nodetable[nodenum].weight = nodetable[i].weight+1;
1265					nodetable[nodenum].neighbornum = i;
1266					if ((nodetable[i].origin[2] - nodetable[nodenum].origin[2]) > 50)
1267					{ //if there's a big drop, make sure we know we can't just magically fly back up
1268						nodetable[nodenum].flags = WPFLAG_ONEWAY_FWD;
1269					}
1270					nodenum++;
1271					cancontinue = 1;
1272				}
1273
1274				if (nodenum >= MAX_NODETABLE_SIZE)
1275				{
1276					break; //failure
1277				}
1278
1279				extendednodes[i] = 1;
1280			}
1281
1282			i++;
1283		}
1284	}
1285
1286	if (!foundit)
1287	{
1288#ifndef _DEBUG //if debug just always print this.
1289		if (!behindTheScenes)
1290#endif
1291		{
1292			G_Printf(S_COLOR_RED "Could not link %i to %i, unreachable by node branching.\n", startindex, endindex);
1293		}
1294		gWPArray[startindex]->flags |= WPFLAG_ONEWAY_FWD;
1295		gWPArray[endindex]->flags |= WPFLAG_ONEWAY_BACK;
1296		if (!behindTheScenes)
1297		{
1298			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);
1299		}
1300
1301		/*while (nodenum >= 0)
1302		{
1303			if (nodetable[nodenum].origin[0] || nodetable[nodenum].origin[1] || nodetable[nodenum].origin[2])
1304			{
1305				CreateNewWP(nodetable[nodenum].origin, nodetable[nodenum].flags);
1306			}
1307
1308			nodenum--;
1309		}*/
1310		//The above code transfers nodes into the "rendered" waypoint array. Strictly for debugging.
1311
1312		if (!behindTheScenes)
1313		{ //just use what we have if we're auto-pathing the level
1314			return 0;
1315		}
1316		else
1317		{
1318			vec3_t endDist;
1319			int nCount = 0;
1320			int idealNode = -1;
1321			float bestDist = 0;
1322			float testDist;
1323
1324			if (nodenum <= 10)
1325			{ //not enough to even really bother.
1326				return 0;
1327			}
1328
1329			//Since it failed, find whichever node is closest to the desired end.
1330			while (nCount < nodenum)
1331			{
1332				VectorSubtract(nodetable[nCount].origin, gWPArray[endindex]->origin, endDist);
1333				testDist = VectorLength(endDist);
1334				if (idealNode == -1)
1335				{
1336					idealNode = nCount;
1337					bestDist = testDist;
1338					nCount++;
1339					continue;
1340				}
1341
1342				if (testDist < bestDist)
1343				{
1344					idealNode = nCount;
1345					bestDist = testDist;
1346				}
1347
1348				nCount++;
1349			}
1350
1351			if (idealNode == -1)
1352			{
1353				return 0;
1354			}
1355
1356			successnodeindex = idealNode;
1357		}
1358	}
1359
1360	i = successnodeindex;
1361	insertindex = startindex;
1362	failsafe = 0;
1363	VectorCopy(gWPArray[startindex]->origin, validspotpos);
1364
1365	while (failsafe < MAX_NODETABLE_SIZE && i < MAX_NODETABLE_SIZE && i >= 0)
1366	{
1367		VectorSubtract(validspotpos, nodetable[i].origin, a);
1368		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)) )
1369		{
1370			nodetable[i].flags |= WPFLAG_CALCULATED;
1371			if (!CreateNewWP_InTrail(nodetable[i].origin, nodetable[i].flags, insertindex))
1372			{
1373				if (!behindTheScenes)
1374				{
1375					G_Printf(S_COLOR_RED "Could not link %i to %i, waypoint limit hit.\n", startindex, endindex);
1376				}
1377				return 0;
1378			}
1379
1380			VectorCopy(nodetable[i].origin, validspotpos);
1381		}
1382
1383		if (i == 0)
1384		{
1385			break;
1386		}
1387
1388		i = nodetable[i].neighbornum;
1389
1390		failsafe++;
1391	}
1392
1393	if (!behindTheScenes)
1394	{
1395		G_Printf(S_COLOR_YELLOW "Finished connecting %i to %i.\n", startindex, endindex);
1396	}
1397
1398	return 1;
1399}
1400#endif
1401
1402int OpposingEnds(int start, int end)
1403{
1404	if (!gWPArray[start] || !gWPArray[start]->inuse || !gWPArray[end] || !gWPArray[end]->inuse)
1405	{
1406		return 0;
1407	}
1408
1409	if ((gWPArray[start]->flags & WPFLAG_ONEWAY_FWD) &&
1410		(gWPArray[end]->flags & WPFLAG_ONEWAY_BACK))
1411	{
1412		return 1;
1413	}
1414
1415	return 0;
1416}
1417
1418int DoorBlockingSection(int start, int end)
1419{ //if a door blocks the trail, we'll just have to assume the points on each side are in visibility when it's open
1420	trace_t tr;
1421	gentity_t *testdoor;
1422	int start_trace_index;
1423
1424	if (!gWPArray[start] || !gWPArray[start]->inuse || !gWPArray[end] || !gWPArray[end]->inuse)
1425	{
1426		return 0;
1427	}
1428
1429	trap_Trace(&tr, gWPArray[start]->origin, NULL, NULL, gWPArray[end]->origin, ENTITYNUM_NONE, MASK_SOLID);
1430
1431	if (tr.fraction == 1)
1432	{
1433		return 0;
1434	}
1435
1436	testdoor = &g_entities[tr.entityNum];
1437
1438	if (!testdoor)
1439	{
1440		return 0;
1441	}
1442
1443	if (!strstr(testdoor->classname, "func_"))
1444	{
1445		return 0;
1446	}
1447
1448	start_trace_index = tr.entityNum;
1449
1450	trap_Trace(&tr, gWPArray[end]->origin, NULL, NULL, gWPArray[start]->origin, ENTITYNUM_NONE, MASK_SOLID);
1451
1452	if (tr.fraction == 1)
1453	{
1454		return 0;
1455	}
1456
1457	if (start_trace_index == tr.entityNum)
1458	{
1459		return 1;
1460	}
1461
1462	return 0;
1463}
1464
1465#ifndef _XBOX
1466int RepairPaths(qboolean behindTheScenes)
1467{
1468	int i;
1469	int preAmount = 0;
1470	int ctRet;
1471	vec3_t a;
1472	float maxDistFactor = 400;
1473
1474	if (!gWPNum)
1475	{
1476		return 0;
1477	}
1478
1479	if (g_RMG.integer)
1480	{
1481		maxDistFactor = 800; //higher tolerance here.
1482	}
1483
1484	i = 0;
1485
1486	preAmount = gWPNum;
1487
1488	trap_Cvar_Update(&bot_wp_distconnect);
1489	trap_Cvar_Update(&bot_wp_visconnect);
1490
1491	while (i < gWPNum)
1492	{
1493		if (gWPArray[i] && gWPArray[i]->inuse && gWPArray[i+1] && gWPArray[i+1]->inuse)
1494		{
1495			VectorSubtract(gWPArray[i]->origin, gWPArray[i+1]->origin, a);
1496
1497			if (!(gWPArray[i+1]->flags & WPFLAG_NOVIS) &&
1498				!(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)
1499				!(gWPArray[i]->flags & WPFLAG_CALCULATED) && //don't calculate it again
1500				!OpposingEnds(i, i+1) &&
1501				((bot_wp_distconnect.value && VectorLength(a) > maxDistFactor) || (!OrgVisible(gWPArray[i]->origin, gWPArray[i+1]->origin, ENTITYNUM_NONE) && bot_wp_visconnect.value) ) &&
1502				!DoorBlockingSection(i, i+1))
1503			{
1504				ctRet = ConnectTrail(i, i+1, behindTheScenes);
1505
1506				if (gWPNum >= MAX_WPARRAY_SIZE)
1507				{ //Bad!
1508					gWPNum = MAX_WPARRAY_SIZE;
1509					break;
1510				}
1511
1512				/*if (!ctRet)
1513				{
1514					return 0;
1515				}*/ //we still want to write it..
1516			}
1517		}
1518
1519		i++;
1520	}
1521
1522	return 1;
1523}
1524#endif
1525
1526int OrgVisibleCurve(vec3_t org1, vec3_t mins, vec3_t maxs, vec3_t org2, int ignore)
1527{
1528	trace_t tr;
1529	vec3_t evenorg1;
1530
1531	VectorCopy(org1, evenorg1);
1532	evenorg1[2] = org2[2];
1533
1534	trap_Trace(&tr, evenorg1, mins, maxs, org2, ignore, MASK_SOLID);
1535
1536	if (tr.fraction == 1 && !tr.startsolid && !tr.allsolid)
1537	{
1538		trap_Trace(&tr, evenorg1, mins, maxs, org1, ignore, MASK_SOLID);
1539
1540		if (tr.fraction == 1 && !tr.startsolid && !tr.allsolid)
1541		{
1542			return 1;
1543		}
1544	}
1545
1546	return 0;
1547}
1548
1549int CanForceJumpTo(int baseindex, int testingindex, float distance)
1550{
1551	float heightdif;
1552	vec3_t xy_base, xy_test, v, mins, maxs;
1553	wpobject_t *wpBase = gWPArray[baseindex];
1554	wpobject_t *wpTest = gWPArray[testingindex];
1555
1556	mins[0] = -15;
1557	mins[1] = -15;
1558	mins[2] = -15; //-1
1559	maxs[0] = 15;
1560	maxs[1] = 15;
1561	maxs[2] = 15; //1
1562
1563	if (!wpBase || !wpBase->inuse || !wpTest || !wpTest->inuse)
1564	{
1565		return 0;
1566	}
1567
1568	if (distance > 400)
1569	{
1570		return 0;
1571	}
1572
1573	VectorCopy(wpBase->origin, xy_base);
1574	VectorCopy(wpTest->origin, xy_test);
1575
1576	xy_base[2] = xy_test[2];
1577
1578	VectorSubtract(xy_base, xy_test, v);
1579
1580	if (VectorLength(v) > MAX_NEIGHBOR_LINK_DISTANCE)
1581	{
1582		return 0;
1583	}
1584
1585	if ((int)wpBase->origin[2] < (int)wpTest->origin[2])
1586	{
1587		heightdif = wpTest->origin[2] - wpBase->origin[2];
1588	}
1589	else
1590	{
1591		return 0; //err..
1592	}
1593
1594	if (heightdif < 128)
1595	{ //don't bother..
1596		return 0;
1597	}
1598
1599	if (heightdif > 512)
1600	{ //too high
1601		return 0;
1602	}
1603
1604	if (!OrgVisibleCurve(wpBase->origin, mins, maxs, wpTest->origin, ENTITYNUM_NONE))
1605	{
1606		return 0;
1607	}
1608
1609	if (heightdif > 400)
1610	{
1611		return 3;
1612	}
1613	else if (heightdif > 256)
1614	{
1615		return 2;
1616	}
1617	else
1618	{
1619		return 1;
1620	}
1621}
1622
1623void CalculatePaths(void)
1624{
1625	int i;
1626	int c;
1627	int forceJumpable;
1628	int maxNeighborDist = MAX_NEIGHBOR_LINK_DISTANCE;
1629	float nLDist;
1630	vec3_t a;
1631	vec3_t mins, maxs;
1632
1633	if (!gWPNum)
1634	{
1635		return;
1636	}
1637
1638	if (g_RMG.integer)
1639	{
1640		maxNeighborDist = DEFAULT_GRID_SPACING + (DEFAULT_GRID_SPACING*0.5);
1641	}
1642
1643	mins[0] = -15;
1644	mins[1] = -15;
1645	mins[2] = -15; //-1
1646	maxs[0] = 15;
1647	maxs[1] = 15;
1648	maxs[2] = 15; //1
1649
1650	//now clear out all the neighbor data before we recalculate
1651	i = 0;
1652
1653	while (i < gWPNum)
1654	{
1655		if (gWPArray[i] && gWPArray[i]->inuse && gWPArray[i]->neighbornum)
1656		{
1657			while (gWPArray[i]->neighbornum >= 0)
1658			{
1659				gWPArray[i]->neighbors[gWPArray[i]->neighbornum].num = 0;
1660				gWPArray[i]->neighbors[gWPArray[i]->neighbornum].forceJumpTo = 0;
1661				gWPArray[i]->neighbornum--;
1662			}
1663			gWPArray[i]->neighbornum = 0;
1664		}
1665
1666		i++;
1667	}
1668
1669	i = 0;
1670
1671	while (i < gWPNum)
1672	{
1673		if (gWPArray[i] && gWPArray[i]->inuse)
1674		{
1675			c = 0;
1676
1677			while (c < gWPNum)
1678			{
1679				if (gWPArray[c] && gWPArray[c]->inuse && i != c &&
1680					NotWithinRange(i, c))
1681				{
1682					VectorSubtract(gWPArray[i]->origin, gWPArray[c]->origin, a);
1683
1684					nLDist = VectorLength(a);
1685					forceJumpable = CanForceJumpTo(i, c, nLDist);
1686
1687					if ((nLDist < maxNeighborDist || forceJumpable) &&
1688						((int)gWPArray[i]->origin[2] == (int)gWPArray[c]->origin[2] || forceJumpable) &&
1689						(OrgVisibleBox(gWPArray[i]->origin, mins, maxs, gWPArray[c]->origin, ENTITYNUM_NONE) || forceJumpable))
1690					{
1691						gWPArray[i]->neighbors[gWPArray[i]->neighbornum].num = c;
1692						if (forceJumpable && ((int)gWPArray[i]->origin[2] != (int)gWPArray[c]->origin[2] || nLDist < maxNeighborDist))
1693						{
1694							gWPArray[i]->neighbors[gWPArray[i]->neighbornum].forceJumpTo = 999;//forceJumpable; //FJSR
1695						}
1696						else
1697						{
1698							gWPArray[i]->neighbors[gWPArray[i]->neighbornum].forceJumpTo = 0;
1699						}
1700						gWPArray[i]->neighbornum++;
1701					}
1702
1703					if (gWPArray[i]->neighbornum >= MAX_NEIGHBOR_SIZE)
1704					{
1705						break;
1706					}
1707				}
1708				c++;
1709			}
1710		}
1711		i++;
1712	}
1713}
1714
1715gentity_t *GetObjectThatTargets(gentity_t *ent)
1716{
1717	gentity_t *next = NULL;
1718
1719	if (!ent->targetname)
1720	{
1721		return NULL;
1722	}
1723
1724	next = G_Find( next, FOFS(target), ent->targetname );
1725
1726	if (next)
1727	{
1728		return next;
1729	}
1730
1731	return NULL;
1732}
1733
1734void CalculateSiegeGoals(void)
1735{
1736	int i = 0;
1737	int looptracker = 0;
1738	int wpindex = 0;
1739	vec3_t dif;
1740	gentity_t *ent;
1741	gentity_t *tent = NULL, *t2ent = NULL;
1742
1743	while (i < level.num_entities)
1744	{
1745		ent = &g_entities[i];
1746
1747		tent = NULL;
1748
1749		if (ent && ent->classname && strcmp(ent->classname, "info_siege_objective") == 0)
1750		{
1751			tent = ent;
1752			t2ent = GetObjectThatTargets(tent);
1753			looptracker = 0;
1754
1755			while (t2ent && looptracker < 2048)
1756			{ //looptracker keeps us from getting stuck in case something is set up weird on this map
1757				tent = t2ent;
1758				t2ent = GetObjectThatTargets(tent);
1759				looptracker++;
1760			}
1761
1762			if (looptracker >= 2048)
1763			{ //something unpleasent has happened
1764				tent = NULL;
1765				break;
1766			}
1767		}
1768
1769		if (tent && ent && tent != ent)
1770		{ //tent should now be the object attached to the mission objective
1771			dif[0] = (tent->r.absmax[0]+tent->r.absmin[0])/2;
1772			dif[1] = (tent->r.absmax[1]+tent->r.absmin[1])/2;
1773			dif[2] = (tent->r.absmax[2]+tent->r.absmin[2])/2;
1774
1775			wpindex = GetNearestVisibleWP(dif, tent->s.number);
1776
1777			if (wpindex != -1 && gWPArray[wpindex] && gWPArray[wpindex]->inuse)
1778			{ //found the waypoint nearest the center of this objective-related object
1779				if (ent->side == SIEGETEAM_TEAM1)
1780				{
1781					gWPArray[wpindex]->flags |= WPFLAG_SIEGE_IMPERIALOBJ;
1782				}
1783				else
1784				{
1785					gWPArray[wpindex]->flags |= WPFLAG_SIEGE_REBELOBJ;
1786				}
1787
1788				gWPArray[wpindex]->associated_entity = tent->s.number;
1789			}
1790		}
1791
1792		i++;
1793	}
1794}
1795
1796float botGlobalNavWeaponWeights[WP_NUM_WEAPONS] =
1797{
1798	0,//WP_NONE,
1799
1800	0,//WP_STUN_BATON,
1801	0,//WP_MELEE
1802	0,//WP_SABER,				 // NOTE: lots of code assumes this is the first weapon (... which is crap) so be careful -Ste.
1803	0,//WP_BRYAR_PISTOL,
1804	3,//WP_BLASTER,
1805	5,//WP_DISRUPTOR,
1806	4,//WP_BOWCASTER,
1807	6,//WP_REPEATER,
1808	7,//WP_DEMP2,
1809	8,//WP_FLECHETTE,
1810	9,//WP_ROCKET_LAUNCHER,
1811	3,//WP_THERMAL,
1812	3,//WP_TRIP_MINE,
1813	3,//WP_DET_PACK,
1814	0//WP_EMPLACED_GUN,
1815};
1816
1817int GetNearestVisibleWPToItem(vec3_t org, int ignore)
1818{
1819	int i;
1820	float bestdist;
1821	float flLen;
1822	int bestindex;
1823	vec3_t a, mins, maxs;
1824
1825	i = 0;
1826	bestdist = 64; //has to be less than 64 units to the item or it isn't safe enough
1827	bestindex = -1;
1828
1829	mins[0] = -15;
1830	mins[1] = -15;
1831	mins[2] = 0;
1832	maxs[0] = 15;
1833	maxs[1] = 15;
1834	maxs[2] = 0;
1835
1836	while (i < gWPNum)
1837	{
1838		if (gWPArray[i] && gWPArray[i]->inuse &&
1839			gWPArray[i]->origin[2]-15 < org[2] &&
1840			gWPArray[i]->origin[2]+15 > org[2])
1841		{
1842			VectorSubtract(org, gWPArray[i]->origin, a);
1843			flLen = VectorLength(a);
1844
1845			if (flLen < bestdist && trap_InPVS(org, gWPArray[i]->origin) && OrgVisibleBox(org, mins, maxs, gWPArray[i]->origin, ignore))
1846			{
1847				bestdist = flLen;
1848				bestindex = i;
1849			}
1850		}
1851
1852		i++;
1853	}
1854
1855	return bestindex;
1856}
1857
1858void CalculateWeightGoals(void)
1859{ //set waypoint weights depending on weapon and item placement
1860	int i = 0;
1861	int wpindex = 0;
1862	gentity_t *ent;
1863	float weight;
1864
1865	trap_Cvar_Update(&bot_wp_clearweight);
1866
1867	if (bot_wp_clearweight.integer)
1868	{ //if set then flush out all weight/goal values before calculating them again
1869		while (i < gWPNum)
1870		{
1871			if (gWPArray[i] && gWPArray[i]->inuse)
1872			{
1873				gWPArray[i]->weight = 0;
1874
1875				if (gWPArray[i]->flags & WPFLAG_GOALPOINT)
1876				{
1877					gWPArray[i]->flags -= WPFLAG_GOALPOINT;
1878				}
1879			}
1880
1881			i++;
1882		}
1883	}
1884
1885	i = 0;
1886
1887	while (i < level.num_entities)
1888	{
1889		ent = &g_entities[i];
1890
1891		weight = 0;
1892
1893		if (ent && ent->classname)
1894		{
1895			if (strcmp(ent->classname, "item_seeker") == 0)
1896			{
1897				weight = 2;
1898			}
1899			else if (strcmp(ent->classname, "item_shield") == 0)
1900			{
1901				weight = 2;
1902			}
1903			else if (strcmp(ent->classname, "item_medpac") == 0)
1904			{
1905				weight = 2;
1906			}
1907			else if (strcmp(ent->classname, "item_sentry_gun") == 0)
1908			{
1909				weight = 2;
1910			}
1911			else if (strcmp(ent->classname, "item_force_enlighten_dark") == 0)
1912			{
1913				weight = 5;
1914			}
1915			else if (strcmp(ent->classname, "item_force_enlighten_light") == 0)
1916			{
1917				weight = 5;
1918			}
1919			else if (strcmp(ent->classname, "item_force_boon") == 0)
1920			{
1921				weight = 5;
1922			}
1923			else if (strcmp(ent->classname, "item_ysalimari") == 0)
1924			{
1925				weight = 2;
1926			}
1927			else if (strstr(ent->classname, "weapon_") && ent->item)
1928			{
1929				weight = botGlobalNavWeaponWeights[ent->item->giTag];
1930			}
1931			else if (ent->item && ent->item->giType == IT_AMMO)
1932			{
1933				weight = 3;
1934			}
1935		}
1936
1937		if (ent && weight)
1938		{
1939			wpindex = GetNearestVisibleWPToItem(ent->s.pos.trBase, ent->s.number);
1940
1941			if (wpindex != -1 && gWPArray[wpindex] && gWPArray[wpindex]->inuse)
1942			{ //found the waypoint nearest the center of this object
1943				gWPArray[wpindex]->weight = weight;
1944				gWPArray[wpindex]->flags |= WPFLAG_GOALPOINT;
1945				gWPArray[wpindex]->associated_entity = ent->s.number;
1946			}
1947		}
1948
1949		i++;
1950	}
1951}
1952
1953void CalculateJumpRoutes(void)
1954{
1955	int i = 0;
1956	float nheightdif = 0;
1957	float pheightdif = 0;
1958
1959	while (i < gWPNum)
1960	{
1961		if (gWPArray[i] && gWPArray[i]->inuse)
1962		{
1963			if (gWPArray[i]->flags & WPFLAG_JUMP)
1964			{
1965				nheightdif = 0;
1966				pheightdif = 0;
1967
1968				gWPArray[i]->forceJumpTo = 0;
1969
1970				if (gWPArray[i-1] && gWPArray[i-1]->inuse && (gWPArray[i-1]->origin[2]+16) < gWPArray[i]->origin[2])
1971				{
1972					nheightdif = (gWPArray[i]->origin[2] - gWPArray[i-1]->origin[2]);
1973				}
1974
1975				if (gWPArray[i+1] && gWPArray[i+1]->inuse && (gWPArray[i+1]->origin[2]+16) < gWPArray[i]->origin[2])
1976				{
1977					pheightdif = (gWPArray[i]->origin[2] - gWPArray[i+1]->origin[2]);
1978				}
1979
1980				if (nheightdif > pheightdif)
1981				{
1982					pheightdif = nheightdif;
1983				}
1984
1985				if (pheightdif)
1986				{
1987					if (pheightdif > 500)
1988					{
1989						gWPArray[i]->forceJumpTo = 999; //FORCE_LEVEL_3; //FJSR
1990					}
1991					else if (pheightdif > 256)
1992					{
1993						gWPArray[i]->forceJumpTo = 999; //FORCE_LEVEL_2; //FJSR
1994					}
1995					else if (pheightdif > 128)
1996					{
1997						gWPArray[i]->forceJumpTo = 999; //FORCE_LEVEL_1; //FJSR
1998					}
1999				}
2000			}
2001		}
2002
2003		i++;
2004	}
2005}
2006
2007int LoadPathData(const char *filename)
2008{
2009	fileHandle_t f;
2010	char *fileString;
2011	char *currentVar;
2012	char *routePath;
2013	wpobject_t thiswp;
2014	int len;
2015	int i, i_cv;
2016	int nei_num;
2017
2018	i = 0;
2019	i_cv = 0;
2020
2021	routePath = (char *)B_TempAlloc(1024);
2022
2023	Com_sprintf(routePath, 1024, "botroutes/%s.wnt\0", filename);
2024
2025	len = trap_FS_FOpenFile(routePath, &f, FS_READ);
2026
2027	B_TempFree(1024); //routePath
2028
2029	if (!f)
2030	{
2031		G_Printf(S_COLOR_YELLOW "Bot route data not found for %s\n", filename);
2032		return 2;
2033	}
2034
2035	if (len >= 524288)
2036	{
2037		G_Printf(S_COLOR_RED "Route file exceeds maximum length\n");
2038		return 0;
2039	}
2040
2041	fileString = (char *)B_TempAlloc(524288);
2042	currentVar = (char *)B_TempAlloc(2048);
2043
2044	trap_FS_Read(fileString, len, f);
2045
2046	if (fileString[i] == 'l')
2047	{ //contains a "levelflags" entry..
2048		char readLFlags[64];
2049		i_cv = 0;
2050
2051		while (fileString[i] != ' ')
2052		{
2053			i++;
2054		}
2055		i++;
2056		while (fileString[i] != '\n')
2057		{
2058			readLFlags[i_cv] = fileString[i];
2059			i_cv++;
2060			i++;
2061		}
2062		readLFlags[i_cv] = 0;
2063		i++;
2064
2065		gLevelFlags = atoi(readLFlags);
2066	}
2067	else
2068	{
2069		gLevelFlags = 0;
2070	}
2071
2072	while (i < len)
2073	{
2074		i_cv = 0;
2075
2076		thiswp.index = 0;
2077		thiswp.flags = 0;
2078		thiswp.inuse = 0;
2079		thiswp.neighbornum = 0;
2080		thiswp.origin[0] = 0;
2081		thiswp.origin[1] = 0;
2082		thiswp.origin[2] = 0;
2083		thiswp.weight = 0;
2084		thiswp.associated_entity = ENTITYNUM_NONE;
2085		thiswp.forceJumpTo = 0;
2086		thiswp.disttonext = 0;
2087		nei_num = 0;
2088
2089		while (nei_num < MAX_NEIGHBOR_SIZE)
2090		{
2091			thiswp.neighbors[nei_num].num = 0;
2092			thiswp.neighbors[nei_num].forceJumpTo = 0;
2093
2094			nei_num++;
2095		}
2096		
2097		while (fileString[i] != ' ')
2098		{
2099			currentVar[i_cv] = fileString[i];
2100			i_cv++;
2101			i++;
2102		}
2103		currentVar[i_cv] = '\0';
2104
2105		thiswp.index = atoi(currentVar);
2106
2107		i_cv = 0;
2108		i++;
2109
2110		while (fileString[i] != ' ')
2111		{
2112			currentVar[i_cv] = fileString[i];
2113			i_cv++;
2114			i++;
2115		}
2116		currentVar[i_cv] = '\0';
2117
2118		thiswp.flags = atoi(currentVar);
2119
2120		i_cv = 0;
2121		i++;
2122
2123		while (fileString[i] != ' ')
2124		{
2125			currentVar[i_cv] = fileString[i];
2126			i_cv++;
2127			i++;
2128		}
2129		currentVar[i_cv] = '\0';
2130
2131		thiswp.weight = atof(currentVar);
2132
2133		i_cv = 0;
2134		i++;
2135		i++;
2136
2137		while (fileString[i] != ' ')
2138		{
2139			currentVar[i_cv] = fileString[i];
2140			i_cv++;
2141			i++;
2142		}
2143		currentVar[i_cv] = '\0';
2144
2145		thiswp.origin[0] = atof(currentVar);
2146
2147		i_cv = 0;
2148		i++;
2149
2150		while (fileString[i] != ' ')
2151		{
2152			currentVar[i_cv] = fileString[i];
2153			i_cv++;
2154			i++;
2155		}
2156		currentVar[i_cv] = '\0';
2157
2158		thiswp.origin[1] = atof(currentVar);
2159
2160		i_cv = 0;
2161		i++;
2162
2163		while (fileString[i] != ')')
2164		{
2165			currentVar[i_cv] = fileString[i];
2166			i_cv++;
2167			i++;
2168		}
2169		currentVar[i_cv] = '\0';
2170
2171		thiswp.origin[2] = atof(currentVar);
2172
2173		i += 4;
2174
2175		while (fileString[i] != '}')
2176		{
2177			i_cv = 0;
2178			while (fileString[i] != ' ' && fileString[i] != '-')
2179			{
2180				currentVar[i_cv] = fileString[i];
2181				i_cv++;
2182				i++;
2183			}
2184			currentVar[i_cv] = '\0';
2185
2186			thiswp.neighbors[thiswp.neighbornum].num = atoi(currentVar);
2187
2188			if (fileString[i] == '-')
2189			{
2190				i_cv = 0;
2191				i++;
2192
2193				while (fileString[i] != ' ')
2194				{
2195					currentVar[i_cv] = fileString[i];
2196					i_cv++;
2197					i++;
2198				}
2199				currentVar[i_cv] = '\0';
2200
2201				thiswp.neighbors[thiswp.neighbornum].forceJumpTo = 999; //atoi(currentVar); //FJSR
2202			}
2203			else
2204			{
2205				thiswp.neighbors[thiswp.neighbornum].forceJumpTo = 0;
2206			}
2207
2208			thiswp.neighbornum++;
2209
2210			i++;
2211		}
2212
2213		i_cv = 0;
2214		i++;
2215		i++;
2216
2217		while (fileString[i] != '\n')
2218		{
2219			currentVar[i_cv] = fileString[i];
2220			i_cv++;
2221			i++;
2222		}
2223		currentVar[i_cv] = '\0';
2224
2225		thiswp.disttonext = atof(currentVar);
2226
2227		CreateNewWP_FromObject(&thiswp);
2228		i++;
2229	}
2230
2231	B_TempFree(524288); //fileString
2232	B_TempFree(2048); //currentVar
2233
2234	trap_FS_FCloseFile(f);
2235
2236	if (g_gametype.integer == GT_SIEGE)
2237	{
2238		CalculateSiegeGoals();
2239	}
2240
2241	CalculateWeightGoals();
2242	//calculate weights for idle activity goals when
2243	//the bot has absolutely nothing else to do
2244
2245	CalculateJumpRoutes();
2246	//Look at jump points and mark them as requiring
2247	//force jumping as needed
2248
2249	return 1;
2250}
2251
2252void FlagObjects(void)
2253{
2254	int i = 0, bestindex = 0, found = 0;
2255	float bestdist = 999999, tlen = 0;
2256	gentity_t *flag_red, *flag_blue, *ent;
2257	vec3_t a, mins, maxs;
2258	trace_t tr;
2259
2260	flag_red = NULL;
2261	flag_blue = NULL;
2262
2263	mins[0] = -15;
2264	mins[1] = -15;
2265	mins[2] = -5;
2266	maxs[0] = 15;
2267	maxs[1] = 15;
2268	maxs[2] = 5;
2269
2270	while (i < level.num_entities)
2271	{
2272		ent = &g_entities[i];
2273
2274		if (ent && ent->inuse && ent->classname)
2275		{
2276			if (!flag_red && strcmp(ent->classname, "team_CTF_redflag") == 0)
2277			{
2278				flag_red = ent;
2279			}
2280			else if (!flag_blue && strcmp(ent->classname, "team_CTF_blueflag") == 0)
2281			{
2282				flag_blue = ent;
2283			}
2284
2285			if (flag_red && flag_blue)
2286			{
2287				break;
2288			}
2289		}
2290
2291		i++;
2292	}
2293
2294	i = 0;
2295
2296	if (!flag_red || !flag_blue)
2297	{
2298		return;
2299	}
2300
2301	while (i < gWPNum)
2302	{
2303		if (gWPArray[i] && gWPArray[i]->inuse)
2304		{
2305			VectorSubtract(flag_red->s.pos.trBase, gWPArray[i]->origin, a);
2306			tlen = VectorLength(a);
2307
2308			if (tlen < bestdist)
2309			{
2310				trap_Trace(&tr, flag_red->s.pos.trBase, mins, maxs, gWPArray[i]->origin, flag_red->s.number, MASK_SOLID);
2311
2312				if (tr.fraction == 1 || tr.entityNum == flag_red->s.number)
2313				{
2314					bestdist = tlen;
2315					bestindex = i;
2316					found = 1;
2317				}
2318			}
2319
2320		}
2321
2322		i++;
2323	}
2324
2325	if (found)
2326	{
2327		gWPArray[bestindex]->flags |= WPFLAG_RED_FLAG;
2328		flagRed = gWPArray[bestindex];
2329		oFlagRed = flagRed;
2330		eFlagRed = flag_red;
2331	}
2332
2333	bestdist = 999999;
2334	bestindex = 0;
2335	found = 0;
2336	i = 0;
2337
2338	while (i < gWPNum)
2339	{
2340		if (gWPArray[i] && gWPArray[i]->inuse)
2341		{
2342			VectorSubtract(flag_blue->s.pos.trBase, gWPArray[i]->origin, a);
2343			tlen = VectorLength(a);
2344
2345			if (tlen < bestdist)
2346			{
2347				trap_Trace(&tr, flag_blue->s.pos.trBase, mins, maxs, gWPArray[i]->origin, flag_blue->s.number, MASK_SOLID);
2348
2349				if (tr.fraction == 1 || tr.entityNum == flag_blue->s.number)
2350				{
2351					bestdist = tlen;
2352					bestindex = i;
2353					found = 1;
2354				}
2355			}
2356
2357		}
2358
2359		i++;
2360	}
2361
2362	if (found)
2363	{
2364		gWPArray[bestindex]->flags |= WPFLAG_BLUE_FLAG;
2365		flagBlue = gWPArray[bestindex];
2366		oFlagBlue = flagBlue;
2367		eFlagBlue = flag_blue;
2368	}
2369}
2370
2371#ifndef _XBOX
2372int SavePathData(const char *filename)
2373{
2374	fileHandle_t f;
2375	char *fileString;
2376	char *storeString;
2377	char *routePath;
2378	vec3_t a;
2379	float flLen;
2380	int i, s, n;
2381
2382	fileString = NULL;
2383	i = 0;
2384	s = 0;
2385
2386	if (!gWPNum)
2387	{
2388		return 0;
2389	}
2390
2391	routePath = (char *)B_TempAlloc(1024);
2392
2393	Com_sprintf(routePath, 1024, "botroutes/%s.wnt\0", filename);
2394
2395	trap_FS_FOpenFile(routePath, &f, FS_WRITE);
2396
2397	B_TempFree(1024); //routePath
2398
2399	if (!f)
2400	{
2401		G_Printf(S_COLOR_RED "ERROR: Could not open file to write path data\n");
2402		return 0;
2403	}
2404
2405	if (!RepairPaths(qfalse)) //check if we can see all waypoints from the last. If not, try to branch over.
2406	{
2407		trap_FS_FCloseFile(f);
2408		return 0;
2409	}
2410
2411	CalculatePaths(); //make everything nice and connected before saving
2412
2413	FlagObjects(); //currently only used for flagging waypoints nearest CTF flags
2414
2415	fileString = (char *)B_TempAlloc(524288);
2416	storeString = (char *)B_TempAlloc(4096);
2417
2418	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]);
2419
2420	n = 0;
2421
2422	while (n < gWPArray[i]->neighbornum)
2423	{
2424		if (gWPArray[i]->neighbors[n].forceJumpTo)
2425		{
2426			Com_sprintf(storeString, 4096, "%s%i-%i ", storeString, gWPArray[i]->neighbors[n].num, gWPArray[i]->neighbors[n].forceJumpTo);
2427		}
2428		else
2429		{
2430			Com_sprintf(storeString, 4096, "%s%i ", storeString, gWPArray[i]->neighbors[n].num);
2431		}
2432		n++;
2433	}
2434
2435	if (gWPArray[i+1] && gWPArray[i+1]->inuse && gWPArray[i+1]->index)
2436	{
2437		VectorSubtract(gWPArray[i]->origin, gWPArray[i+1]->origin, a);
2438		flLen = VectorLength(a);
2439	}
2440	else
2441	{
2442		flLen = 0;
2443	}
2444
2445	gWPArray[i]->disttonext = flLen;
2446
2447	Com_sprintf(fileString, 524288, "%s} %f\n", fileString, flLen);
2448
2449	i++;
2450
2451	while (i < gWPNum)
2452	{
2453		//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]);
2454		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]);
2455
2456		n = 0;
2457
2458		while (n < gWPArray[i]->neighbornum)
2459		{
2460			if (gWPArray[i]->neighbors[n].forceJumpTo)
2461			{
2462				Com_sprintf(storeString, 4096, "%s%i-%i ", storeString, gWPArray[i]->neighbors[n].num, gWPArray[i]->neighbors[n].forceJumpTo);
2463			}
2464			else
2465			{
2466				Com_sprintf(storeString, 4096, "%s%i ", storeString, gWPArray[i]->neighbors[n].num);
2467			}
2468			n++;
2469		}
2470
2471		if (gWPArray[i+1] && gWPArray[i+1]->inuse && gWPArray[i+1]->index)
2472		{
2473			VectorSubtract(gWPArray[i]->origin, gWPArray[i+1]->origin, a);
2474			flLen = VectorLength(a);
2475		}
2476		else
2477		{
2478			flLen = 0;
2479		}
2480
2481		gWPArray[i]->disttonext = flLen;
2482
2483		Com_sprintf(storeString, 4096, "%s} %f\n", storeString, flLen);
2484
2485		strcat(fileString, storeString);
2486
2487		i++;
2488	}
2489
2490	trap_FS_Write(fileString, strlen(fileString), f);
2491
2492	B_TempFree(524288); //fileString
2493	B_TempFree(4096); //storeString
2494
2495	trap_FS_FCloseFile(f);
2496
2497	G_Printf("Path data has been saved and updated. You may need to restart the level for some things to be properly calculated.\n");
2498
2499	return 1;
2500}
2501#endif
2502
2503//#define PAINFULLY_DEBUGGING_THROUGH_VM
2504
2505#define MAX_SPAWNPOINT_ARRAY 64
2506int gSpawnPointNum = 0;
2507gentity_t *gSpawnPoints[MAX_SPAWNPOINT_ARRAY];
2508
2509#ifndef _XBOX
2510int G_NearestNodeToPoint(vec3_t point)
2511{ //gets the node on the entire grid which is nearest to the specified coordinates.
2512	vec3_t vSub;
2513	int bestIndex = -1;
2514	int i = 0;
2515	float bestDist = 0;
2516	float testDist = 0;
2517
2518	while (i < nodenum)
2519	{
2520		VectorSubtract(nodetable[i].origin, point, vSub);
2521		testDist = VectorLength(vSub);
2522
2523		if (bestIndex == -1)
2524		{
2525			bestIndex = i;
2526			bestDist = testDist;
2527
2528			i++;
2529			continue;
2530		}
2531
2532		if (testDist < bestDist)
2533		{
2534			bestIndex = i;
2535			bestDist = testDist;
2536		}
2537		i++;
2538	}
2539
2540	return bestIndex;
2541}
2542#endif
2543
2544#ifndef _XBOX
2545void G_NodeClearForNext(void)
2546{ //reset nodes for the next trail connection.
2547	int i = 0;
2548
2549	while (i < nodenum)
2550	{
2551		nodetable[i].flags = 0;
2552		nodetable[i].weight = 99999;
2553
2554		i++;
2555	}
2556}
2557
2558void G_NodeClearFlags(void)
2559{ //only clear out flags so nodes can be reused.
2560	int i = 0;
2561
2562	while (i < nodenum)
2563	{
2564		nodetable[i].flags = 0;
2565
2566		i++;
2567	}
2568}
2569
2570int G_NodeMatchingXY(float x, float y)
2571{ //just get the first unflagged node with the matching x,y coordinates.
2572	int i = 0;
2573
2574	while (i < nodenum)
2575	{
2576		if (nodetable[i].origin[0] == x &&
2577			nodetable[i].origin[1] == y &&
2578			!nodetable[i].flags)
2579		{
2580			return i;
2581		}
2582
2583		i++;
2584	}
2585
2586	return -1;
2587}
2588
2589int G_NodeMatchingXY_BA(int x, int y, int final)
2590{ //return the node with the lowest weight that matches the specified x,y coordinates.
2591	int i = 0;
2592	int bestindex = -1;
2593	float bestWeight = 9999;
2594
2595	while (i < nodenum)
2596	{
2597		if ((int)nodetable[i].origin[0] == x &&
2598			(int)nodetable[i].origin[1] == y &&
2599			!nodetable[i].flags &&
2600			((nodetable[i].weight < bestWeight) || (i == final)))
2601		{
2602			if (i == final)
2603			{
2604				return i;
2605			}
2606			bestindex = i;
2607			bestWeight = nodetable[i].weight;
2608		}
2609
2610		i++;
2611	}
2612
2613	return bestindex;
2614}
2615
2616int G_RecursiveConnection(int start, int end, int weight, qboolean traceCheck, float baseHeight)
2617{
2618	int indexDirections[4]; //0 == down, 1 == up, 2 == left, 3 == right
2619	int recursiveIndex = -1;
2620	int i = 0;
2621	int passWeight = weight;
2622	vec2_t givenXY;
2623	trace_t tr;
2624
2625	passWeight++;
2626	nodetable[start].weight = passWeight;
2627
2628	givenXY[0] = nodetable[start].origin[0];
2629	givenXY[1] = nodetable[start].origin[1];
2630	givenXY[0] -= DEFAULT_GRID_SPACING;
2631	indexDirections[0] = G_NodeMatchingXY(givenXY[0], givenXY[1]);
2632
2633	givenXY[0] = nodetable[start].origin[0];
2634	givenXY[1] = nodetable[start].origin[1];
2635	givenXY[0] += DEFAULT_GRID_SPACING;
2636	indexDirections[1] = G_NodeMatchingXY(givenXY[0], givenXY[1]);
2637
2638	givenXY[0] = nodetable[start].origin[0];
2639	givenXY[1] = nodetable[start].origin[1];
2640	givenXY[1] -= DEFAULT_GRID_SPACING;
2641	indexDirections[2] = G_NodeMatchingXY(givenXY[0], givenXY[1]);
2642
2643	givenXY[0] = nodetable[start].origin[0];
2644	givenXY[1] = nodetable[start].origin[1];
2645	givenXY[1] += DEFAULT_GRID_SPACING;
2646	indexDirections[3] = G_NodeMatchingXY(givenXY[0], givenXY[1]);
2647
2648	i = 0;
2649	while (i < 4)
2650	{
2651		if (indexDirections[i] == end)
2652		{ //we've connected all the way to the destination.
2653			return indexDirections[i];
2654		}
2655
2656		if (indexDirections[i] != -1 && nodetable[indexDirections[i]].flags)
2657		{ //this point is already used, so it's not valid.
2658			indexDirections[i] = -1;
2659		}
2660		else if (indexDirections[i] != -1)
2661		{ //otherwise mark it as used.
2662			nodetable[indexDirections[i]].flags = 1;
2663		}
2664
2665		if (indexDirections[i] != -1 && traceCheck)
2666		{ //if we care about trace visibility between nodes, perform the check and mark as not valid if the trace isn't clear.
2667			trap_Trace(&tr, nodetable[start].origin, NULL, NULL, nodetable[indexDirections[i]].origin, ENTITYNUM_NONE, CONTENTS_SOLID);
2668
2669			if (tr.fraction != 1)
2670			{
2671				indexDirections[i] = -1;
2672			}
2673		}
2674
2675		if (indexDirections[i] != -1)
2676		{ //it's still valid, so keep connecting via this point.
2677			recursiveIndex = G_RecursiveConnection(indexDirections[i], end, passWeight, traceCheck, baseHeight);
2678		}
2679
2680		if (recursiveIndex != -1)
2681		{ //the result of the recursive check was valid, so return it.
2682			return recursiveIndex;
2683		}
2684
2685		i++;
2686	}
2687
2688	return recursiveIndex;
2689}
2690
2691#ifdef DEBUG_NODE_FILE
2692void G_DebugNodeFile()
2693{
2694	fileHandle_t f;
2695	int i = 0;
2696	float placeX;
2697	char fileString[131072];
2698	gentity_t *terrain = G_Find( NULL, FOFS(classname), "terrain" );
2699
2700	fileString[0] = 0;
2701
2702	placeX = terrain->r.absmin[0];
2703
2704	while (i < nodenum)
2705	{
2706		strcat(fileString, va("%i-%f ", i, nodetable[i].weight));
2707		placeX += DEFAULT_GRID_SPACING;
2708
2709		if (placeX >= terrain->r.absmax[0])
2710		{
2711			strcat(fileString, "\n");
2712			placeX = terrain->r.absmin[0];
2713		}
2714		i++;
2715	}
2716
2717	trap_FS_FOpenFile("ROUTEDEBUG.txt", &f, FS_WRITE);
2718	trap_FS_Write(fileString, strlen(fileString), f);
2719	trap_FS_FCloseFile(f);
2720}
2721#endif
2722
2723#endif
2724//#define ASCII_ART_DEBUG
2725//#define ASCII_ART_NODE_DEBUG
2726
2727#ifdef ASCII_ART_DEBUG
2728
2729#define ALLOWABLE_DEBUG_FILE_SIZE 1048576
2730
2731void CreateAsciiTableRepresentation()
2732{ //Draw a text grid of the entire waypoint array (useful for debugging final waypoint placement)
2733	fileHandle_t f;
2734	int i = 0;
2735	int sP = 0;
2736	int placeX;
2737	int placeY;
2738	int oldX;
2739	int oldY;
2740	char fileString[ALLOWABLE_DEBUG_FILE_SIZE];
2741	char bChr = '+';
2742	gentity_t *terrain = G_Find( NULL, FOFS(classname), "terrain" );
2743
2744	placeX = terrain->r.absmin[0];
2745	placeY = terrain->r.absmin[1];
2746
2747	oldX = placeX-1;
2748	oldY = placeY-1;
2749
2750	while (placeY < terrain->r.absmax[1])
2751	{
2752		while (placeX < terrain->r.absmax[0])
2753		{
2754			qboolean gotit = qfalse;
2755
2756			i = 0;
2757			while (i < gWPNum)
2758			{
2759				if (((int)gWPArray[i]->origin[0] <= placeX && (int)gWPArray[i]->origin[0] > oldX) &&
2760					((int)gWPArray[i]->origin[1] <= placeY && (int)gWPArray[i]->origin[1] > oldY))
2761				{
2762					gotit = qtrue;
2763					break;
2764				}
2765				i++;
2766			}
2767
2768			if (gotit)
2769			{
2770				if (gWPArray[i]->flags & WPFLAG_ONEWAY_FWD)
2771				{
2772					bChr = 'F';
2773				}
2774				else if (gWPArray[i]->flags & WPFLAG_ONEWAY_BACK)
2775				{
2776					bChr = 'B';
2777				}
2778				else
2779				{
2780					bChr = '+';
2781				}
2782
2783				if (gWPArray[i]->index < 10)
2784				{
2785					fileString[sP] = bChr;
2786					fileString[sP+1] = '0';
2787					fileString[sP+2] = '0';
2788					fileString[sP+3] = va("%i", gWPArray[i]->index)[0];
2789				}
2790				else if (gWPArray[i]->index < 100)
2791				{
2792					char *vastore = va("%i", gWPArray[i]->index);
2793
2794					fileString[sP] = bChr;
2795					fileString[sP+1] = '0';
2796					fileString[sP+2] = vastore[0];
2797					fileString[sP+3] = vastore[1];
2798				}
2799				else if (gWPArray[i]->index < 1000)
2800				{
2801					char *vastore = va("%i", gWPArray[i]->index);
2802
2803					fileString[sP] = bChr;
2804					fileString[sP+1] = vastore[0];
2805					fileString[sP+2] = vastore[1];
2806					fileString[sP+3] = vastore[2];
2807				}
2808				else
2809				{
2810					fileString[sP] = 'X';
2811					fileString[sP+1] = 'X';
2812					fileString[sP+2] = 'X';
2813					fileString[sP+3] = 'X';
2814				}
2815			}
2816			else
2817			{
2818				fileString[sP] = '-';
2819				fileString[sP+1] = '-';
2820				fileString[sP+2] = '-';
2821				fileString[sP+3] = '-';
2822			}
2823
2824			sP += 4;
2825
2826			if (sP >= ALLOWABLE_DEBUG_FILE_SIZE-16)
2827			{
2828				break;
2829			}
2830			oldX = placeX;
2831			placeX += DEFAULT_GRID_SPACING;
2832		}
2833
2834		placeX = terrain->r.absmin[0];
2835		oldX = placeX-1;
2836		fileString[sP] = '\n';
2837		sP++;
2838
2839		if (sP >= ALLOWABLE_DEBUG_FILE_SIZE-16)
2840		{
2841			break;
2842		}
2843
2844		oldY = placeY;
2845		placeY += DEFAULT_GRID_SPACING;
2846	}
2847
2848	fileString[sP] = 0;
2849
2850	trap_FS_FOpenFile("ROUTEDRAWN.txt", &f, FS_WRITE);
2851	trap_FS_Write(fileString, strlen(fileString), f);
2852	trap_FS_FCloseFile(f);
2853}
2854
2855void CreateAsciiNodeTableRepresentation(int start, int end)
2856{ //draw a text grid of a single node path, from point A to Z.
2857	fileHandle_t f;
2858	int i = 0;
2859	int sP = 0;
2860	int placeX;
2861	int placeY;
2862	int oldX;
2863	int oldY;
2864	char fileString[ALLOWABLE_DEBUG_FILE_SIZE];
2865	gentity_t *terrain = G_Find( NULL, FOFS(classname), "terrain" );
2866
2867	placeX = terrain->r.absmin[0];
2868	placeY = terrain->r.absmin[1];
2869
2870	oldX = placeX-1;
2871	oldY = placeY-1;
2872
2873	while (placeY < terrain->r.absmax[1])
2874	{
2875		while (placeX < terrain->r.absmax[0])
2876		{
2877			qboolean gotit = qfalse;
2878
2879			i = 0;
2880			while (i < nodenum)
2881			{
2882				if (((int)nodetable[i].origin[0] <= placeX && (int)nodetable[i].origin[0] > oldX) &&
2883					((int)nodetable[i].origin[1] <= placeY && (int)nodetable[i].origin[1] > oldY))
2884				{
2885					gotit = qtrue;
2886					break;
2887				}
2888				i++;
2889			}
2890
2891			if (gotit)
2892			{
2893				if (i == start)
2894				{ //beginning of the node trail
2895					fileString[sP] = 'A';
2896					fileString[sP+1] = 'A';
2897					fileString[sP+2] = 'A';
2898					fileString[sP+3] = 'A';
2899				}
2900				else if (i == end)
2901				{ //destination of the node trail
2902					fileString[sP] = 'Z';
2903					fileString[sP+1] = 'Z';
2904					fileString[sP+2] = 'Z';
2905					fileString[sP+3] = 'Z';
2906				}
2907				else if (nodetable[i].weight < 10)
2908				{
2909					fileString[sP] = '+';
2910					fileString[sP+1] = '0';
2911					fileString[sP+2] = '0';
2912					fileString[sP+3] = va("%f", nodetable[i].weight)[0];
2913				}
2914				else if (nodetable[i].weight < 100)
2915				{
2916					char *vastore = va("%f", nodetable[i].weight);
2917
2918					fileString[sP] = '+';
2919					fileString[sP+1] = '0';
2920					fileString[sP+2] = vastore[0];
2921					fileString[sP+3] = vastore[1];
2922				}
2923				else if (nodetable[i].weight < 1000)
2924				{
2925					char *vastore = va("%f", nodetable[i].weight);
2926
2927					fileString[sP] = '+';
2928					fileString[sP+1] = vastore[0];
2929					fileString[sP+2] = vastore[1];
2930					fileString[sP+3] = vastore[2];
2931				}
2932				else
2933				{
2934					fileString[sP] = 'X';
2935					fileString[sP+1] = 'X';
2936					fileString[sP+2] = 'X';
2937					fileString[sP+3] = 'X';
2938				}
2939			}
2940			else
2941			{
2942				fileString[sP] = '-';
2943				fileString[sP+1] = '-';
2944				fileString[sP+2] = '-';
2945				fileString[sP+3] = '-';
2946			}
2947
2948			sP += 4;
2949
2950			if (sP >= ALLOWABLE_DEBUG_FILE_SIZE-16)
2951			{
2952				break;
2953			}
2954			oldX = placeX;
2955			placeX += DEFAULT_GRID_SPACING;
2956		}
2957
2958		placeX = terrain->r.absmin[0];
2959		oldX = placeX-1;
2960		fileString[sP] = '\n';
2961		sP++;
2962
2963		if (sP >= ALLOWABLE_DEBUG_FILE_SIZE-16)
2964		{
2965			break;
2966		}
2967
2968		oldY = placeY;
2969		placeY += DEFAULT_GRID_SPACING;
2970	}
2971
2972	fileString[sP] = 0;
2973
2974	trap_FS_FOpenFile("ROUTEDRAWN.txt", &f, FS_WRITE);
2975	trap_FS_Write(fileString, strlen(fileString), f);
2976	trap_FS_FCloseFile(f);
2977}
2978#endif
2979
2980#ifndef _XBOX
2981qboolean G_BackwardAttachment(int start, int finalDestination, int insertAfter)
2982{ //After creating a node path between 2 points, this function links the 2 points with actual waypoint data.
2983	int indexDirections[4]; //0 == down, 1 == up, 2 == left, 3 == right
2984	int i = 0;
2985	int lowestWeight = 9999;
2986	int desiredIndex = -1;
2987	vec2_t givenXY;
2988
2989	givenXY[0] = nodetable[start].origin[0];
2990	givenXY[1] = nodetable[start].origin[1];
2991	givenXY[0] -= DEFAULT_GRID_SPACING;
2992	indexDirections[0] = G_NodeMatchingXY_BA(givenXY[0], givenXY[1], finalDestination);
2993
2994	givenXY[0] = nodetable[start].origin[0];
2995	givenXY[1] = nodetable[start].origin[1];
2996	givenXY[0] += DEFAULT_GRID_SPACING;
2997	indexDirections[1] = G_NodeMatchingXY_BA(givenXY[0], givenXY[1], finalDestination);
2998
2999	givenXY[0] = nodetable[start].origin[0];
3000	givenXY[1] = nodetable[start].origin[1];
3001	givenXY[1] -= DEFAULT_GRID_SPACING;
3002	indexDirections[2] = G_NodeMatchingXY_BA(givenXY[0], givenXY[1], finalDestination);
3003
3004	givenXY[0] = nodetable[start].origin[0];
3005	givenXY[1] = nodetable[start].origin[1];
3006	givenXY[1] += DEFAULT_GRID_SPACING;
3007	indexDirections[3] = G_NodeMatchingXY_BA(givenXY[0], givenXY[1], finalDestination);
3008
3009	while (i < 4)
3010	{
3011		if (indexDirections[i] != -1)
3012		{
3013			if (indexDirections[i] == finalDestination)
3014			{ //hooray, we've found the original point and linked all the way back to it.
3015				CreateNewWP_InsertUnder(nodetable[start].origin, 0, insertAfter);
3016				CreateNewWP_InsertUnder(nodetable[indexDirections[i]].origin, 0, insertAfter);
3017				return qtrue;
3018			}
3019
3020			if (nodetable[indexDirections[i]].weight < lowestWeight && nodetable[indexDirections[i]].weight && !nodetable[indexDirections[i]].flags /*&& (nodetable[indexDirections[i]].origin[2]-64 < nodetable[start].origin[2])*/)
3021			{
3022				desiredIndex = indexDirections[i];
3023				lowestWeight = nodetable[indexDirections[i]].weight;
3024			}
3025		}
3026		i++;
3027	}
3028
3029	if (desiredIndex != -1)
3030	{ //Create a waypoint here, and then recursively call this function for the next neighbor with the lowest weight.
3031		if (gWPNum < 3900)
3032		{
3033			CreateNewWP_InsertUnder(nodetable[start].origin, 0, insertAfter);
3034		}
3035		else
3036		{
3037#ifdef PAINFULLY_DEBUGGING_THROUGH_VM
3038			Com_Printf("WAYPOINTS FULL\n");
3039#endif
3040			return qfalse;
3041		}
3042
3043		nodetable[start].flags = 1;
3044		return G_BackwardAttachment(desiredIndex, finalDestination, insertAfter);
3045	}
3046
3047	return qfalse;
3048}
3049
3050
3051#ifdef _DEBUG
3052#define PATH_TIME_DEBUG
3053#endif
3054
3055void G_RMGPathing(void)
3056{ //Generate waypoint information on-the-fly for the random mission.
3057	float placeX, placeY, placeZ;
3058	int i = 0;
3059	int gridSpacing = DEFAULT_GRID_SPACING;
3060	int nearestIndex = 0;
3061	int nearestIndexForNext = 0;
3062#ifdef PATH_TIME_DEBUG
3063	int startTime = 0;
3064	int endTime = 0;
3065#endif
3066	vec3_t downVec, trMins, trMaxs;
3067	trace_t tr;
3068	gentity_t *terrain = G_Find( NULL, FOFS(classname), "terrain" );
3069
3070	if (!terrain || !terrain->inuse || terrain->s.eType != ET_TERRAIN)
3071	{
3072		G_Printf("Error: RMG with no terrain!\n");
3073		return;
3074	}
3075
3076#ifdef PATH_TIME_DEBUG
3077	startTime = trap_Milliseconds();
3078#endif
3079
3080	nodenum = 0;
3081	memset(&nodetable, 0, sizeof(nodetable));
3082
3083	VectorSet(trMins, -15, -15, DEFAULT_MINS_2);
3084	VectorSet(trMaxs, 15, 15, DEFAULT_MAXS_2);
3085
3086	placeX = terrain->r.absmin[0];
3087	placeY = terrain->r.absmin[1];
3088	placeZ = terrain->r.absmax[2]-400;
3089
3090	//skim through the entirety of the terrain limits and drop nodes, removing
3091	//nodes that start in solid or fall too high on the terrain.
3092	while (placeY < terrain->r.absmax[1])
3093	{
3094		if (nodenum >= MAX_NODETABLE_SIZE)
3095		{
3096			break;
3097		}
3098
3099		while (placeX < terrain->r.absmax[0])
3100		{
3101			if (nodenum >= MAX_NODETABLE_SIZE)
3102			{
3103				break;
3104			}
3105
3106			nodetable[nodenum].origin[0] = placeX;
3107			nodetable[nodenum].origin[1] = placeY;
3108			nodetable[nodenum].origin[2] = placeZ;
3109
3110			VectorCopy(nodetable[nodenum].origin, downVec);
3111			downVec[2] -= 3000;
3112			trap_Trace(&tr, nodetable[nodenum].origin, trMins, trMaxs, downVec, ENTITYNUM_NONE, MASK_SOLID);
3113
3114			if ((tr.entityNum >= ENTITYNUM_WORLD || g_entities[tr.entityNum].s.eType == ET_TERRAIN) && tr.endpos[2] < terrain->r.absmin[2]+750)
3115			{ //only drop nodes on terrain directly
3116				VectorCopy(tr.endpos, nodetable[nodenum].origin);
3117				nodenum++;
3118			}
3119			else
3120			{
3121				VectorClear(nodetable[nodenum].origin);
3122			}
3123
3124			placeX += gridSpacing;
3125		}
3126
3127		placeX = terrain->r.absmin[0];
3128		placeY += gridSpacing;
3129	}
3130
3131#ifdef PAINFULLY_DEBUGGING_THROUGH_VM
3132	Com_Printf("NODE GRID PLACED ON TERRAIN\n");
3133#endif
3134
3135	G_NodeClearForNext();
3136
3137	//The grid has been placed down, now use it to connect the points in the level.
3138	while (i < gSpawnPointNum-1)
3139	{
3140		if (!gSpawnPoints[i] || !gSpawnPoints[i]->inuse || !gSpawnPoints[i+1] || !gSpawnPoints[i+1]->inuse)
3141		{
3142			i++;
3143			continue;
3144		}
3145
3146		nearestIndex = G_NearestNodeToPoint(gSpawnPoints[i]->s.origin);
3147		nearestIndexForNext = G_NearestNodeToPoint(gSpawnPoints[i+1]->s.origin);
3148
3149#ifdef PAINFULLY_DEBUGGING_THROUGH_VM
3150		Com_Printf("%i GOT %i INDEX WITH %i INDEX FOR NEXT\n", nearestIndex, nearestIndexForNext);
3151#endif
3152
3153		if (nearestIndex == -1 || nearestIndexForNext == -1)
3154		{ //Looks like there is no grid data near one of the points. Ideally, this will never happen.
3155			i++;
3156			continue;
3157		}
3158
3159		if (nearestIndex == nearestIndexForNext)
3160		{ //Two spawn points on top of each other? We don't need to do both points, keep going until the next differs.
3161			i++;
3162			continue;
3163		}
3164
3165		//So, nearestIndex is now the node for the spawn point we're on, and nearestIndexForNext is the
3166		//node we want to get to from here.
3167
3168		//For now I am going to branch out mindlessly, but I will probably want to use some sort of A* algorithm
3169		//here to lessen the time taken.
3170		if (G_RecursiveConnection(nearestIndex, nearestIndexForNext, 0, qtrue, terrain->r.absmin[2]) != nearestIndexForNext)
3171		{ //failed to branch to where we want. Oh well, try it without trace checks.
3172			G_NodeClearForNext();
3173
3174#ifdef PAINFULLY_DEBUGGING_THROUGH_VM
3175			Com_Printf("FAILED RECURSIVE WITH TRACES\n");
3176#endif
3177
3178			if (G_RecursiveConnection(nearestIndex, nearestIndexForNext, 0, qfalse, terrain->r.absmin[2]) != nearestIndexForNext)
3179			{ //still failed somehow. Just disregard this point.
3180#ifdef PAINFULLY_DEBUGGING_THROUGH_VM
3181				Com_Printf("FAILED RECURSIVE -WITHOUT- TRACES (?!?!)\n");
3182#endif
3183				G_NodeClearForNext();
3184				i++;
3185				continue;
3186			}
3187		}
3188
3189		//Now our node array is set up so that highest reasonable weight is the destination node, and 2 is next to the original index,
3190		//so trace back to that point.
3191		G_NodeClearFlags();
3192
3193#ifdef ASCII_ART_DEBUG
3194#ifdef ASCII_ART_NODE_DEBUG
3195		CreateAsciiNodeTableRepresentation(nearestIndex, nearestIndexForNext);
3196#endif
3197#endif
3198		if (G_BackwardAttachment(nearestIndexForNext, nearestIndex, gWPNum-1))
3199		{ //successfully connected the trail from nearestIndex to nearestIndexForNext
3200			if (gSpawnPoints[i+1]->inuse && gSpawnPoints[i+1]->item &&
3201				gSpawnPoints[i+1]->item->giType == IT_TEAM)
3202			{ //This point is actually a CTF flag.
3203				if (gSpawnPoints[i+1]->item->giTag == PW_REDFLAG || gSpawnPoints[i+1]->item->giTag == PW_BLUEFLAG)
3204				{ //Place a waypoint on the flag next in the trail, so the nearest grid point will link to it.
3205					CreateNewWP_InsertUnder(gSpawnPoints[i+1]->s.origin, WPFLAG_NEVERONEWAY, gWPNum-1);
3206				}
3207			}
3208
3209#ifdef PAINFULLY_DEBUGGING_THROUGH_VM
3210			Com_Printf("BACKWARD ATTACHMENT %i SUCCESS\n", i);
3211#endif
3212		}
3213		else
3214		{
3215#ifdef PAINFULLY_DEBUGGING_THROUGH_VM
3216			Com_Printf("BACKWARD ATTACHMENT FAILED\n");
3217#endif
3218			break;
3219		}
3220
3221#ifdef DEBUG_NODE_FILE
3222		G_DebugNodeFile();
3223#endif
3224
3225		G_NodeClearForNext();
3226		i++;
3227	}
3228
3229#ifdef PAINFULLY_DEBUGGING_THROUGH_VM
3230	Com_Printf("FINISHED RMG AUTOPATH\n");
3231#endif
3232
3233#ifdef PAINFULLY_DEBUGGING_THROUGH_VM
3234	Com_Printf("BEGINNING PATH REPAIR...\n");
3235#endif
3236	RepairPaths(qtrue); //this has different behaviour for RMG and will just flag all points one way that don't trace to each other.
3237#ifdef PAINFULLY_DEBUGGING_THROUGH_VM
3238	Com_Printf("FINISHED PATH REPAIR.\n");
3239#endif
3240
3241#ifdef PATH_TIME_DEBUG
3242	endTime = trap_Milliseconds();
3243
3244	G_Printf("Total routing time taken: %ims\n", (endTime - startTime));
3245#endif
3246
3247#ifdef ASCII_ART_DEBUG
3248	CreateAsciiTableRepresentation();
3249#endif
3250}
3251#endif
3252
3253#ifndef _XBOX
3254void BeginAutoPathRoutine(void)
3255{ //Called for RMG levels.
3256	int i = 0;
3257	gentity_t *ent = NULL;
3258	vec3_t v;
3259
3260	gSpawnPointNum = 0;
3261
3262	CreateNewWP(vec3_origin, 0); //create a dummy waypoint to insert under
3263
3264	while (i < level.num_entities)
3265	{
3266		ent = &g_entities[i];
3267
3268		if (ent && ent->inuse && ent->classname && ent->classname[0] && !Q_stricmp(ent->classname, "info_player_deathmatch"))
3269		{
3270			if (ent->s.origin[2] < 1280)
3271			{ //h4x
3272				gSpawnPoints[gSpawnPointNum] = ent;
3273				gSpawnPointNum++;
3274			}
3275		}
3276		else if (ent && ent->inuse && ent->item && ent->item->giType == IT_TEAM &&
3277			(ent->item->giTag == PW_REDFLAG || ent->item->giTag == PW_BLUEFLAG))
3278		{ //also make it path to flags in CTF.
3279			gSpawnPoints[gSpawnPointNum] = ent;
3280			gSpawnPointNum++;
3281		}
3282
3283		i++;
3284	}
3285
3286	if (gSpawnPointNum < 1)
3287	{
3288		return;
3289	}
3290
3291	G_RMGPathing();
3292#ifdef PAINFULLY_DEBUGGING_THROUGH_VM
3293	Com_Printf("LINKING PATHS...\n");
3294#endif
3295	
3296	//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.
3297	trap_Bot_UpdateWaypoints(gWPNum, gWPArray);
3298	trap_Bot_CalculatePaths(g_RMG.integer);
3299	//CalculatePaths(); //make everything nice and connected
3300
3301#ifdef PAINFULLY_DEBUGGING_THROUGH_VM
3302	Com_Printf("FINISHED LINKING PATHS.\n");
3303#endif
3304
3305#ifdef PAINFULLY_DEBUGGING_THROUGH_VM
3306	Com_Printf("FLAGGING OBJECTS...\n");
3307#endif
3308	FlagObjects(); //currently only used for flagging waypoints nearest CTF flags
3309#ifdef PAINFULLY_DEBUGGING_THROUGH_VM
3310	Com_Printf("FINISHED FLAGGING OBJECTS.\n");
3311#endif
3312
3313#ifdef PAINFULLY_DEBUGGING_THROUGH_VM
3314	Com_Printf("CALCULATING WAYPOINT DISTANCES...\n");
3315#endif
3316	i = 0;
3317
3318	while (i < gWPNum-1)
3319	{ //disttonext is normally set on save, and when a file is loaded. For RMG we must do it after calc'ing.
3320		VectorSubtract(gWPArray[i]->origin, gWPArray[i+1]->origin, v);
3321		gWPArray[i]->disttonext = VectorLength(v);
3322		i++;
3323	}
3324#ifdef PAINFULLY_DEBUGGING_THROUGH_VM
3325	Com_Printf("FINISHED CALCULATING.\n");
3326#endif
3327
3328#ifdef PAINFULLY_DEBUGGING_THROUGH_VM
3329	Com_Printf("FINAL STEP...\n");
3330#endif
3331	RemoveWP(); //remove the dummy point at the end of the trail
3332#ifdef PAINFULLY_DEBUGGING_THROUGH_VM
3333	Com_Printf("COMPLETE.\n");
3334#endif
3335
3336#ifdef PAINFULLY_DEBUGGING_THROUGH_VM
3337	if (gWPNum >= 4096-1)
3338	{
3339		Com_Printf("%i waypoints say that YOU ARE A TERRIBLE MAN.\n", gWPNum);
3340	}
3341#endif
3342}
3343
3344#endif
3345extern vmCvar_t bot_normgpath;
3346
3347void LoadPath_ThisLevel(void)
3348{
3349	vmCvar_t	mapname;
3350	int			i = 0;
3351	gentity_t	*ent = NULL;
3352
3353	trap_Cvar_Register( &mapname, "mapname", "", CVAR_SERVERINFO | CVAR_ROM );
3354
3355	if (g_RMG.integer)
3356	{ //If RMG, generate the path on-the-fly
3357#ifdef _XBOX
3358		assert(0);
3359#else
3360		trap_Cvar_Register(&bot_normgpath, "bot_normgpath", "1", CVAR_CHEAT);
3361		//note: This is disabled for now as I'm using standard bot nav
3362		//on premade terrain levels.
3363
3364		if (!bot_normgpath.integer)
3365		{ //autopath the random map
3366			BeginAutoPathRoutine();
3367		}
3368		else
3369		{ //try loading standard nav data
3370			LoadPathData(mapname.string);
3371		}
3372
3373		gLevelFlags |= LEVELFLAG_NOPOINTPREDICTION;
3374#endif
3375	}
3376	else
3377	{
3378		if (LoadPathData(mapname.string) == 2)
3379		{
3380			//enter "edit" mode if cheats enabled?
3381		}
3382	}
3383
3384	trap_Cvar_Update(&bot_wp_edit);
3385
3386	if (bot_wp_edit.value)
3387	{
3388		gBotEdit = 1;
3389	}
3390	else
3391	{
3392		gBotEdit = 0;
3393	}
3394
3395	//set the flag entities
3396	while (i < level.num_entities)
3397	{
3398		ent = &g_entities[i];
3399
3400		if (ent && ent->inuse && ent->classname)
3401		{
3402			if (!eFlagRed && strcmp(ent->classname, "team_CTF_redflag") == 0)
3403			{
3404				eFlagRed = ent;
3405			}
3406			else if (!eFlagBlue && strcmp(ent->classname, "team_CTF_blueflag") == 0)
3407			{
3408				eFlagBlue = ent;
3409			}
3410
3411			if (eFlagRed && eFlagBlue)
3412			{
3413				break;
3414			}
3415		}
3416
3417		i++;
3418	}
3419
3420#ifdef PAINFULLY_DEBUGGING_THROUGH_VM
3421	Com_Printf("BOT PATHING IS COMPLETE.\n");
3422#endif
3423}
3424
3425gentity_t *GetClosestSpawn(gentity_t *ent)
3426{
3427	gentity_t	*spawn;
3428	gentity_t	*closestSpawn = NULL;
3429	float		closestDist = -1;
3430	int			i = MAX_CLIENTS;
3431
3432	spawn = NULL;
3433
3434	while (i < level.num_entities)
3435	{
3436		spawn = &g_entities[i];
3437
3438		if (spawn && spawn->inuse && (!Q_stricmp(spawn->classname, "info_player_start") || !Q_stricmp(spawn->classname, "info_player_deathmatch")) )
3439		{
3440			float checkDist;
3441			vec3_t vSub;
3442
3443			VectorSubtract(ent->client->ps.origin, spawn->r.currentOrigin, vSub);
3444			checkDist = VectorLength(vSub);
3445
3446			if (closestDist == -1 || checkDist < closestDist)
3447			{
3448				closestSpawn = spawn;
3449				closestDist = checkDist;
3450			}
3451		}
3452
3453		i++;
3454	}
3455
3456	return closestSpawn;
3457}
3458
3459gentity_t *GetNextSpawnInIndex(gentity_t *currentSpawn)
3460{
3461	gentity_t	*spawn;
3462	gentity_t	*nextSpawn = NULL;
3463	int			i = currentSpawn->s.number+1;
3464
3465	spawn = NULL;
3466
3467	while (i < level.num_entities)
3468	{
3469		spawn = &g_entities[i];
3470
3471		if (spawn && spawn->inuse && (!Q_stricmp(spawn->classname, "info_player_start") || !Q_stricmp(spawn->classname, "info_player_deathmatch")) )
3472		{
3473			nextSpawn = spawn;
3474			break;
3475		}
3476
3477		i++;
3478	}
3479
3480	if (!nextSpawn)
3481	{ //loop back around to 0
3482		i = MAX_CLIENTS;
3483
3484		while (i < level.num_entities)
3485		{
3486			spawn = &g_entities[i];
3487
3488			if (spawn && spawn->inuse && (!Q_stricmp(spawn->classname, "info_player_start") || !Q_stricmp(spawn->classname, "info_player_deathmatch")) )
3489			{
3490				nextSpawn = spawn;
3491				break;
3492			}
3493
3494			i++;
3495		}
3496	}
3497
3498	return nextSpawn;
3499}
3500
3501int AcceptBotCommand(char *cmd, gentity_t *pl)
3502{
3503	int OptionalArgument, i;
3504	int FlagsFromArgument;
3505	char *OptionalSArgument, *RequiredSArgument;
3506#ifndef _XBOX
3507	vmCvar_t mapname;
3508#endif
3509
3510	if (!gBotEdit)
3511	{
3512		return 0;
3513	}
3514
3515	OptionalArgument = 0;
3516	i = 0;
3517	FlagsFromArgument = 0;
3518	OptionalSArgument = NULL;
3519	RequiredSArgument = NULL;
3520
3521	//if a waypoint editing related command is issued, bots will deactivate.
3522	//once bot_wp_save is issued and the trail is recalculated, bots will activate again.
3523
3524	if (!pl || !pl->client)
3525	{
3526		return 0;
3527	}
3528
3529	if (Q_stricmp (cmd, "bot_wp_cmdlist") == 0) //lists all the bot waypoint commands.
3530	{
3531		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");
3532		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");
3533		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");
3534		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");
3535		G_Printf(S_COLOR_YELLOW "bot_wp_tele" S_COLOR_WHITE " - Teleport yourself to the specified waypoint's location\n");
3536		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");
3537		G_Printf(S_COLOR_YELLOW "bot_wp_save" S_COLOR_WHITE " - Saves all waypoint data into a file for later use\n");
3538
3539		return 1;
3540	}
3541
3542	if (Q_stricmp (cmd, "bot_wp_add") == 0)
3543	{
3544		gDeactivated = 1;
3545		OptionalSArgument = ConcatArgs( 1 );
3546
3547		if (OptionalSArgument)
3548		{
3549			OptionalArgument = atoi(OptionalSArgument);
3550		}
3551
3552		if (OptionalSArgument && OptionalSArgument[0])
3553		{
3554			CreateNewWP_InTrail(pl->client->ps.origin, 0, OptionalArgument);
3555		}
3556		else
3557		{
3558			CreateNewWP(pl->client->ps.origin, 0);
3559		}
3560		return 1;
3561	}
3562
3563	if (Q_stricmp (cmd, "bot_wp_rem") == 0)
3564	{
3565		gDeactivated = 1;
3566
3567		OptionalSArgument = ConcatArgs( 1 );
3568
3569		if (OptionalSArgument)
3570		{
3571			OptionalArgument = atoi(OptionalSArgument);
3572		}
3573
3574		if (OptionalSArgument && OptionalSArgument[0])
3575		{
3576			RemoveWP_InTrail(OptionalArgument);
3577		}
3578		else
3579		{
3580			RemoveWP();
3581		}
3582
3583		return 1;
3584	}
3585
3586	if (Q_stricmp (cmd, "bot_wp_tele") == 0)
3587	{
3588		gDeactivated = 1;
3589		OptionalSArgument = ConcatArgs( 1 );
3590
3591		if (OptionalSArgument)
3592		{
3593			OptionalArgument = atoi(OptionalSArgument);
3594		}
3595
3596		if (OptionalSArgument && OptionalSArgument[0])
3597		{
3598			TeleportToWP(pl, OptionalArgument);
3599		}
3600		else
3601		{
3602			G_Printf(S_COLOR_YELLOW "You didn't specify an index. Assuming last.\n");
3603			TeleportToWP(pl, gWPNum-1);
3604		}
3605		return 1;
3606	}
3607
3608	if (Q_stricmp (cmd, "bot_wp_spawntele") == 0)
3609	{
3610		gentity_t *closestSpawn = GetClosestSpawn(pl);
3611
3612		if (!closestSpawn)
3613		{ //There should always be a spawn point..
3614			return 1;
3615		}
3616
3617		closestSpawn = GetNextSpawnInIndex(closestSpawn);
3618
3619		if (closestSpawn)
3620		{
3621			VectorCopy(closestSpawn->r.currentOrigin, pl->client->ps.origin);
3622		}
3623		return 1;
3624	}
3625
3626	if (Q_stricmp (cmd, "bot_wp_addflagged") == 0)
3627	{
3628		gDeactivated = 1;
3629
3630		RequiredSArgument = ConcatArgs( 1 );
3631
3632		if (!RequiredSArgument || !RequiredSArgument[0])
3633		{
3634			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");
3635			return 1;
3636		}
3637
3638		while (RequiredSArgument[i])
3639		{
3640			if (RequiredSArgument[i] == 'j')
3641			{
3642				FlagsFromArgument |= WPFLAG_JUMP;
3643			}
3644			else if (RequiredSArgument[i] == 'd')
3645			{
3646				FlagsFromArgument |= WPFLAG_DUCK;
3647			}
3648			else if (RequiredSArgument[i] == 'c')
3649			{
3650				FlagsFromArgument |= WPFLAG_SNIPEORCAMPSTAND;
3651			}
3652			else if (RequiredSArgument[i] == 'f')
3653			{
3654				FlagsFromArgument |= WPFLAG_WAITFORFUNC;
3655			}
3656			else if (RequiredSArgument[i] == 's')
3657			{
3658				FlagsFromArgument |= WPFLAG_SNIPEORCAMP;
3659			}
3660			else if (RequiredSArgument[i] == 'x')
3661			{
3662				FlagsFromArgument |= WPFLAG_ONEWAY_FWD;
3663			}
3664			else if (RequiredSArgument[i] == 'y')
3665			{
3666				FlagsFromArgument |= WPFLAG_ONEWAY_BACK;
3667			}
3668			else if (RequiredSArgument[i] == 'g')
3669			{
3670				FlagsFromArgument |= WPFLAG_GOALPOINT;
3671			}
3672			else if (RequiredSArgument[i] == 'n')
3673			{
3674				FlagsFromArgument |= WPFLAG_NOVIS;
3675			}
3676			else if (RequiredSArgument[i] == 'm')
3677			{
3678				FlagsFromArgument |= WPFLAG_NOMOVEFUNC;
3679			}
3680
3681			i++;
3682		}
3683
3684		OptionalSArgument = ConcatArgs( 2 );
3685
3686		if (OptionalSArgument)
3687		{
3688			OptionalArgument = atoi(OptionalSArgument);
3689		}
3690
3691		if (OptionalSArgument && OptionalSArgument[0])
3692		{
3693			CreateNewWP_InTrail(pl->client->ps.origin, FlagsFromArgument, OptionalArgument);
3694		}
3695		else
3696		{
3697			CreateNewWP(pl->client->ps.origin, FlagsFromArgument);
3698		}
3699		return 1;
3700	}
3701
3702	if (Q_stricmp (cmd, "bot_wp_switchflags") == 0)
3703	{
3704		gDeactivated = 1;
3705
3706		RequiredSArgument = ConcatArgs( 1 );
3707
3708		if (!RequiredSArgument || !RequiredSArgument[0])
3709		{
3710			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");
3711			return 1;
3712		}
3713
3714		while (RequiredSArgument[i])
3715		{
3716			if (RequiredSArgument[i] == 'j')
3717			{
3718				FlagsFromArgument |= WPFLAG_JUMP;
3719			}
3720			else if (RequiredSArgument[i] == 'd')
3721			{
3722				FlagsFromArgument |= WPFLAG_DUCK;
3723			}
3724			else if (RequiredSArgument[i] == 'c')
3725			{
3726				FlagsFromArgument |= WPFLAG_SNIPEORCAMPSTAND;
3727			}
3728			else if (RequiredSArgument[i] == 'f')
3729			{
3730				FlagsFromArgument |= WPFLAG_WAITFORFUNC;
3731			}
3732			else if (RequiredSArgument[i] == 's')
3733			{
3734				FlagsFromArgument |= WPFLAG_SNIPEORCAMP;
3735			}
3736			else if (RequiredSArgument[i] == 'x')
3737			{
3738				FlagsFromArgument |= WPFLAG_ONEWAY_FWD;
3739			}
3740			else if (RequiredSArgument[i] == 'y')
3741			{
3742				FlagsFromArgument |= WPFLAG_ONEWAY_BACK;
3743			}
3744			else if (RequiredSArgument[i] == 'g')
3745			{
3746				FlagsFromArgument |= WPFLAG_GOALPOINT;
3747			}
3748			else if (RequiredSArgument[i] == 'n')
3749			{
3750				FlagsFromArgument |= WPFLAG_NOVIS;
3751			}
3752			else if (RequiredSArgument[i] == 'm')
3753			{
3754				FlagsFromArgument |= WPFLAG_NOMOVEFUNC;
3755			}
3756
3757			i++;
3758		}
3759
3760		OptionalSArgument = ConcatArgs( 2 );
3761
3762		if (OptionalSArgument)
3763		{
3764			OptionalArgument = atoi(OptionalSArgument);
3765		}
3766
3767		if (OptionalSArgument && OptionalSArgument[0])
3768		{
3769			WPFlagsModify(OptionalArgument, FlagsFromArgument);
3770		}
3771		else
3772		{
3773			G_Printf(S_COLOR_YELLOW "Waypoint number (to modify) needed for bot_wp_switchflags\nSyntax: bot_wp_switchflags <flags> <n>\n");
3774		}
3775		return 1;
3776	}
3777
3778	if (Q_stricmp (cmd, "bot_wp_killoneways") == 0)
3779	{
3780		i = 0;
3781
3782		while (i < gWPNum)
3783		{
3784			if (gWPArray[i] && gWPArray[i]->inuse)
3785			{
3786				if (gWPArray[i]->flags & WPFLAG_ONEWAY_FWD)
3787				{
3788					gWPArray[i]->flags -= WPFLAG_ONEWAY_FWD;
3789				}
3790				if (gWPArray[i]->flags & WPFLAG_ONEWAY_BACK)
3791				{
3792					gWPArray[i]->flags -= WPFLAG_ONEWAY_BACK;
3793				}
3794			}
3795
3796			i++;
3797		}
3798
3799		return 1;
3800	}
3801
3802#ifndef _XBOX
3803	if (Q_stricmp (cmd, "bot_wp_save") == 0)
3804	{
3805		gDeactivated = 0;
3806		trap_Cvar_Register( &mapname, "mapname", "", CVAR_SERVERINFO | CVAR_ROM );
3807		SavePathData(mapname.string);
3808		return 1;
3809	}
3810#endif
3811
3812	return 0;
3813}