#include "g_local.h" #include "q_shared.h" #include "botlib.h" #include "ai_main.h" float gWPRenderTime = 0; float gDeactivated = 0; float gBotEdit = 0; int gWPRenderedFrame = 0; #include "../namespace_begin.h" wpobject_t *gWPArray[MAX_WPARRAY_SIZE]; int gWPNum = 0; #include "../namespace_end.h" int gLastPrintedIndex = -1; #ifndef _XBOX nodeobject_t nodetable[MAX_NODETABLE_SIZE]; int nodenum; //so we can connect broken trails #endif int gLevelFlags = 0; char *GetFlagStr( int flags ) { char *flagstr; int i; flagstr = (char *)B_TempAlloc(128); i = 0; if (!flags) { strcpy(flagstr, "none\0"); goto fend; } if (flags & WPFLAG_JUMP) { flagstr[i] = 'j'; i++; } if (flags & WPFLAG_DUCK) { flagstr[i] = 'd'; i++; } if (flags & WPFLAG_SNIPEORCAMPSTAND) { flagstr[i] = 'c'; i++; } if (flags & WPFLAG_WAITFORFUNC) { flagstr[i] = 'f'; i++; } if (flags & WPFLAG_SNIPEORCAMP) { flagstr[i] = 's'; i++; } if (flags & WPFLAG_ONEWAY_FWD) { flagstr[i] = 'x'; i++; } if (flags & WPFLAG_ONEWAY_BACK) { flagstr[i] = 'y'; i++; } if (flags & WPFLAG_GOALPOINT) { flagstr[i] = 'g'; i++; } if (flags & WPFLAG_NOVIS) { flagstr[i] = 'n'; i++; } if (flags & WPFLAG_NOMOVEFUNC) { flagstr[i] = 'm'; i++; } if (flags & WPFLAG_RED_FLAG) { if (i) { flagstr[i] = ' '; i++; } flagstr[i] = 'r'; i++; flagstr[i] = 'e'; i++; flagstr[i] = 'd'; i++; flagstr[i] = ' '; i++; flagstr[i] = 'f'; i++; flagstr[i] = 'l'; i++; flagstr[i] = 'a'; i++; flagstr[i] = 'g'; i++; } if (flags & WPFLAG_BLUE_FLAG) { if (i) { flagstr[i] = ' '; i++; } flagstr[i] = 'b'; i++; flagstr[i] = 'l'; i++; flagstr[i] = 'u'; i++; flagstr[i] = 'e'; i++; flagstr[i] = ' '; i++; flagstr[i] = 'f'; i++; flagstr[i] = 'l'; i++; flagstr[i] = 'a'; i++; flagstr[i] = 'g'; i++; } if (flags & WPFLAG_SIEGE_IMPERIALOBJ) { if (i) { flagstr[i] = ' '; i++; } flagstr[i] = 's'; i++; flagstr[i] = 'a'; i++; flagstr[i] = 'g'; i++; flagstr[i] = 'a'; i++; flagstr[i] = '_'; i++; flagstr[i] = 'i'; i++; flagstr[i] = 'm'; i++; flagstr[i] = 'p'; i++; } if (flags & WPFLAG_SIEGE_REBELOBJ) { if (i) { flagstr[i] = ' '; i++; } flagstr[i] = 's'; i++; flagstr[i] = 'a'; i++; flagstr[i] = 'g'; i++; flagstr[i] = 'a'; i++; flagstr[i] = '_'; i++; flagstr[i] = 'r'; i++; flagstr[i] = 'e'; i++; flagstr[i] = 'b'; i++; } flagstr[i] = '\0'; if (i == 0) { strcpy(flagstr, "unknown\0"); } fend: return flagstr; } void G_TestLine(vec3_t start, vec3_t end, int color, int time) { gentity_t *te; te = G_TempEntity( start, EV_TESTLINE ); VectorCopy(start, te->s.origin); VectorCopy(end, te->s.origin2); te->s.time2 = time; te->s.weapon = color; te->r.svFlags |= SVF_BROADCAST; } void BotWaypointRender(void) { int i, n; int inc_checker; int bestindex; int gotbestindex; float bestdist; float checkdist; gentity_t *plum; gentity_t *viewent; char *flagstr; vec3_t a; if (!gBotEdit) { return; } bestindex = 0; if (gWPRenderTime > level.time) { goto checkprint; } gWPRenderTime = level.time + 100; i = gWPRenderedFrame; inc_checker = gWPRenderedFrame; while (i < gWPNum) { if (gWPArray[i] && gWPArray[i]->inuse) { plum = G_TempEntity( gWPArray[i]->origin, EV_SCOREPLUM ); plum->r.svFlags |= SVF_BROADCAST; plum->s.time = i; n = 0; while (n < gWPArray[i]->neighbornum) { if (gWPArray[i]->neighbors[n].forceJumpTo && gWPArray[gWPArray[i]->neighbors[n].num]) { G_TestLine(gWPArray[i]->origin, gWPArray[gWPArray[i]->neighbors[n].num]->origin, 0x0000ff, 5000); } n++; } gWPRenderedFrame++; } else { gWPRenderedFrame = 0; break; } if ((i - inc_checker) > 4) { break; //don't render too many at once } i++; } if (i >= gWPNum) { gWPRenderTime = level.time + 1500; //wait a bit after we finish doing the whole trail gWPRenderedFrame = 0; } checkprint: if (!bot_wp_info.value) { return; } viewent = &g_entities[0]; //only show info to the first client if (!viewent || !viewent->client) { //client isn't in the game yet? return; } bestdist = 256; //max distance for showing point info gotbestindex = 0; i = 0; while (i < gWPNum) { if (gWPArray[i] && gWPArray[i]->inuse) { VectorSubtract(viewent->client->ps.origin, gWPArray[i]->origin, a); checkdist = VectorLength(a); if (checkdist < bestdist) { bestdist = checkdist; bestindex = i; gotbestindex = 1; } } i++; } if (gotbestindex && bestindex != gLastPrintedIndex) { flagstr = GetFlagStr(gWPArray[bestindex]->flags); gLastPrintedIndex = bestindex; 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])); //GetFlagStr allocates 128 bytes for this, if it's changed then obviously this must be as well B_TempFree(128); //flagstr plum = G_TempEntity( gWPArray[bestindex]->origin, EV_SCOREPLUM ); plum->r.svFlags |= SVF_BROADCAST; plum->s.time = bestindex; //render it once } else if (!gotbestindex) { gLastPrintedIndex = -1; } } void TransferWPData(int from, int to) { if (!gWPArray[to]) { gWPArray[to] = (wpobject_t *)B_Alloc(sizeof(wpobject_t)); } if (!gWPArray[to]) { G_Printf(S_COLOR_RED "FATAL ERROR: Could not allocated memory for waypoint\n"); } gWPArray[to]->flags = gWPArray[from]->flags; gWPArray[to]->weight = gWPArray[from]->weight; gWPArray[to]->associated_entity = gWPArray[from]->associated_entity; gWPArray[to]->disttonext = gWPArray[from]->disttonext; gWPArray[to]->forceJumpTo = gWPArray[from]->forceJumpTo; gWPArray[to]->index = to; gWPArray[to]->inuse = gWPArray[from]->inuse; VectorCopy(gWPArray[from]->origin, gWPArray[to]->origin); } void CreateNewWP(vec3_t origin, int flags) { if (gWPNum >= MAX_WPARRAY_SIZE) { if (!g_RMG.integer) { G_Printf(S_COLOR_YELLOW "Warning: Waypoint limit hit (%i)\n", MAX_WPARRAY_SIZE); } return; } if (!gWPArray[gWPNum]) { gWPArray[gWPNum] = (wpobject_t *)B_Alloc(sizeof(wpobject_t)); } if (!gWPArray[gWPNum]) { G_Printf(S_COLOR_RED "ERROR: Could not allocated memory for waypoint\n"); } gWPArray[gWPNum]->flags = flags; gWPArray[gWPNum]->weight = 0; //calculated elsewhere gWPArray[gWPNum]->associated_entity = ENTITYNUM_NONE; //set elsewhere gWPArray[gWPNum]->forceJumpTo = 0; gWPArray[gWPNum]->disttonext = 0; //calculated elsewhere gWPArray[gWPNum]->index = gWPNum; gWPArray[gWPNum]->inuse = 1; VectorCopy(origin, gWPArray[gWPNum]->origin); gWPNum++; } void CreateNewWP_FromObject(wpobject_t *wp) { int i; if (gWPNum >= MAX_WPARRAY_SIZE) { return; } if (!gWPArray[gWPNum]) { gWPArray[gWPNum] = (wpobject_t *)B_Alloc(sizeof(wpobject_t)); } if (!gWPArray[gWPNum]) { G_Printf(S_COLOR_RED "ERROR: Could not allocated memory for waypoint\n"); } gWPArray[gWPNum]->flags = wp->flags; gWPArray[gWPNum]->weight = wp->weight; gWPArray[gWPNum]->associated_entity = wp->associated_entity; gWPArray[gWPNum]->disttonext = wp->disttonext; gWPArray[gWPNum]->forceJumpTo = wp->forceJumpTo; gWPArray[gWPNum]->index = gWPNum; gWPArray[gWPNum]->inuse = 1; VectorCopy(wp->origin, gWPArray[gWPNum]->origin); gWPArray[gWPNum]->neighbornum = wp->neighbornum; i = wp->neighbornum; while (i >= 0) { gWPArray[gWPNum]->neighbors[i].num = wp->neighbors[i].num; gWPArray[gWPNum]->neighbors[i].forceJumpTo = wp->neighbors[i].forceJumpTo; i--; } if (gWPArray[gWPNum]->flags & WPFLAG_RED_FLAG) { flagRed = gWPArray[gWPNum]; oFlagRed = flagRed; } else if (gWPArray[gWPNum]->flags & WPFLAG_BLUE_FLAG) { flagBlue = gWPArray[gWPNum]; oFlagBlue = flagBlue; } gWPNum++; } void RemoveWP(void) { if (gWPNum <= 0) { return; } gWPNum--; if (!gWPArray[gWPNum] || !gWPArray[gWPNum]->inuse) { return; } //B_Free((wpobject_t *)gWPArray[gWPNum]); if (gWPArray[gWPNum]) { memset( gWPArray[gWPNum], 0, sizeof(gWPArray[gWPNum]) ); } //gWPArray[gWPNum] = NULL; if (gWPArray[gWPNum]) { gWPArray[gWPNum]->inuse = 0; } } void RemoveAllWP(void) { while(gWPNum) { RemoveWP(); } } void RemoveWP_InTrail(int afterindex) { int foundindex; int foundanindex; int didchange; int i; foundindex = 0; foundanindex = 0; didchange = 0; i = 0; if (afterindex < 0 || afterindex >= gWPNum) { G_Printf(S_COLOR_YELLOW "Waypoint number %i does not exist\n", afterindex); return; } while (i < gWPNum) { if (gWPArray[i] && gWPArray[i]->inuse && gWPArray[i]->index == afterindex) { foundindex = i; foundanindex = 1; break; } i++; } if (!foundanindex) { G_Printf(S_COLOR_YELLOW "Waypoint index %i should exist, but does not (?)\n", afterindex); return; } i = 0; while (i <= gWPNum) { if (gWPArray[i] && gWPArray[i]->index == foundindex) { //B_Free(gWPArray[i]); //Keep reusing the memory memset( gWPArray[i], 0, sizeof(gWPArray[i]) ); //gWPArray[i] = NULL; gWPArray[i]->inuse = 0; didchange = 1; } else if (gWPArray[i] && didchange) { TransferWPData(i, i-1); //B_Free(gWPArray[i]); //Keep reusing the memory memset( gWPArray[i], 0, sizeof(gWPArray[i]) ); //gWPArray[i] = NULL; gWPArray[i]->inuse = 0; } i++; } gWPNum--; } int CreateNewWP_InTrail(vec3_t origin, int flags, int afterindex) { int foundindex; int foundanindex; int i; foundindex = 0; foundanindex = 0; i = 0; if (gWPNum >= MAX_WPARRAY_SIZE) { if (!g_RMG.integer) { G_Printf(S_COLOR_YELLOW "Warning: Waypoint limit hit (%i)\n", MAX_WPARRAY_SIZE); } return 0; } if (afterindex < 0 || afterindex >= gWPNum) { G_Printf(S_COLOR_YELLOW "Waypoint number %i does not exist\n", afterindex); return 0; } while (i < gWPNum) { if (gWPArray[i] && gWPArray[i]->inuse && gWPArray[i]->index == afterindex) { foundindex = i; foundanindex = 1; break; } i++; } if (!foundanindex) { G_Printf(S_COLOR_YELLOW "Waypoint index %i should exist, but does not (?)\n", afterindex); return 0; } i = gWPNum; while (i >= 0) { if (gWPArray[i] && gWPArray[i]->inuse && gWPArray[i]->index != foundindex) { TransferWPData(i, i+1); } else if (gWPArray[i] && gWPArray[i]->inuse && gWPArray[i]->index == foundindex) { i++; if (!gWPArray[i]) { gWPArray[i] = (wpobject_t *)B_Alloc(sizeof(wpobject_t)); } gWPArray[i]->flags = flags; gWPArray[i]->weight = 0; //calculated elsewhere gWPArray[i]->associated_entity = ENTITYNUM_NONE; //set elsewhere gWPArray[i]->disttonext = 0; //calculated elsewhere gWPArray[i]->forceJumpTo = 0; gWPArray[i]->index = i; gWPArray[i]->inuse = 1; VectorCopy(origin, gWPArray[i]->origin); gWPNum++; break; } i--; } return 1; } int CreateNewWP_InsertUnder(vec3_t origin, int flags, int afterindex) { int foundindex; int foundanindex; int i; foundindex = 0; foundanindex = 0; i = 0; if (gWPNum >= MAX_WPARRAY_SIZE) { if (!g_RMG.integer) { G_Printf(S_COLOR_YELLOW "Warning: Waypoint limit hit (%i)\n", MAX_WPARRAY_SIZE); } return 0; } if (afterindex < 0 || afterindex >= gWPNum) { G_Printf(S_COLOR_YELLOW "Waypoint number %i does not exist\n", afterindex); return 0; } while (i < gWPNum) { if (gWPArray[i] && gWPArray[i]->inuse && gWPArray[i]->index == afterindex) { foundindex = i; foundanindex = 1; break; } i++; } if (!foundanindex) { G_Printf(S_COLOR_YELLOW "Waypoint index %i should exist, but does not (?)\n", afterindex); return 0; } i = gWPNum; while (i >= 0) { if (gWPArray[i] && gWPArray[i]->inuse && gWPArray[i]->index != foundindex) { TransferWPData(i, i+1); } else if (gWPArray[i] && gWPArray[i]->inuse && gWPArray[i]->index == foundindex) { //i++; TransferWPData(i, i+1); if (!gWPArray[i]) { gWPArray[i] = (wpobject_t *)B_Alloc(sizeof(wpobject_t)); } gWPArray[i]->flags = flags; gWPArray[i]->weight = 0; //calculated elsewhere gWPArray[i]->associated_entity = ENTITYNUM_NONE; //set elsewhere gWPArray[i]->disttonext = 0; //calculated elsewhere gWPArray[i]->forceJumpTo = 0; gWPArray[i]->index = i; gWPArray[i]->inuse = 1; VectorCopy(origin, gWPArray[i]->origin); gWPNum++; break; } i--; } return 1; } void TeleportToWP(gentity_t *pl, int afterindex) { int foundindex; int foundanindex; int i; if (!pl || !pl->client) { return; } foundindex = 0; foundanindex = 0; i = 0; if (afterindex < 0 || afterindex >= gWPNum) { G_Printf(S_COLOR_YELLOW "Waypoint number %i does not exist\n", afterindex); return; } while (i < gWPNum) { if (gWPArray[i] && gWPArray[i]->inuse && gWPArray[i]->index == afterindex) { foundindex = i; foundanindex = 1; break; } i++; } if (!foundanindex) { G_Printf(S_COLOR_YELLOW "Waypoint index %i should exist, but does not (?)\n", afterindex); return; } VectorCopy(gWPArray[foundindex]->origin, pl->client->ps.origin); return; } void WPFlagsModify(int wpnum, int flags) { if (wpnum < 0 || wpnum >= gWPNum || !gWPArray[wpnum] || !gWPArray[wpnum]->inuse) { G_Printf(S_COLOR_YELLOW "WPFlagsModify: Waypoint %i does not exist\n", wpnum); return; } gWPArray[wpnum]->flags = flags; } static int NotWithinRange(int base, int extent) { if (extent > base && base+5 >= extent) { return 0; } if (extent < base && base-5 <= extent) { return 0; } return 1; } #ifndef _XBOX int NodeHere(vec3_t spot) { int i; i = 0; while (i < nodenum) { if ((int)nodetable[i].origin[0] == (int)spot[0] && (int)nodetable[i].origin[1] == (int)spot[1]) { if ((int)nodetable[i].origin[2] == (int)spot[2] || ((int)nodetable[i].origin[2] < (int)spot[2] && (int)nodetable[i].origin[2]+5 > (int)spot[2]) || ((int)nodetable[i].origin[2] > (int)spot[2] && (int)nodetable[i].origin[2]-5 < (int)spot[2])) { return 1; } } i++; } return 0; } #endif int CanGetToVector(vec3_t org1, vec3_t org2, vec3_t mins, vec3_t maxs) { trace_t tr; trap_Trace(&tr, org1, mins, maxs, org2, ENTITYNUM_NONE, MASK_SOLID); if (tr.fraction == 1 && !tr.startsolid && !tr.allsolid) { return 1; } return 0; } #if 0 int CanGetToVectorTravel(vec3_t org1, vec3_t org2, vec3_t mins, vec3_t maxs) { trace_t tr; vec3_t a, ang, fwd; vec3_t midpos, dmid; float startheight, midheight, fLen; mins[2] = -13; maxs[2] = 13; trap_Trace(&tr, org1, mins, maxs, org2, ENTITYNUM_NONE, MASK_SOLID); if (tr.fraction != 1 || tr.startsolid || tr.allsolid) { return 0; } VectorSubtract(org2, org1, a); vectoangles(a, ang); AngleVectors(ang, fwd, NULL, NULL); fLen = VectorLength(a)/2; midpos[0] = org1[0] + fwd[0]*fLen; midpos[1] = org1[1] + fwd[1]*fLen; midpos[2] = org1[2] + fwd[2]*fLen; VectorCopy(org1, dmid); dmid[2] -= 1024; trap_Trace(&tr, midpos, NULL, NULL, dmid, ENTITYNUM_NONE, MASK_SOLID); startheight = org1[2] - tr.endpos[2]; VectorCopy(midpos, dmid); dmid[2] -= 1024; trap_Trace(&tr, midpos, NULL, NULL, dmid, ENTITYNUM_NONE, MASK_SOLID); if (tr.startsolid || tr.allsolid) { return 1; } midheight = midpos[2] - tr.endpos[2]; if (midheight > startheight*2) { return 0; //too steep of a drop.. can't go on } return 1; } #else int CanGetToVectorTravel(vec3_t org1, vec3_t moveTo, vec3_t mins, vec3_t maxs) //int ExampleAnimEntMove(gentity_t *self, vec3_t moveTo, float stepSize) { trace_t tr; vec3_t stepTo; vec3_t stepSub; vec3_t stepGoal; vec3_t workingOrg; vec3_t lastIncrement; vec3_t finalMeasure; float stepSize = 0; float measureLength = 0; int didMove = 0; int traceMask = MASK_PLAYERSOLID; qboolean initialDone = qfalse; VectorCopy(org1, workingOrg); VectorCopy(org1, lastIncrement); VectorCopy(moveTo, stepTo); stepTo[2] = workingOrg[2]; VectorSubtract(stepTo, workingOrg, stepSub); stepSize = VectorLength(stepSub); //make the step size the length of the original positions without Z VectorNormalize(stepSub); while (!initialDone || didMove) { initialDone = qtrue; didMove = 0; stepGoal[0] = workingOrg[0] + stepSub[0]*stepSize; stepGoal[1] = workingOrg[1] + stepSub[1]*stepSize; stepGoal[2] = workingOrg[2] + stepSub[2]*stepSize; trap_Trace(&tr, workingOrg, mins, maxs, stepGoal, ENTITYNUM_NONE, traceMask); if (!tr.startsolid && !tr.allsolid && tr.fraction) { vec3_t vecSub; VectorSubtract(workingOrg, tr.endpos, vecSub); if (VectorLength(vecSub) > (stepSize/2)) { workingOrg[0] = tr.endpos[0]; workingOrg[1] = tr.endpos[1]; //trap_LinkEntity(self); didMove = 1; } } if (didMove != 1) { //stair check vec3_t trFrom; vec3_t trTo; vec3_t trDir; vec3_t vecMeasure; VectorCopy(tr.endpos, trFrom); trFrom[2] += 16; VectorSubtract(/*tr.endpos*/stepGoal, workingOrg, trDir); VectorNormalize(trDir); trTo[0] = tr.endpos[0] + trDir[0]*2; trTo[1] = tr.endpos[1] + trDir[1]*2; trTo[2] = tr.endpos[2] + trDir[2]*2; trTo[2] += 16; VectorSubtract(trFrom, trTo, vecMeasure); if (VectorLength(vecMeasure) > 1) { trap_Trace(&tr, trFrom, mins, maxs, trTo, ENTITYNUM_NONE, traceMask); if (!tr.startsolid && !tr.allsolid && tr.fraction == 1) { //clear trace here, probably up a step vec3_t trDown; vec3_t trUp; VectorCopy(tr.endpos, trUp); VectorCopy(tr.endpos, trDown); trDown[2] -= 16; trap_Trace(&tr, trFrom, mins, maxs, trTo, ENTITYNUM_NONE, traceMask); if (!tr.startsolid && !tr.allsolid) { //plop us down on the step after moving up VectorCopy(tr.endpos, workingOrg); //trap_LinkEntity(self); didMove = 1; } } } } VectorSubtract(lastIncrement, workingOrg, finalMeasure); measureLength = VectorLength(finalMeasure); if (!measureLength) { //no progress, break out. If last movement was a sucess didMove will equal 1. break; } stepSize -= measureLength; //subtract the progress distance from the step size so we don't overshoot the mark. if (stepSize <= 0) { break; } VectorCopy(workingOrg, lastIncrement); } return didMove; } #endif #ifndef _XBOX int ConnectTrail(int startindex, int endindex, qboolean behindTheScenes) { int foundit; int cancontinue; int i; int failsafe; int successnodeindex; int insertindex; int prenodestart; byte extendednodes[MAX_NODETABLE_SIZE]; //for storing checked nodes and not trying to extend them each a bazillion times float fvecmeas; float baseheight; float branchDistance; float maxDistFactor = 256; vec3_t a; vec3_t startplace, starttrace; vec3_t mins, maxs; vec3_t testspot; vec3_t validspotpos; trace_t tr; if (g_RMG.integer) { //this might be temporary. Or not. if (!(gWPArray[startindex]->flags & WPFLAG_NEVERONEWAY) && !(gWPArray[endindex]->flags & WPFLAG_NEVERONEWAY)) { gWPArray[startindex]->flags |= WPFLAG_ONEWAY_FWD; gWPArray[endindex]->flags |= WPFLAG_ONEWAY_BACK; } return 0; } if (!g_RMG.integer) { branchDistance = TABLE_BRANCH_DISTANCE; } else { branchDistance = 512; //be less precise here, terrain is fairly broad, and we don't want to take an hour precalculating } if (g_RMG.integer) { maxDistFactor = 700; } mins[0] = -15; mins[1] = -15; mins[2] = 0; maxs[0] = 15; maxs[1] = 15; maxs[2] = 0; nodenum = 0; foundit = 0; i = 0; successnodeindex = 0; while (i < MAX_NODETABLE_SIZE) //clear it out before using it { nodetable[i].flags = 0; // nodetable[i].index = 0; nodetable[i].inuse = 0; nodetable[i].neighbornum = 0; nodetable[i].origin[0] = 0; nodetable[i].origin[1] = 0; nodetable[i].origin[2] = 0; nodetable[i].weight = 0; extendednodes[i] = 0; i++; } i = 0; if (!behindTheScenes) { G_Printf(S_COLOR_YELLOW "Point %i is not connected to %i - Repairing...\n", startindex, endindex); } VectorCopy(gWPArray[startindex]->origin, startplace); VectorCopy(startplace, starttrace); starttrace[2] -= 4096; trap_Trace(&tr, startplace, NULL, NULL, starttrace, ENTITYNUM_NONE, MASK_SOLID); baseheight = startplace[2] - tr.endpos[2]; cancontinue = 1; VectorCopy(startplace, nodetable[nodenum].origin); nodetable[nodenum].weight = 1; nodetable[nodenum].inuse = 1; // nodetable[nodenum].index = nodenum; nodenum++; while (nodenum < MAX_NODETABLE_SIZE && !foundit && cancontinue) { if (g_RMG.integer) { //adjust the branch distance dynamically depending on the distance from the start and end points. vec3_t startDist; vec3_t endDist; float startDistf; float endDistf; VectorSubtract(nodetable[nodenum-1].origin, gWPArray[startindex]->origin, startDist); VectorSubtract(nodetable[nodenum-1].origin, gWPArray[endindex]->origin, endDist); startDistf = VectorLength(startDist); endDistf = VectorLength(endDist); if (startDistf < 64 || endDistf < 64) { branchDistance = 64; } else if (startDistf < 128 || endDistf < 128) { branchDistance = 128; } else if (startDistf < 256 || endDistf < 256) { branchDistance = 256; } else if (startDistf < 512 || endDistf < 512) { branchDistance = 512; } else { branchDistance = 800; } } cancontinue = 0; i = 0; prenodestart = nodenum; while (i < prenodestart) { if (extendednodes[i] != 1) { VectorSubtract(gWPArray[endindex]->origin, nodetable[i].origin, a); fvecmeas = VectorLength(a); if (fvecmeas < 128 && CanGetToVector(gWPArray[endindex]->origin, nodetable[i].origin, mins, maxs)) { foundit = 1; successnodeindex = i; break; } VectorCopy(nodetable[i].origin, testspot); testspot[0] += branchDistance; VectorCopy(testspot, starttrace); starttrace[2] -= 4096; trap_Trace(&tr, testspot, NULL, NULL, starttrace, ENTITYNUM_NONE, MASK_SOLID); testspot[2] = tr.endpos[2]+baseheight; if (!NodeHere(testspot) && !tr.startsolid && !tr.allsolid && CanGetToVector(nodetable[i].origin, testspot, mins, maxs)) { VectorCopy(testspot, nodetable[nodenum].origin); nodetable[nodenum].inuse = 1; // nodetable[nodenum].index = nodenum; nodetable[nodenum].weight = nodetable[i].weight+1; nodetable[nodenum].neighbornum = i; if ((nodetable[i].origin[2] - nodetable[nodenum].origin[2]) > 50) { //if there's a big drop, make sure we know we can't just magically fly back up nodetable[nodenum].flags = WPFLAG_ONEWAY_FWD; } nodenum++; cancontinue = 1; } if (nodenum >= MAX_NODETABLE_SIZE) { break; //failure } VectorCopy(nodetable[i].origin, testspot); testspot[0] -= branchDistance; VectorCopy(testspot, starttrace); starttrace[2] -= 4096; trap_Trace(&tr, testspot, NULL, NULL, starttrace, ENTITYNUM_NONE, MASK_SOLID); testspot[2] = tr.endpos[2]+baseheight; if (!NodeHere(testspot) && !tr.startsolid && !tr.allsolid && CanGetToVector(nodetable[i].origin, testspot, mins, maxs)) { VectorCopy(testspot, nodetable[nodenum].origin); nodetable[nodenum].inuse = 1; // nodetable[nodenum].index = nodenum; nodetable[nodenum].weight = nodetable[i].weight+1; nodetable[nodenum].neighbornum = i; if ((nodetable[i].origin[2] - nodetable[nodenum].origin[2]) > 50) { //if there's a big drop, make sure we know we can't just magically fly back up nodetable[nodenum].flags = WPFLAG_ONEWAY_FWD; } nodenum++; cancontinue = 1; } if (nodenum >= MAX_NODETABLE_SIZE) { break; //failure } VectorCopy(nodetable[i].origin, testspot); testspot[1] += branchDistance; VectorCopy(testspot, starttrace); starttrace[2] -= 4096; trap_Trace(&tr, testspot, NULL, NULL, starttrace, ENTITYNUM_NONE, MASK_SOLID); testspot[2] = tr.endpos[2]+baseheight; if (!NodeHere(testspot) && !tr.startsolid && !tr.allsolid && CanGetToVector(nodetable[i].origin, testspot, mins, maxs)) { VectorCopy(testspot, nodetable[nodenum].origin); nodetable[nodenum].inuse = 1; // nodetable[nodenum].index = nodenum; nodetable[nodenum].weight = nodetable[i].weight+1; nodetable[nodenum].neighbornum = i; if ((nodetable[i].origin[2] - nodetable[nodenum].origin[2]) > 50) { //if there's a big drop, make sure we know we can't just magically fly back up nodetable[nodenum].flags = WPFLAG_ONEWAY_FWD; } nodenum++; cancontinue = 1; } if (nodenum >= MAX_NODETABLE_SIZE) { break; //failure } VectorCopy(nodetable[i].origin, testspot); testspot[1] -= branchDistance; VectorCopy(testspot, starttrace); starttrace[2] -= 4096; trap_Trace(&tr, testspot, NULL, NULL, starttrace, ENTITYNUM_NONE, MASK_SOLID); testspot[2] = tr.endpos[2]+baseheight; if (!NodeHere(testspot) && !tr.startsolid && !tr.allsolid && CanGetToVector(nodetable[i].origin, testspot, mins, maxs)) { VectorCopy(testspot, nodetable[nodenum].origin); nodetable[nodenum].inuse = 1; // nodetable[nodenum].index = nodenum; nodetable[nodenum].weight = nodetable[i].weight+1; nodetable[nodenum].neighbornum = i; if ((nodetable[i].origin[2] - nodetable[nodenum].origin[2]) > 50) { //if there's a big drop, make sure we know we can't just magically fly back up nodetable[nodenum].flags = WPFLAG_ONEWAY_FWD; } nodenum++; cancontinue = 1; } if (nodenum >= MAX_NODETABLE_SIZE) { break; //failure } extendednodes[i] = 1; } i++; } } if (!foundit) { #ifndef _DEBUG //if debug just always print this. if (!behindTheScenes) #endif { G_Printf(S_COLOR_RED "Could not link %i to %i, unreachable by node branching.\n", startindex, endindex); } gWPArray[startindex]->flags |= WPFLAG_ONEWAY_FWD; gWPArray[endindex]->flags |= WPFLAG_ONEWAY_BACK; if (!behindTheScenes) { 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); } /*while (nodenum >= 0) { if (nodetable[nodenum].origin[0] || nodetable[nodenum].origin[1] || nodetable[nodenum].origin[2]) { CreateNewWP(nodetable[nodenum].origin, nodetable[nodenum].flags); } nodenum--; }*/ //The above code transfers nodes into the "rendered" waypoint array. Strictly for debugging. if (!behindTheScenes) { //just use what we have if we're auto-pathing the level return 0; } else { vec3_t endDist; int nCount = 0; int idealNode = -1; float bestDist = 0; float testDist; if (nodenum <= 10) { //not enough to even really bother. return 0; } //Since it failed, find whichever node is closest to the desired end. while (nCount < nodenum) { VectorSubtract(nodetable[nCount].origin, gWPArray[endindex]->origin, endDist); testDist = VectorLength(endDist); if (idealNode == -1) { idealNode = nCount; bestDist = testDist; nCount++; continue; } if (testDist < bestDist) { idealNode = nCount; bestDist = testDist; } nCount++; } if (idealNode == -1) { return 0; } successnodeindex = idealNode; } } i = successnodeindex; insertindex = startindex; failsafe = 0; VectorCopy(gWPArray[startindex]->origin, validspotpos); while (failsafe < MAX_NODETABLE_SIZE && i < MAX_NODETABLE_SIZE && i >= 0) { VectorSubtract(validspotpos, nodetable[i].origin, a); 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)) ) { nodetable[i].flags |= WPFLAG_CALCULATED; if (!CreateNewWP_InTrail(nodetable[i].origin, nodetable[i].flags, insertindex)) { if (!behindTheScenes) { G_Printf(S_COLOR_RED "Could not link %i to %i, waypoint limit hit.\n", startindex, endindex); } return 0; } VectorCopy(nodetable[i].origin, validspotpos); } if (i == 0) { break; } i = nodetable[i].neighbornum; failsafe++; } if (!behindTheScenes) { G_Printf(S_COLOR_YELLOW "Finished connecting %i to %i.\n", startindex, endindex); } return 1; } #endif int OpposingEnds(int start, int end) { if (!gWPArray[start] || !gWPArray[start]->inuse || !gWPArray[end] || !gWPArray[end]->inuse) { return 0; } if ((gWPArray[start]->flags & WPFLAG_ONEWAY_FWD) && (gWPArray[end]->flags & WPFLAG_ONEWAY_BACK)) { return 1; } return 0; } int DoorBlockingSection(int start, int end) { //if a door blocks the trail, we'll just have to assume the points on each side are in visibility when it's open trace_t tr; gentity_t *testdoor; int start_trace_index; if (!gWPArray[start] || !gWPArray[start]->inuse || !gWPArray[end] || !gWPArray[end]->inuse) { return 0; } trap_Trace(&tr, gWPArray[start]->origin, NULL, NULL, gWPArray[end]->origin, ENTITYNUM_NONE, MASK_SOLID); if (tr.fraction == 1) { return 0; } testdoor = &g_entities[tr.entityNum]; if (!testdoor) { return 0; } if (!strstr(testdoor->classname, "func_")) { return 0; } start_trace_index = tr.entityNum; trap_Trace(&tr, gWPArray[end]->origin, NULL, NULL, gWPArray[start]->origin, ENTITYNUM_NONE, MASK_SOLID); if (tr.fraction == 1) { return 0; } if (start_trace_index == tr.entityNum) { return 1; } return 0; } #ifndef _XBOX int RepairPaths(qboolean behindTheScenes) { int i; int preAmount = 0; int ctRet; vec3_t a; float maxDistFactor = 400; if (!gWPNum) { return 0; } if (g_RMG.integer) { maxDistFactor = 800; //higher tolerance here. } i = 0; preAmount = gWPNum; trap_Cvar_Update(&bot_wp_distconnect); trap_Cvar_Update(&bot_wp_visconnect); while (i < gWPNum) { if (gWPArray[i] && gWPArray[i]->inuse && gWPArray[i+1] && gWPArray[i+1]->inuse) { VectorSubtract(gWPArray[i]->origin, gWPArray[i+1]->origin, a); if (!(gWPArray[i+1]->flags & WPFLAG_NOVIS) && !(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) !(gWPArray[i]->flags & WPFLAG_CALCULATED) && //don't calculate it again !OpposingEnds(i, i+1) && ((bot_wp_distconnect.value && VectorLength(a) > maxDistFactor) || (!OrgVisible(gWPArray[i]->origin, gWPArray[i+1]->origin, ENTITYNUM_NONE) && bot_wp_visconnect.value) ) && !DoorBlockingSection(i, i+1)) { ctRet = ConnectTrail(i, i+1, behindTheScenes); if (gWPNum >= MAX_WPARRAY_SIZE) { //Bad! gWPNum = MAX_WPARRAY_SIZE; break; } /*if (!ctRet) { return 0; }*/ //we still want to write it.. } } i++; } return 1; } #endif int OrgVisibleCurve(vec3_t org1, vec3_t mins, vec3_t maxs, vec3_t org2, int ignore) { trace_t tr; vec3_t evenorg1; VectorCopy(org1, evenorg1); evenorg1[2] = org2[2]; trap_Trace(&tr, evenorg1, mins, maxs, org2, ignore, MASK_SOLID); if (tr.fraction == 1 && !tr.startsolid && !tr.allsolid) { trap_Trace(&tr, evenorg1, mins, maxs, org1, ignore, MASK_SOLID); if (tr.fraction == 1 && !tr.startsolid && !tr.allsolid) { return 1; } } return 0; } int CanForceJumpTo(int baseindex, int testingindex, float distance) { float heightdif; vec3_t xy_base, xy_test, v, mins, maxs; wpobject_t *wpBase = gWPArray[baseindex]; wpobject_t *wpTest = gWPArray[testingindex]; mins[0] = -15; mins[1] = -15; mins[2] = -15; //-1 maxs[0] = 15; maxs[1] = 15; maxs[2] = 15; //1 if (!wpBase || !wpBase->inuse || !wpTest || !wpTest->inuse) { return 0; } if (distance > 400) { return 0; } VectorCopy(wpBase->origin, xy_base); VectorCopy(wpTest->origin, xy_test); xy_base[2] = xy_test[2]; VectorSubtract(xy_base, xy_test, v); if (VectorLength(v) > MAX_NEIGHBOR_LINK_DISTANCE) { return 0; } if ((int)wpBase->origin[2] < (int)wpTest->origin[2]) { heightdif = wpTest->origin[2] - wpBase->origin[2]; } else { return 0; //err.. } if (heightdif < 128) { //don't bother.. return 0; } if (heightdif > 512) { //too high return 0; } if (!OrgVisibleCurve(wpBase->origin, mins, maxs, wpTest->origin, ENTITYNUM_NONE)) { return 0; } if (heightdif > 400) { return 3; } else if (heightdif > 256) { return 2; } else { return 1; } } void CalculatePaths(void) { int i; int c; int forceJumpable; int maxNeighborDist = MAX_NEIGHBOR_LINK_DISTANCE; float nLDist; vec3_t a; vec3_t mins, maxs; if (!gWPNum) { return; } if (g_RMG.integer) { maxNeighborDist = DEFAULT_GRID_SPACING + (DEFAULT_GRID_SPACING*0.5); } mins[0] = -15; mins[1] = -15; mins[2] = -15; //-1 maxs[0] = 15; maxs[1] = 15; maxs[2] = 15; //1 //now clear out all the neighbor data before we recalculate i = 0; while (i < gWPNum) { if (gWPArray[i] && gWPArray[i]->inuse && gWPArray[i]->neighbornum) { while (gWPArray[i]->neighbornum >= 0) { gWPArray[i]->neighbors[gWPArray[i]->neighbornum].num = 0; gWPArray[i]->neighbors[gWPArray[i]->neighbornum].forceJumpTo = 0; gWPArray[i]->neighbornum--; } gWPArray[i]->neighbornum = 0; } i++; } i = 0; while (i < gWPNum) { if (gWPArray[i] && gWPArray[i]->inuse) { c = 0; while (c < gWPNum) { if (gWPArray[c] && gWPArray[c]->inuse && i != c && NotWithinRange(i, c)) { VectorSubtract(gWPArray[i]->origin, gWPArray[c]->origin, a); nLDist = VectorLength(a); forceJumpable = CanForceJumpTo(i, c, nLDist); if ((nLDist < maxNeighborDist || forceJumpable) && ((int)gWPArray[i]->origin[2] == (int)gWPArray[c]->origin[2] || forceJumpable) && (OrgVisibleBox(gWPArray[i]->origin, mins, maxs, gWPArray[c]->origin, ENTITYNUM_NONE) || forceJumpable)) { gWPArray[i]->neighbors[gWPArray[i]->neighbornum].num = c; if (forceJumpable && ((int)gWPArray[i]->origin[2] != (int)gWPArray[c]->origin[2] || nLDist < maxNeighborDist)) { gWPArray[i]->neighbors[gWPArray[i]->neighbornum].forceJumpTo = 999;//forceJumpable; //FJSR } else { gWPArray[i]->neighbors[gWPArray[i]->neighbornum].forceJumpTo = 0; } gWPArray[i]->neighbornum++; } if (gWPArray[i]->neighbornum >= MAX_NEIGHBOR_SIZE) { break; } } c++; } } i++; } } gentity_t *GetObjectThatTargets(gentity_t *ent) { gentity_t *next = NULL; if (!ent->targetname) { return NULL; } next = G_Find( next, FOFS(target), ent->targetname ); if (next) { return next; } return NULL; } void CalculateSiegeGoals(void) { int i = 0; int looptracker = 0; int wpindex = 0; vec3_t dif; gentity_t *ent; gentity_t *tent = NULL, *t2ent = NULL; while (i < level.num_entities) { ent = &g_entities[i]; tent = NULL; if (ent && ent->classname && strcmp(ent->classname, "info_siege_objective") == 0) { tent = ent; t2ent = GetObjectThatTargets(tent); looptracker = 0; while (t2ent && looptracker < 2048) { //looptracker keeps us from getting stuck in case something is set up weird on this map tent = t2ent; t2ent = GetObjectThatTargets(tent); looptracker++; } if (looptracker >= 2048) { //something unpleasent has happened tent = NULL; break; } } if (tent && ent && tent != ent) { //tent should now be the object attached to the mission objective dif[0] = (tent->r.absmax[0]+tent->r.absmin[0])/2; dif[1] = (tent->r.absmax[1]+tent->r.absmin[1])/2; dif[2] = (tent->r.absmax[2]+tent->r.absmin[2])/2; wpindex = GetNearestVisibleWP(dif, tent->s.number); if (wpindex != -1 && gWPArray[wpindex] && gWPArray[wpindex]->inuse) { //found the waypoint nearest the center of this objective-related object if (ent->side == SIEGETEAM_TEAM1) { gWPArray[wpindex]->flags |= WPFLAG_SIEGE_IMPERIALOBJ; } else { gWPArray[wpindex]->flags |= WPFLAG_SIEGE_REBELOBJ; } gWPArray[wpindex]->associated_entity = tent->s.number; } } i++; } } float botGlobalNavWeaponWeights[WP_NUM_WEAPONS] = { 0,//WP_NONE, 0,//WP_STUN_BATON, 0,//WP_MELEE 0,//WP_SABER, // NOTE: lots of code assumes this is the first weapon (... which is crap) so be careful -Ste. 0,//WP_BRYAR_PISTOL, 3,//WP_BLASTER, 5,//WP_DISRUPTOR, 4,//WP_BOWCASTER, 6,//WP_REPEATER, 7,//WP_DEMP2, 8,//WP_FLECHETTE, 9,//WP_ROCKET_LAUNCHER, 3,//WP_THERMAL, 3,//WP_TRIP_MINE, 3,//WP_DET_PACK, 0//WP_EMPLACED_GUN, }; int GetNearestVisibleWPToItem(vec3_t org, int ignore) { int i; float bestdist; float flLen; int bestindex; vec3_t a, mins, maxs; i = 0; bestdist = 64; //has to be less than 64 units to the item or it isn't safe enough bestindex = -1; mins[0] = -15; mins[1] = -15; mins[2] = 0; maxs[0] = 15; maxs[1] = 15; maxs[2] = 0; while (i < gWPNum) { if (gWPArray[i] && gWPArray[i]->inuse && gWPArray[i]->origin[2]-15 < org[2] && gWPArray[i]->origin[2]+15 > org[2]) { VectorSubtract(org, gWPArray[i]->origin, a); flLen = VectorLength(a); if (flLen < bestdist && trap_InPVS(org, gWPArray[i]->origin) && OrgVisibleBox(org, mins, maxs, gWPArray[i]->origin, ignore)) { bestdist = flLen; bestindex = i; } } i++; } return bestindex; } void CalculateWeightGoals(void) { //set waypoint weights depending on weapon and item placement int i = 0; int wpindex = 0; gentity_t *ent; float weight; trap_Cvar_Update(&bot_wp_clearweight); if (bot_wp_clearweight.integer) { //if set then flush out all weight/goal values before calculating them again while (i < gWPNum) { if (gWPArray[i] && gWPArray[i]->inuse) { gWPArray[i]->weight = 0; if (gWPArray[i]->flags & WPFLAG_GOALPOINT) { gWPArray[i]->flags -= WPFLAG_GOALPOINT; } } i++; } } i = 0; while (i < level.num_entities) { ent = &g_entities[i]; weight = 0; if (ent && ent->classname) { if (strcmp(ent->classname, "item_seeker") == 0) { weight = 2; } else if (strcmp(ent->classname, "item_shield") == 0) { weight = 2; } else if (strcmp(ent->classname, "item_medpac") == 0) { weight = 2; } else if (strcmp(ent->classname, "item_sentry_gun") == 0) { weight = 2; } else if (strcmp(ent->classname, "item_force_enlighten_dark") == 0) { weight = 5; } else if (strcmp(ent->classname, "item_force_enlighten_light") == 0) { weight = 5; } else if (strcmp(ent->classname, "item_force_boon") == 0) { weight = 5; } else if (strcmp(ent->classname, "item_ysalimari") == 0) { weight = 2; } else if (strstr(ent->classname, "weapon_") && ent->item) { weight = botGlobalNavWeaponWeights[ent->item->giTag]; } else if (ent->item && ent->item->giType == IT_AMMO) { weight = 3; } } if (ent && weight) { wpindex = GetNearestVisibleWPToItem(ent->s.pos.trBase, ent->s.number); if (wpindex != -1 && gWPArray[wpindex] && gWPArray[wpindex]->inuse) { //found the waypoint nearest the center of this object gWPArray[wpindex]->weight = weight; gWPArray[wpindex]->flags |= WPFLAG_GOALPOINT; gWPArray[wpindex]->associated_entity = ent->s.number; } } i++; } } void CalculateJumpRoutes(void) { int i = 0; float nheightdif = 0; float pheightdif = 0; while (i < gWPNum) { if (gWPArray[i] && gWPArray[i]->inuse) { if (gWPArray[i]->flags & WPFLAG_JUMP) { nheightdif = 0; pheightdif = 0; gWPArray[i]->forceJumpTo = 0; if (gWPArray[i-1] && gWPArray[i-1]->inuse && (gWPArray[i-1]->origin[2]+16) < gWPArray[i]->origin[2]) { nheightdif = (gWPArray[i]->origin[2] - gWPArray[i-1]->origin[2]); } if (gWPArray[i+1] && gWPArray[i+1]->inuse && (gWPArray[i+1]->origin[2]+16) < gWPArray[i]->origin[2]) { pheightdif = (gWPArray[i]->origin[2] - gWPArray[i+1]->origin[2]); } if (nheightdif > pheightdif) { pheightdif = nheightdif; } if (pheightdif) { if (pheightdif > 500) { gWPArray[i]->forceJumpTo = 999; //FORCE_LEVEL_3; //FJSR } else if (pheightdif > 256) { gWPArray[i]->forceJumpTo = 999; //FORCE_LEVEL_2; //FJSR } else if (pheightdif > 128) { gWPArray[i]->forceJumpTo = 999; //FORCE_LEVEL_1; //FJSR } } } } i++; } } int LoadPathData(const char *filename) { fileHandle_t f; char *fileString; char *currentVar; char *routePath; wpobject_t thiswp; int len; int i, i_cv; int nei_num; i = 0; i_cv = 0; routePath = (char *)B_TempAlloc(1024); Com_sprintf(routePath, 1024, "botroutes/%s.wnt\0", filename); len = trap_FS_FOpenFile(routePath, &f, FS_READ); B_TempFree(1024); //routePath if (!f) { G_Printf(S_COLOR_YELLOW "Bot route data not found for %s\n", filename); return 2; } if (len >= 524288) { G_Printf(S_COLOR_RED "Route file exceeds maximum length\n"); return 0; } fileString = (char *)B_TempAlloc(524288); currentVar = (char *)B_TempAlloc(2048); trap_FS_Read(fileString, len, f); if (fileString[i] == 'l') { //contains a "levelflags" entry.. char readLFlags[64]; i_cv = 0; while (fileString[i] != ' ') { i++; } i++; while (fileString[i] != '\n') { readLFlags[i_cv] = fileString[i]; i_cv++; i++; } readLFlags[i_cv] = 0; i++; gLevelFlags = atoi(readLFlags); } else { gLevelFlags = 0; } while (i < len) { i_cv = 0; thiswp.index = 0; thiswp.flags = 0; thiswp.inuse = 0; thiswp.neighbornum = 0; thiswp.origin[0] = 0; thiswp.origin[1] = 0; thiswp.origin[2] = 0; thiswp.weight = 0; thiswp.associated_entity = ENTITYNUM_NONE; thiswp.forceJumpTo = 0; thiswp.disttonext = 0; nei_num = 0; while (nei_num < MAX_NEIGHBOR_SIZE) { thiswp.neighbors[nei_num].num = 0; thiswp.neighbors[nei_num].forceJumpTo = 0; nei_num++; } while (fileString[i] != ' ') { currentVar[i_cv] = fileString[i]; i_cv++; i++; } currentVar[i_cv] = '\0'; thiswp.index = atoi(currentVar); i_cv = 0; i++; while (fileString[i] != ' ') { currentVar[i_cv] = fileString[i]; i_cv++; i++; } currentVar[i_cv] = '\0'; thiswp.flags = atoi(currentVar); i_cv = 0; i++; while (fileString[i] != ' ') { currentVar[i_cv] = fileString[i]; i_cv++; i++; } currentVar[i_cv] = '\0'; thiswp.weight = atof(currentVar); i_cv = 0; i++; i++; while (fileString[i] != ' ') { currentVar[i_cv] = fileString[i]; i_cv++; i++; } currentVar[i_cv] = '\0'; thiswp.origin[0] = atof(currentVar); i_cv = 0; i++; while (fileString[i] != ' ') { currentVar[i_cv] = fileString[i]; i_cv++; i++; } currentVar[i_cv] = '\0'; thiswp.origin[1] = atof(currentVar); i_cv = 0; i++; while (fileString[i] != ')') { currentVar[i_cv] = fileString[i]; i_cv++; i++; } currentVar[i_cv] = '\0'; thiswp.origin[2] = atof(currentVar); i += 4; while (fileString[i] != '}') { i_cv = 0; while (fileString[i] != ' ' && fileString[i] != '-') { currentVar[i_cv] = fileString[i]; i_cv++; i++; } currentVar[i_cv] = '\0'; thiswp.neighbors[thiswp.neighbornum].num = atoi(currentVar); if (fileString[i] == '-') { i_cv = 0; i++; while (fileString[i] != ' ') { currentVar[i_cv] = fileString[i]; i_cv++; i++; } currentVar[i_cv] = '\0'; thiswp.neighbors[thiswp.neighbornum].forceJumpTo = 999; //atoi(currentVar); //FJSR } else { thiswp.neighbors[thiswp.neighbornum].forceJumpTo = 0; } thiswp.neighbornum++; i++; } i_cv = 0; i++; i++; while (fileString[i] != '\n') { currentVar[i_cv] = fileString[i]; i_cv++; i++; } currentVar[i_cv] = '\0'; thiswp.disttonext = atof(currentVar); CreateNewWP_FromObject(&thiswp); i++; } B_TempFree(524288); //fileString B_TempFree(2048); //currentVar trap_FS_FCloseFile(f); if (g_gametype.integer == GT_SIEGE) { CalculateSiegeGoals(); } CalculateWeightGoals(); //calculate weights for idle activity goals when //the bot has absolutely nothing else to do CalculateJumpRoutes(); //Look at jump points and mark them as requiring //force jumping as needed return 1; } void FlagObjects(void) { int i = 0, bestindex = 0, found = 0; float bestdist = 999999, tlen = 0; gentity_t *flag_red, *flag_blue, *ent; vec3_t a, mins, maxs; trace_t tr; flag_red = NULL; flag_blue = NULL; mins[0] = -15; mins[1] = -15; mins[2] = -5; maxs[0] = 15; maxs[1] = 15; maxs[2] = 5; while (i < level.num_entities) { ent = &g_entities[i]; if (ent && ent->inuse && ent->classname) { if (!flag_red && strcmp(ent->classname, "team_CTF_redflag") == 0) { flag_red = ent; } else if (!flag_blue && strcmp(ent->classname, "team_CTF_blueflag") == 0) { flag_blue = ent; } if (flag_red && flag_blue) { break; } } i++; } i = 0; if (!flag_red || !flag_blue) { return; } while (i < gWPNum) { if (gWPArray[i] && gWPArray[i]->inuse) { VectorSubtract(flag_red->s.pos.trBase, gWPArray[i]->origin, a); tlen = VectorLength(a); if (tlen < bestdist) { trap_Trace(&tr, flag_red->s.pos.trBase, mins, maxs, gWPArray[i]->origin, flag_red->s.number, MASK_SOLID); if (tr.fraction == 1 || tr.entityNum == flag_red->s.number) { bestdist = tlen; bestindex = i; found = 1; } } } i++; } if (found) { gWPArray[bestindex]->flags |= WPFLAG_RED_FLAG; flagRed = gWPArray[bestindex]; oFlagRed = flagRed; eFlagRed = flag_red; } bestdist = 999999; bestindex = 0; found = 0; i = 0; while (i < gWPNum) { if (gWPArray[i] && gWPArray[i]->inuse) { VectorSubtract(flag_blue->s.pos.trBase, gWPArray[i]->origin, a); tlen = VectorLength(a); if (tlen < bestdist) { trap_Trace(&tr, flag_blue->s.pos.trBase, mins, maxs, gWPArray[i]->origin, flag_blue->s.number, MASK_SOLID); if (tr.fraction == 1 || tr.entityNum == flag_blue->s.number) { bestdist = tlen; bestindex = i; found = 1; } } } i++; } if (found) { gWPArray[bestindex]->flags |= WPFLAG_BLUE_FLAG; flagBlue = gWPArray[bestindex]; oFlagBlue = flagBlue; eFlagBlue = flag_blue; } } #ifndef _XBOX int SavePathData(const char *filename) { fileHandle_t f; char *fileString; char *storeString; char *routePath; vec3_t a; float flLen; int i, s, n; fileString = NULL; i = 0; s = 0; if (!gWPNum) { return 0; } routePath = (char *)B_TempAlloc(1024); Com_sprintf(routePath, 1024, "botroutes/%s.wnt\0", filename); trap_FS_FOpenFile(routePath, &f, FS_WRITE); B_TempFree(1024); //routePath if (!f) { G_Printf(S_COLOR_RED "ERROR: Could not open file to write path data\n"); return 0; } if (!RepairPaths(qfalse)) //check if we can see all waypoints from the last. If not, try to branch over. { trap_FS_FCloseFile(f); return 0; } CalculatePaths(); //make everything nice and connected before saving FlagObjects(); //currently only used for flagging waypoints nearest CTF flags fileString = (char *)B_TempAlloc(524288); storeString = (char *)B_TempAlloc(4096); 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]); n = 0; while (n < gWPArray[i]->neighbornum) { if (gWPArray[i]->neighbors[n].forceJumpTo) { Com_sprintf(storeString, 4096, "%s%i-%i ", storeString, gWPArray[i]->neighbors[n].num, gWPArray[i]->neighbors[n].forceJumpTo); } else { Com_sprintf(storeString, 4096, "%s%i ", storeString, gWPArray[i]->neighbors[n].num); } n++; } if (gWPArray[i+1] && gWPArray[i+1]->inuse && gWPArray[i+1]->index) { VectorSubtract(gWPArray[i]->origin, gWPArray[i+1]->origin, a); flLen = VectorLength(a); } else { flLen = 0; } gWPArray[i]->disttonext = flLen; Com_sprintf(fileString, 524288, "%s} %f\n", fileString, flLen); i++; while (i < gWPNum) { //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]); 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]); n = 0; while (n < gWPArray[i]->neighbornum) { if (gWPArray[i]->neighbors[n].forceJumpTo) { Com_sprintf(storeString, 4096, "%s%i-%i ", storeString, gWPArray[i]->neighbors[n].num, gWPArray[i]->neighbors[n].forceJumpTo); } else { Com_sprintf(storeString, 4096, "%s%i ", storeString, gWPArray[i]->neighbors[n].num); } n++; } if (gWPArray[i+1] && gWPArray[i+1]->inuse && gWPArray[i+1]->index) { VectorSubtract(gWPArray[i]->origin, gWPArray[i+1]->origin, a); flLen = VectorLength(a); } else { flLen = 0; } gWPArray[i]->disttonext = flLen; Com_sprintf(storeString, 4096, "%s} %f\n", storeString, flLen); strcat(fileString, storeString); i++; } trap_FS_Write(fileString, strlen(fileString), f); B_TempFree(524288); //fileString B_TempFree(4096); //storeString trap_FS_FCloseFile(f); G_Printf("Path data has been saved and updated. You may need to restart the level for some things to be properly calculated.\n"); return 1; } #endif //#define PAINFULLY_DEBUGGING_THROUGH_VM #define MAX_SPAWNPOINT_ARRAY 64 int gSpawnPointNum = 0; gentity_t *gSpawnPoints[MAX_SPAWNPOINT_ARRAY]; #ifndef _XBOX int G_NearestNodeToPoint(vec3_t point) { //gets the node on the entire grid which is nearest to the specified coordinates. vec3_t vSub; int bestIndex = -1; int i = 0; float bestDist = 0; float testDist = 0; while (i < nodenum) { VectorSubtract(nodetable[i].origin, point, vSub); testDist = VectorLength(vSub); if (bestIndex == -1) { bestIndex = i; bestDist = testDist; i++; continue; } if (testDist < bestDist) { bestIndex = i; bestDist = testDist; } i++; } return bestIndex; } #endif #ifndef _XBOX void G_NodeClearForNext(void) { //reset nodes for the next trail connection. int i = 0; while (i < nodenum) { nodetable[i].flags = 0; nodetable[i].weight = 99999; i++; } } void G_NodeClearFlags(void) { //only clear out flags so nodes can be reused. int i = 0; while (i < nodenum) { nodetable[i].flags = 0; i++; } } int G_NodeMatchingXY(float x, float y) { //just get the first unflagged node with the matching x,y coordinates. int i = 0; while (i < nodenum) { if (nodetable[i].origin[0] == x && nodetable[i].origin[1] == y && !nodetable[i].flags) { return i; } i++; } return -1; } int G_NodeMatchingXY_BA(int x, int y, int final) { //return the node with the lowest weight that matches the specified x,y coordinates. int i = 0; int bestindex = -1; float bestWeight = 9999; while (i < nodenum) { if ((int)nodetable[i].origin[0] == x && (int)nodetable[i].origin[1] == y && !nodetable[i].flags && ((nodetable[i].weight < bestWeight) || (i == final))) { if (i == final) { return i; } bestindex = i; bestWeight = nodetable[i].weight; } i++; } return bestindex; } int G_RecursiveConnection(int start, int end, int weight, qboolean traceCheck, float baseHeight) { int indexDirections[4]; //0 == down, 1 == up, 2 == left, 3 == right int recursiveIndex = -1; int i = 0; int passWeight = weight; vec2_t givenXY; trace_t tr; passWeight++; nodetable[start].weight = passWeight; givenXY[0] = nodetable[start].origin[0]; givenXY[1] = nodetable[start].origin[1]; givenXY[0] -= DEFAULT_GRID_SPACING; indexDirections[0] = G_NodeMatchingXY(givenXY[0], givenXY[1]); givenXY[0] = nodetable[start].origin[0]; givenXY[1] = nodetable[start].origin[1]; givenXY[0] += DEFAULT_GRID_SPACING; indexDirections[1] = G_NodeMatchingXY(givenXY[0], givenXY[1]); givenXY[0] = nodetable[start].origin[0]; givenXY[1] = nodetable[start].origin[1]; givenXY[1] -= DEFAULT_GRID_SPACING; indexDirections[2] = G_NodeMatchingXY(givenXY[0], givenXY[1]); givenXY[0] = nodetable[start].origin[0]; givenXY[1] = nodetable[start].origin[1]; givenXY[1] += DEFAULT_GRID_SPACING; indexDirections[3] = G_NodeMatchingXY(givenXY[0], givenXY[1]); i = 0; while (i < 4) { if (indexDirections[i] == end) { //we've connected all the way to the destination. return indexDirections[i]; } if (indexDirections[i] != -1 && nodetable[indexDirections[i]].flags) { //this point is already used, so it's not valid. indexDirections[i] = -1; } else if (indexDirections[i] != -1) { //otherwise mark it as used. nodetable[indexDirections[i]].flags = 1; } if (indexDirections[i] != -1 && traceCheck) { //if we care about trace visibility between nodes, perform the check and mark as not valid if the trace isn't clear. trap_Trace(&tr, nodetable[start].origin, NULL, NULL, nodetable[indexDirections[i]].origin, ENTITYNUM_NONE, CONTENTS_SOLID); if (tr.fraction != 1) { indexDirections[i] = -1; } } if (indexDirections[i] != -1) { //it's still valid, so keep connecting via this point. recursiveIndex = G_RecursiveConnection(indexDirections[i], end, passWeight, traceCheck, baseHeight); } if (recursiveIndex != -1) { //the result of the recursive check was valid, so return it. return recursiveIndex; } i++; } return recursiveIndex; } #ifdef DEBUG_NODE_FILE void G_DebugNodeFile() { fileHandle_t f; int i = 0; float placeX; char fileString[131072]; gentity_t *terrain = G_Find( NULL, FOFS(classname), "terrain" ); fileString[0] = 0; placeX = terrain->r.absmin[0]; while (i < nodenum) { strcat(fileString, va("%i-%f ", i, nodetable[i].weight)); placeX += DEFAULT_GRID_SPACING; if (placeX >= terrain->r.absmax[0]) { strcat(fileString, "\n"); placeX = terrain->r.absmin[0]; } i++; } trap_FS_FOpenFile("ROUTEDEBUG.txt", &f, FS_WRITE); trap_FS_Write(fileString, strlen(fileString), f); trap_FS_FCloseFile(f); } #endif #endif //#define ASCII_ART_DEBUG //#define ASCII_ART_NODE_DEBUG #ifdef ASCII_ART_DEBUG #define ALLOWABLE_DEBUG_FILE_SIZE 1048576 void CreateAsciiTableRepresentation() { //Draw a text grid of the entire waypoint array (useful for debugging final waypoint placement) fileHandle_t f; int i = 0; int sP = 0; int placeX; int placeY; int oldX; int oldY; char fileString[ALLOWABLE_DEBUG_FILE_SIZE]; char bChr = '+'; gentity_t *terrain = G_Find( NULL, FOFS(classname), "terrain" ); placeX = terrain->r.absmin[0]; placeY = terrain->r.absmin[1]; oldX = placeX-1; oldY = placeY-1; while (placeY < terrain->r.absmax[1]) { while (placeX < terrain->r.absmax[0]) { qboolean gotit = qfalse; i = 0; while (i < gWPNum) { if (((int)gWPArray[i]->origin[0] <= placeX && (int)gWPArray[i]->origin[0] > oldX) && ((int)gWPArray[i]->origin[1] <= placeY && (int)gWPArray[i]->origin[1] > oldY)) { gotit = qtrue; break; } i++; } if (gotit) { if (gWPArray[i]->flags & WPFLAG_ONEWAY_FWD) { bChr = 'F'; } else if (gWPArray[i]->flags & WPFLAG_ONEWAY_BACK) { bChr = 'B'; } else { bChr = '+'; } if (gWPArray[i]->index < 10) { fileString[sP] = bChr; fileString[sP+1] = '0'; fileString[sP+2] = '0'; fileString[sP+3] = va("%i", gWPArray[i]->index)[0]; } else if (gWPArray[i]->index < 100) { char *vastore = va("%i", gWPArray[i]->index); fileString[sP] = bChr; fileString[sP+1] = '0'; fileString[sP+2] = vastore[0]; fileString[sP+3] = vastore[1]; } else if (gWPArray[i]->index < 1000) { char *vastore = va("%i", gWPArray[i]->index); fileString[sP] = bChr; fileString[sP+1] = vastore[0]; fileString[sP+2] = vastore[1]; fileString[sP+3] = vastore[2]; } else { fileString[sP] = 'X'; fileString[sP+1] = 'X'; fileString[sP+2] = 'X'; fileString[sP+3] = 'X'; } } else { fileString[sP] = '-'; fileString[sP+1] = '-'; fileString[sP+2] = '-'; fileString[sP+3] = '-'; } sP += 4; if (sP >= ALLOWABLE_DEBUG_FILE_SIZE-16) { break; } oldX = placeX; placeX += DEFAULT_GRID_SPACING; } placeX = terrain->r.absmin[0]; oldX = placeX-1; fileString[sP] = '\n'; sP++; if (sP >= ALLOWABLE_DEBUG_FILE_SIZE-16) { break; } oldY = placeY; placeY += DEFAULT_GRID_SPACING; } fileString[sP] = 0; trap_FS_FOpenFile("ROUTEDRAWN.txt", &f, FS_WRITE); trap_FS_Write(fileString, strlen(fileString), f); trap_FS_FCloseFile(f); } void CreateAsciiNodeTableRepresentation(int start, int end) { //draw a text grid of a single node path, from point A to Z. fileHandle_t f; int i = 0; int sP = 0; int placeX; int placeY; int oldX; int oldY; char fileString[ALLOWABLE_DEBUG_FILE_SIZE]; gentity_t *terrain = G_Find( NULL, FOFS(classname), "terrain" ); placeX = terrain->r.absmin[0]; placeY = terrain->r.absmin[1]; oldX = placeX-1; oldY = placeY-1; while (placeY < terrain->r.absmax[1]) { while (placeX < terrain->r.absmax[0]) { qboolean gotit = qfalse; i = 0; while (i < nodenum) { if (((int)nodetable[i].origin[0] <= placeX && (int)nodetable[i].origin[0] > oldX) && ((int)nodetable[i].origin[1] <= placeY && (int)nodetable[i].origin[1] > oldY)) { gotit = qtrue; break; } i++; } if (gotit) { if (i == start) { //beginning of the node trail fileString[sP] = 'A'; fileString[sP+1] = 'A'; fileString[sP+2] = 'A'; fileString[sP+3] = 'A'; } else if (i == end) { //destination of the node trail fileString[sP] = 'Z'; fileString[sP+1] = 'Z'; fileString[sP+2] = 'Z'; fileString[sP+3] = 'Z'; } else if (nodetable[i].weight < 10) { fileString[sP] = '+'; fileString[sP+1] = '0'; fileString[sP+2] = '0'; fileString[sP+3] = va("%f", nodetable[i].weight)[0]; } else if (nodetable[i].weight < 100) { char *vastore = va("%f", nodetable[i].weight); fileString[sP] = '+'; fileString[sP+1] = '0'; fileString[sP+2] = vastore[0]; fileString[sP+3] = vastore[1]; } else if (nodetable[i].weight < 1000) { char *vastore = va("%f", nodetable[i].weight); fileString[sP] = '+'; fileString[sP+1] = vastore[0]; fileString[sP+2] = vastore[1]; fileString[sP+3] = vastore[2]; } else { fileString[sP] = 'X'; fileString[sP+1] = 'X'; fileString[sP+2] = 'X'; fileString[sP+3] = 'X'; } } else { fileString[sP] = '-'; fileString[sP+1] = '-'; fileString[sP+2] = '-'; fileString[sP+3] = '-'; } sP += 4; if (sP >= ALLOWABLE_DEBUG_FILE_SIZE-16) { break; } oldX = placeX; placeX += DEFAULT_GRID_SPACING; } placeX = terrain->r.absmin[0]; oldX = placeX-1; fileString[sP] = '\n'; sP++; if (sP >= ALLOWABLE_DEBUG_FILE_SIZE-16) { break; } oldY = placeY; placeY += DEFAULT_GRID_SPACING; } fileString[sP] = 0; trap_FS_FOpenFile("ROUTEDRAWN.txt", &f, FS_WRITE); trap_FS_Write(fileString, strlen(fileString), f); trap_FS_FCloseFile(f); } #endif #ifndef _XBOX qboolean G_BackwardAttachment(int start, int finalDestination, int insertAfter) { //After creating a node path between 2 points, this function links the 2 points with actual waypoint data. int indexDirections[4]; //0 == down, 1 == up, 2 == left, 3 == right int i = 0; int lowestWeight = 9999; int desiredIndex = -1; vec2_t givenXY; givenXY[0] = nodetable[start].origin[0]; givenXY[1] = nodetable[start].origin[1]; givenXY[0] -= DEFAULT_GRID_SPACING; indexDirections[0] = G_NodeMatchingXY_BA(givenXY[0], givenXY[1], finalDestination); givenXY[0] = nodetable[start].origin[0]; givenXY[1] = nodetable[start].origin[1]; givenXY[0] += DEFAULT_GRID_SPACING; indexDirections[1] = G_NodeMatchingXY_BA(givenXY[0], givenXY[1], finalDestination); givenXY[0] = nodetable[start].origin[0]; givenXY[1] = nodetable[start].origin[1]; givenXY[1] -= DEFAULT_GRID_SPACING; indexDirections[2] = G_NodeMatchingXY_BA(givenXY[0], givenXY[1], finalDestination); givenXY[0] = nodetable[start].origin[0]; givenXY[1] = nodetable[start].origin[1]; givenXY[1] += DEFAULT_GRID_SPACING; indexDirections[3] = G_NodeMatchingXY_BA(givenXY[0], givenXY[1], finalDestination); while (i < 4) { if (indexDirections[i] != -1) { if (indexDirections[i] == finalDestination) { //hooray, we've found the original point and linked all the way back to it. CreateNewWP_InsertUnder(nodetable[start].origin, 0, insertAfter); CreateNewWP_InsertUnder(nodetable[indexDirections[i]].origin, 0, insertAfter); return qtrue; } if (nodetable[indexDirections[i]].weight < lowestWeight && nodetable[indexDirections[i]].weight && !nodetable[indexDirections[i]].flags /*&& (nodetable[indexDirections[i]].origin[2]-64 < nodetable[start].origin[2])*/) { desiredIndex = indexDirections[i]; lowestWeight = nodetable[indexDirections[i]].weight; } } i++; } if (desiredIndex != -1) { //Create a waypoint here, and then recursively call this function for the next neighbor with the lowest weight. if (gWPNum < 3900) { CreateNewWP_InsertUnder(nodetable[start].origin, 0, insertAfter); } else { #ifdef PAINFULLY_DEBUGGING_THROUGH_VM Com_Printf("WAYPOINTS FULL\n"); #endif return qfalse; } nodetable[start].flags = 1; return G_BackwardAttachment(desiredIndex, finalDestination, insertAfter); } return qfalse; } #ifdef _DEBUG #define PATH_TIME_DEBUG #endif void G_RMGPathing(void) { //Generate waypoint information on-the-fly for the random mission. float placeX, placeY, placeZ; int i = 0; int gridSpacing = DEFAULT_GRID_SPACING; int nearestIndex = 0; int nearestIndexForNext = 0; #ifdef PATH_TIME_DEBUG int startTime = 0; int endTime = 0; #endif vec3_t downVec, trMins, trMaxs; trace_t tr; gentity_t *terrain = G_Find( NULL, FOFS(classname), "terrain" ); if (!terrain || !terrain->inuse || terrain->s.eType != ET_TERRAIN) { G_Printf("Error: RMG with no terrain!\n"); return; } #ifdef PATH_TIME_DEBUG startTime = trap_Milliseconds(); #endif nodenum = 0; memset(&nodetable, 0, sizeof(nodetable)); VectorSet(trMins, -15, -15, DEFAULT_MINS_2); VectorSet(trMaxs, 15, 15, DEFAULT_MAXS_2); placeX = terrain->r.absmin[0]; placeY = terrain->r.absmin[1]; placeZ = terrain->r.absmax[2]-400; //skim through the entirety of the terrain limits and drop nodes, removing //nodes that start in solid or fall too high on the terrain. while (placeY < terrain->r.absmax[1]) { if (nodenum >= MAX_NODETABLE_SIZE) { break; } while (placeX < terrain->r.absmax[0]) { if (nodenum >= MAX_NODETABLE_SIZE) { break; } nodetable[nodenum].origin[0] = placeX; nodetable[nodenum].origin[1] = placeY; nodetable[nodenum].origin[2] = placeZ; VectorCopy(nodetable[nodenum].origin, downVec); downVec[2] -= 3000; trap_Trace(&tr, nodetable[nodenum].origin, trMins, trMaxs, downVec, ENTITYNUM_NONE, MASK_SOLID); if ((tr.entityNum >= ENTITYNUM_WORLD || g_entities[tr.entityNum].s.eType == ET_TERRAIN) && tr.endpos[2] < terrain->r.absmin[2]+750) { //only drop nodes on terrain directly VectorCopy(tr.endpos, nodetable[nodenum].origin); nodenum++; } else { VectorClear(nodetable[nodenum].origin); } placeX += gridSpacing; } placeX = terrain->r.absmin[0]; placeY += gridSpacing; } #ifdef PAINFULLY_DEBUGGING_THROUGH_VM Com_Printf("NODE GRID PLACED ON TERRAIN\n"); #endif G_NodeClearForNext(); //The grid has been placed down, now use it to connect the points in the level. while (i < gSpawnPointNum-1) { if (!gSpawnPoints[i] || !gSpawnPoints[i]->inuse || !gSpawnPoints[i+1] || !gSpawnPoints[i+1]->inuse) { i++; continue; } nearestIndex = G_NearestNodeToPoint(gSpawnPoints[i]->s.origin); nearestIndexForNext = G_NearestNodeToPoint(gSpawnPoints[i+1]->s.origin); #ifdef PAINFULLY_DEBUGGING_THROUGH_VM Com_Printf("%i GOT %i INDEX WITH %i INDEX FOR NEXT\n", nearestIndex, nearestIndexForNext); #endif if (nearestIndex == -1 || nearestIndexForNext == -1) { //Looks like there is no grid data near one of the points. Ideally, this will never happen. i++; continue; } if (nearestIndex == nearestIndexForNext) { //Two spawn points on top of each other? We don't need to do both points, keep going until the next differs. i++; continue; } //So, nearestIndex is now the node for the spawn point we're on, and nearestIndexForNext is the //node we want to get to from here. //For now I am going to branch out mindlessly, but I will probably want to use some sort of A* algorithm //here to lessen the time taken. if (G_RecursiveConnection(nearestIndex, nearestIndexForNext, 0, qtrue, terrain->r.absmin[2]) != nearestIndexForNext) { //failed to branch to where we want. Oh well, try it without trace checks. G_NodeClearForNext(); #ifdef PAINFULLY_DEBUGGING_THROUGH_VM Com_Printf("FAILED RECURSIVE WITH TRACES\n"); #endif if (G_RecursiveConnection(nearestIndex, nearestIndexForNext, 0, qfalse, terrain->r.absmin[2]) != nearestIndexForNext) { //still failed somehow. Just disregard this point. #ifdef PAINFULLY_DEBUGGING_THROUGH_VM Com_Printf("FAILED RECURSIVE -WITHOUT- TRACES (?!?!)\n"); #endif G_NodeClearForNext(); i++; continue; } } //Now our node array is set up so that highest reasonable weight is the destination node, and 2 is next to the original index, //so trace back to that point. G_NodeClearFlags(); #ifdef ASCII_ART_DEBUG #ifdef ASCII_ART_NODE_DEBUG CreateAsciiNodeTableRepresentation(nearestIndex, nearestIndexForNext); #endif #endif if (G_BackwardAttachment(nearestIndexForNext, nearestIndex, gWPNum-1)) { //successfully connected the trail from nearestIndex to nearestIndexForNext if (gSpawnPoints[i+1]->inuse && gSpawnPoints[i+1]->item && gSpawnPoints[i+1]->item->giType == IT_TEAM) { //This point is actually a CTF flag. if (gSpawnPoints[i+1]->item->giTag == PW_REDFLAG || gSpawnPoints[i+1]->item->giTag == PW_BLUEFLAG) { //Place a waypoint on the flag next in the trail, so the nearest grid point will link to it. CreateNewWP_InsertUnder(gSpawnPoints[i+1]->s.origin, WPFLAG_NEVERONEWAY, gWPNum-1); } } #ifdef PAINFULLY_DEBUGGING_THROUGH_VM Com_Printf("BACKWARD ATTACHMENT %i SUCCESS\n", i); #endif } else { #ifdef PAINFULLY_DEBUGGING_THROUGH_VM Com_Printf("BACKWARD ATTACHMENT FAILED\n"); #endif break; } #ifdef DEBUG_NODE_FILE G_DebugNodeFile(); #endif G_NodeClearForNext(); i++; } #ifdef PAINFULLY_DEBUGGING_THROUGH_VM Com_Printf("FINISHED RMG AUTOPATH\n"); #endif #ifdef PAINFULLY_DEBUGGING_THROUGH_VM Com_Printf("BEGINNING PATH REPAIR...\n"); #endif RepairPaths(qtrue); //this has different behaviour for RMG and will just flag all points one way that don't trace to each other. #ifdef PAINFULLY_DEBUGGING_THROUGH_VM Com_Printf("FINISHED PATH REPAIR.\n"); #endif #ifdef PATH_TIME_DEBUG endTime = trap_Milliseconds(); G_Printf("Total routing time taken: %ims\n", (endTime - startTime)); #endif #ifdef ASCII_ART_DEBUG CreateAsciiTableRepresentation(); #endif } #endif #ifndef _XBOX void BeginAutoPathRoutine(void) { //Called for RMG levels. int i = 0; gentity_t *ent = NULL; vec3_t v; gSpawnPointNum = 0; CreateNewWP(vec3_origin, 0); //create a dummy waypoint to insert under while (i < level.num_entities) { ent = &g_entities[i]; if (ent && ent->inuse && ent->classname && ent->classname[0] && !Q_stricmp(ent->classname, "info_player_deathmatch")) { if (ent->s.origin[2] < 1280) { //h4x gSpawnPoints[gSpawnPointNum] = ent; gSpawnPointNum++; } } else if (ent && ent->inuse && ent->item && ent->item->giType == IT_TEAM && (ent->item->giTag == PW_REDFLAG || ent->item->giTag == PW_BLUEFLAG)) { //also make it path to flags in CTF. gSpawnPoints[gSpawnPointNum] = ent; gSpawnPointNum++; } i++; } if (gSpawnPointNum < 1) { return; } G_RMGPathing(); #ifdef PAINFULLY_DEBUGGING_THROUGH_VM Com_Printf("LINKING PATHS...\n"); #endif //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. trap_Bot_UpdateWaypoints(gWPNum, gWPArray); trap_Bot_CalculatePaths(g_RMG.integer); //CalculatePaths(); //make everything nice and connected #ifdef PAINFULLY_DEBUGGING_THROUGH_VM Com_Printf("FINISHED LINKING PATHS.\n"); #endif #ifdef PAINFULLY_DEBUGGING_THROUGH_VM Com_Printf("FLAGGING OBJECTS...\n"); #endif FlagObjects(); //currently only used for flagging waypoints nearest CTF flags #ifdef PAINFULLY_DEBUGGING_THROUGH_VM Com_Printf("FINISHED FLAGGING OBJECTS.\n"); #endif #ifdef PAINFULLY_DEBUGGING_THROUGH_VM Com_Printf("CALCULATING WAYPOINT DISTANCES...\n"); #endif i = 0; while (i < gWPNum-1) { //disttonext is normally set on save, and when a file is loaded. For RMG we must do it after calc'ing. VectorSubtract(gWPArray[i]->origin, gWPArray[i+1]->origin, v); gWPArray[i]->disttonext = VectorLength(v); i++; } #ifdef PAINFULLY_DEBUGGING_THROUGH_VM Com_Printf("FINISHED CALCULATING.\n"); #endif #ifdef PAINFULLY_DEBUGGING_THROUGH_VM Com_Printf("FINAL STEP...\n"); #endif RemoveWP(); //remove the dummy point at the end of the trail #ifdef PAINFULLY_DEBUGGING_THROUGH_VM Com_Printf("COMPLETE.\n"); #endif #ifdef PAINFULLY_DEBUGGING_THROUGH_VM if (gWPNum >= 4096-1) { Com_Printf("%i waypoints say that YOU ARE A TERRIBLE MAN.\n", gWPNum); } #endif } #endif extern vmCvar_t bot_normgpath; void LoadPath_ThisLevel(void) { vmCvar_t mapname; int i = 0; gentity_t *ent = NULL; trap_Cvar_Register( &mapname, "mapname", "", CVAR_SERVERINFO | CVAR_ROM ); if (g_RMG.integer) { //If RMG, generate the path on-the-fly #ifdef _XBOX assert(0); #else trap_Cvar_Register(&bot_normgpath, "bot_normgpath", "1", CVAR_CHEAT); //note: This is disabled for now as I'm using standard bot nav //on premade terrain levels. if (!bot_normgpath.integer) { //autopath the random map BeginAutoPathRoutine(); } else { //try loading standard nav data LoadPathData(mapname.string); } gLevelFlags |= LEVELFLAG_NOPOINTPREDICTION; #endif } else { if (LoadPathData(mapname.string) == 2) { //enter "edit" mode if cheats enabled? } } trap_Cvar_Update(&bot_wp_edit); if (bot_wp_edit.value) { gBotEdit = 1; } else { gBotEdit = 0; } //set the flag entities while (i < level.num_entities) { ent = &g_entities[i]; if (ent && ent->inuse && ent->classname) { if (!eFlagRed && strcmp(ent->classname, "team_CTF_redflag") == 0) { eFlagRed = ent; } else if (!eFlagBlue && strcmp(ent->classname, "team_CTF_blueflag") == 0) { eFlagBlue = ent; } if (eFlagRed && eFlagBlue) { break; } } i++; } #ifdef PAINFULLY_DEBUGGING_THROUGH_VM Com_Printf("BOT PATHING IS COMPLETE.\n"); #endif } gentity_t *GetClosestSpawn(gentity_t *ent) { gentity_t *spawn; gentity_t *closestSpawn = NULL; float closestDist = -1; int i = MAX_CLIENTS; spawn = NULL; while (i < level.num_entities) { spawn = &g_entities[i]; if (spawn && spawn->inuse && (!Q_stricmp(spawn->classname, "info_player_start") || !Q_stricmp(spawn->classname, "info_player_deathmatch")) ) { float checkDist; vec3_t vSub; VectorSubtract(ent->client->ps.origin, spawn->r.currentOrigin, vSub); checkDist = VectorLength(vSub); if (closestDist == -1 || checkDist < closestDist) { closestSpawn = spawn; closestDist = checkDist; } } i++; } return closestSpawn; } gentity_t *GetNextSpawnInIndex(gentity_t *currentSpawn) { gentity_t *spawn; gentity_t *nextSpawn = NULL; int i = currentSpawn->s.number+1; spawn = NULL; while (i < level.num_entities) { spawn = &g_entities[i]; if (spawn && spawn->inuse && (!Q_stricmp(spawn->classname, "info_player_start") || !Q_stricmp(spawn->classname, "info_player_deathmatch")) ) { nextSpawn = spawn; break; } i++; } if (!nextSpawn) { //loop back around to 0 i = MAX_CLIENTS; while (i < level.num_entities) { spawn = &g_entities[i]; if (spawn && spawn->inuse && (!Q_stricmp(spawn->classname, "info_player_start") || !Q_stricmp(spawn->classname, "info_player_deathmatch")) ) { nextSpawn = spawn; break; } i++; } } return nextSpawn; } int AcceptBotCommand(char *cmd, gentity_t *pl) { int OptionalArgument, i; int FlagsFromArgument; char *OptionalSArgument, *RequiredSArgument; #ifndef _XBOX vmCvar_t mapname; #endif if (!gBotEdit) { return 0; } OptionalArgument = 0; i = 0; FlagsFromArgument = 0; OptionalSArgument = NULL; RequiredSArgument = NULL; //if a waypoint editing related command is issued, bots will deactivate. //once bot_wp_save is issued and the trail is recalculated, bots will activate again. if (!pl || !pl->client) { return 0; } if (Q_stricmp (cmd, "bot_wp_cmdlist") == 0) //lists all the bot waypoint commands. { 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"); 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"); 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"); 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"); G_Printf(S_COLOR_YELLOW "bot_wp_tele" S_COLOR_WHITE " - Teleport yourself to the specified waypoint's location\n"); 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"); G_Printf(S_COLOR_YELLOW "bot_wp_save" S_COLOR_WHITE " - Saves all waypoint data into a file for later use\n"); return 1; } if (Q_stricmp (cmd, "bot_wp_add") == 0) { gDeactivated = 1; OptionalSArgument = ConcatArgs( 1 ); if (OptionalSArgument) { OptionalArgument = atoi(OptionalSArgument); } if (OptionalSArgument && OptionalSArgument[0]) { CreateNewWP_InTrail(pl->client->ps.origin, 0, OptionalArgument); } else { CreateNewWP(pl->client->ps.origin, 0); } return 1; } if (Q_stricmp (cmd, "bot_wp_rem") == 0) { gDeactivated = 1; OptionalSArgument = ConcatArgs( 1 ); if (OptionalSArgument) { OptionalArgument = atoi(OptionalSArgument); } if (OptionalSArgument && OptionalSArgument[0]) { RemoveWP_InTrail(OptionalArgument); } else { RemoveWP(); } return 1; } if (Q_stricmp (cmd, "bot_wp_tele") == 0) { gDeactivated = 1; OptionalSArgument = ConcatArgs( 1 ); if (OptionalSArgument) { OptionalArgument = atoi(OptionalSArgument); } if (OptionalSArgument && OptionalSArgument[0]) { TeleportToWP(pl, OptionalArgument); } else { G_Printf(S_COLOR_YELLOW "You didn't specify an index. Assuming last.\n"); TeleportToWP(pl, gWPNum-1); } return 1; } if (Q_stricmp (cmd, "bot_wp_spawntele") == 0) { gentity_t *closestSpawn = GetClosestSpawn(pl); if (!closestSpawn) { //There should always be a spawn point.. return 1; } closestSpawn = GetNextSpawnInIndex(closestSpawn); if (closestSpawn) { VectorCopy(closestSpawn->r.currentOrigin, pl->client->ps.origin); } return 1; } if (Q_stricmp (cmd, "bot_wp_addflagged") == 0) { gDeactivated = 1; RequiredSArgument = ConcatArgs( 1 ); if (!RequiredSArgument || !RequiredSArgument[0]) { 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"); return 1; } while (RequiredSArgument[i]) { if (RequiredSArgument[i] == 'j') { FlagsFromArgument |= WPFLAG_JUMP; } else if (RequiredSArgument[i] == 'd') { FlagsFromArgument |= WPFLAG_DUCK; } else if (RequiredSArgument[i] == 'c') { FlagsFromArgument |= WPFLAG_SNIPEORCAMPSTAND; } else if (RequiredSArgument[i] == 'f') { FlagsFromArgument |= WPFLAG_WAITFORFUNC; } else if (RequiredSArgument[i] == 's') { FlagsFromArgument |= WPFLAG_SNIPEORCAMP; } else if (RequiredSArgument[i] == 'x') { FlagsFromArgument |= WPFLAG_ONEWAY_FWD; } else if (RequiredSArgument[i] == 'y') { FlagsFromArgument |= WPFLAG_ONEWAY_BACK; } else if (RequiredSArgument[i] == 'g') { FlagsFromArgument |= WPFLAG_GOALPOINT; } else if (RequiredSArgument[i] == 'n') { FlagsFromArgument |= WPFLAG_NOVIS; } else if (RequiredSArgument[i] == 'm') { FlagsFromArgument |= WPFLAG_NOMOVEFUNC; } i++; } OptionalSArgument = ConcatArgs( 2 ); if (OptionalSArgument) { OptionalArgument = atoi(OptionalSArgument); } if (OptionalSArgument && OptionalSArgument[0]) { CreateNewWP_InTrail(pl->client->ps.origin, FlagsFromArgument, OptionalArgument); } else { CreateNewWP(pl->client->ps.origin, FlagsFromArgument); } return 1; } if (Q_stricmp (cmd, "bot_wp_switchflags") == 0) { gDeactivated = 1; RequiredSArgument = ConcatArgs( 1 ); if (!RequiredSArgument || !RequiredSArgument[0]) { 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 \n"); return 1; } while (RequiredSArgument[i]) { if (RequiredSArgument[i] == 'j') { FlagsFromArgument |= WPFLAG_JUMP; } else if (RequiredSArgument[i] == 'd') { FlagsFromArgument |= WPFLAG_DUCK; } else if (RequiredSArgument[i] == 'c') { FlagsFromArgument |= WPFLAG_SNIPEORCAMPSTAND; } else if (RequiredSArgument[i] == 'f') { FlagsFromArgument |= WPFLAG_WAITFORFUNC; } else if (RequiredSArgument[i] == 's') { FlagsFromArgument |= WPFLAG_SNIPEORCAMP; } else if (RequiredSArgument[i] == 'x') { FlagsFromArgument |= WPFLAG_ONEWAY_FWD; } else if (RequiredSArgument[i] == 'y') { FlagsFromArgument |= WPFLAG_ONEWAY_BACK; } else if (RequiredSArgument[i] == 'g') { FlagsFromArgument |= WPFLAG_GOALPOINT; } else if (RequiredSArgument[i] == 'n') { FlagsFromArgument |= WPFLAG_NOVIS; } else if (RequiredSArgument[i] == 'm') { FlagsFromArgument |= WPFLAG_NOMOVEFUNC; } i++; } OptionalSArgument = ConcatArgs( 2 ); if (OptionalSArgument) { OptionalArgument = atoi(OptionalSArgument); } if (OptionalSArgument && OptionalSArgument[0]) { WPFlagsModify(OptionalArgument, FlagsFromArgument); } else { G_Printf(S_COLOR_YELLOW "Waypoint number (to modify) needed for bot_wp_switchflags\nSyntax: bot_wp_switchflags \n"); } return 1; } if (Q_stricmp (cmd, "bot_wp_killoneways") == 0) { i = 0; while (i < gWPNum) { if (gWPArray[i] && gWPArray[i]->inuse) { if (gWPArray[i]->flags & WPFLAG_ONEWAY_FWD) { gWPArray[i]->flags -= WPFLAG_ONEWAY_FWD; } if (gWPArray[i]->flags & WPFLAG_ONEWAY_BACK) { gWPArray[i]->flags -= WPFLAG_ONEWAY_BACK; } } i++; } return 1; } #ifndef _XBOX if (Q_stricmp (cmd, "bot_wp_save") == 0) { gDeactivated = 0; trap_Cvar_Register( &mapname, "mapname", "", CVAR_SERVERINFO | CVAR_ROM ); SavePathData(mapname.string); return 1; } #endif return 0; }