//g_squad.cpp #include "b_local.h" #include "g_functions.h" #include "g_nav.h" #include "say.h" #include "g_squad.h" #include "g_navigator.h" extern CNavigator navigator; ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// int num_squad_paths; squadPath_t squadPaths[MAX_SQUAD_PATHS]; squadRoute_t squadRoutes[MAX_SQUAD_PATHS]; static float squadPointCheckedDist[MAX_WAYPOINTS_IN_PATH]; static float search_depth; static float search_depth_limit; static qboolean branchFound[MAX_WAYPOINTS_IN_PATH][MAX_WAYPOINTS_IN_PATH]; #define SEARCH_DEPTH_INC 32 #define MAX_SEARCH_DEPTH 4096 extern int NPC_BuildSquadPointDistances( gentity_t *self, const vec3_t center, squadPath_t *squadPath, int keyWp ); /* qboolean NAV_BranchFindsWp(int squadPathNum, int start, int end) Recursive function goes down every branch until reaches end or finds goal waypoint */ qboolean NAV_BranchFindsWp(int squadPathNum, int start, int end) { squadPath_t *path = &squadPaths[squadPathNum]; float init_search_depth; int i; init_search_depth = search_depth; squadPointCheckedDist[start] = search_depth; for(i = 0; i < MAX_PATH_BRANCHES; i++) { search_depth = init_search_depth; if(path->waypoints[start].nextWp[i] == -1) {//no nextWp continue; } search_depth += path->waypoints[start].nextWpDist[i]; if(search_depth > search_depth_limit) {//too far for this check continue; } if(squadPointCheckedDist[path->waypoints[start].nextWp[i]] < search_depth) {//Already tried to go through this one at a shorter depth continue; } if(path->waypoints[start].nextWp[i] == end) {//a neighbor return qtrue; } else if( NAV_BranchFindsWp(squadPathNum, path->waypoints[start].nextWp[i], end)) {//branch eventually gets there return qtrue; } } //no branches off here can get there search_depth = init_search_depth; return qfalse; } /* int NAV_NextSquadPoint(int squadPathNum, int start, int end) FIXME: doesn't find shortest route, just the first */ int NAV_NextSquadPoint(int squadPathNum, int start, int end) { squadPath_t *path = &squadPaths[squadPathNum]; float init_search_depth; int i; for(i = 0; i < MAX_WAYPOINTS_IN_PATH; i++) { squadPointCheckedDist[i] = Q3_INFINITE; } init_search_depth = search_depth = 0; squadPointCheckedDist[start] = search_depth;//0, don't backtrack through here at any time for(i = 0; i < MAX_PATH_BRANCHES; i++) { search_depth = init_search_depth; if(path->waypoints[start].nextWp[i] == -1) {//no nextWp continue; } search_depth += path->waypoints[start].nextWpDist[i]; if(search_depth > search_depth_limit) {//too far for this check continue; } if(squadPointCheckedDist[path->waypoints[start].nextWp[i]] < search_depth) {//Already tried to go through this one continue; } if(path->waypoints[start].nextWp[i] == end) {//a neighbor return path->waypoints[start].nextWp[i]; } else if( NAV_BranchFindsWp(squadPathNum, path->waypoints[start].nextWp[i], end)) { return path->waypoints[start].nextWp[i]; } } //no branches off here can get there, failed search_depth = Q3_INFINITE; return -1; } //FIXME: calc once and save out in nav file, would speed up map load times and savegame load times void NAV_GenerateSquadRoutes (int squadPathNum) { squadPath_t *path = &squadPaths[squadPathNum]; squadRoute_t *route = &squadRoutes[squadPathNum]; int start, end; for(start = 0; start < path->numWaypoints; start++) { for(end = 0; end < path->numWaypoints; end++) { route->nextSquadPoint[start][end] = -1; route->cost[start][end] = Q3_INFINITE; if(end == start) { continue; } search_depth_limit = 0; while(route->nextSquadPoint[start][end] == -1 && search_depth_limit < MAX_SEARCH_DEPTH) {//Keep looking until we find a route or hit the max dearch depth search_depth_limit += SEARCH_DEPTH_INC; route->nextSquadPoint[start][end] = NAV_NextSquadPoint(squadPathNum, start, end); route->cost[start][end] = search_depth; } } } } void NAV_insertWaypointInPath (squadPathWaypoint_t *squadPathWp, gentity_t *currWp) { VectorCopy(currWp->currentOrigin, squadPathWp->origin); squadPathWp->leadDist = currWp->speed; /* if(currWp->paintarget && currWp->paintarget[0]) { squadPathWp->target = G_NewString( currWp->paintarget ); } else { squadPathWp->target = NULL; } */ } /* void NAV_LinkSquadPointNeighbors(int squadPathNum, int wp1, int wp2, char *wpName) Links 2 squadpoints as their next free neighbors, avoids doing so if they were already connected */ void NAV_LinkSquadPointNeighbors(int squadPathNum, int wp1, int wp2, char *wpName) { int k; if(!branchFound[wp1][wp2]) { for(k = 0; k < MAX_PATH_BRANCHES; k++) { if(squadPaths[num_squad_paths].waypoints[wp1].nextWp[k] == -1) { squadPaths[num_squad_paths].waypoints[wp1].nextWp[k] = wp2; if ( k > 1 ) {//This one has three paths in and out, it branches squadPaths[num_squad_paths].waypoints[wp1].flags |= SPF_BRANCH; } break; } if( k == MAX_PATH_BRANCHES) { G_Error("ERROR: Squad Path %s has exceeded max number of branches on waypoint (%s)\n", squadPaths[num_squad_paths].ownername, wpName); return; } } branchFound[wp1][wp2] = qtrue; } if(!branchFound[wp2][wp1]) { for(k = 0; k < MAX_PATH_BRANCHES; k++) { if(squadPaths[num_squad_paths].waypoints[wp2].nextWp[k] == -1) { squadPaths[num_squad_paths].waypoints[wp2].nextWp[k] = wp1; if ( k > 1 ) {//This one has three paths in and out, it branches squadPaths[num_squad_paths].waypoints[wp2].flags |= SPF_BRANCH; } break; } if( k == MAX_PATH_BRANCHES) { G_Error("ERROR: Squad Path %s has exceeded max number of branches on waypoint (%s)\n", squadPaths[num_squad_paths].ownername, wpName); return; } } branchFound[wp2][wp1] = qtrue; } } /* void NAV_GenerateSquadPaths (void) FIXME: make "waypoints" a pointer in squadPath struct use a static local waypoint array struct in this func to find all waypoints in a path When have count, G_Alloc a waypoint array of the needed size and point to it from the squadPath FIXME: even if has 1 branch, will find it 4 times */ #define USE_PRECALCED_SQD_FILES #ifdef USE_PRECALCED_SQD_FILES #define SQD_VERSION_NUMBER 2 extern int giMapChecksum; typedef struct { // verification fields... // int iVersionNumber; int iMAP_CRC; int iSizeof_squadPaths; int iSizeof_squadRoutes; // // data fields... // int iNumSquadPaths; // squadPath_t squadPaths[MAX_SQUAD_PATHS]; // no longer declared, since only NZ entries saved out // squadRoute_t squadRoutes[MAX_SQUAD_PATHS]; } SQDHEADER; #endif void NAV_GenerateSquadPaths (void) { gentity_t *currWp = NULL; #ifdef USE_PRECALCED_SQD_FILES char sFilenameSQD[MAX_QPATH]; qboolean qbUseSQDFile = qfalse; // first, we can do a quick check as to whether or not there are any squad paths without having to load a file // and read out a zero entry... // qboolean qbAtLeastOneSquadPathExists = qfalse; currWp = NULL; while(NULL != (currWp = G_Find(currWp, FOFS(classname), "waypoint_squadpath")) ) { qbAtLeastOneSquadPathExists = qtrue; break; } if (qbAtLeastOneSquadPathExists) { // so do we have a pre-calc'd file of squad data?... // Q_strncpyz(sFilenameSQD,va("maps/%s.sqd",level.mapname),sizeof(sFilenameSQD)); byte *pbData = NULL; /*int iSize = */gi.FS_ReadFile( sFilenameSQD, (void **)&pbData); if (pbData) { SQDHEADER* pSquadHeader = (SQDHEADER*) pbData; // check the header is valid... // if (pSquadHeader->iVersionNumber == SQD_VERSION_NUMBER && pSquadHeader->iMAP_CRC == giMapChecksum && pSquadHeader->iSizeof_squadPaths == sizeof(squadPaths) && pSquadHeader->iSizeof_squadRoutes == sizeof(squadRoutes) ) { // data looks valid, let's use it... // qbUseSQDFile = qtrue; // tell rest of function not to re-calc num_squad_paths = pSquadHeader->iNumSquadPaths; squadPath_t* pLoadedSquadPathData = (squadPath_t*) &pSquadHeader[1]; squadRoute_t* pLoadedSquadRouteData = (squadRoute_t*) &pLoadedSquadPathData[num_squad_paths]; memcpy(squadPaths, pLoadedSquadPathData, num_squad_paths * sizeof(squadPath_t)); memcpy(squadRoutes,pLoadedSquadRouteData, num_squad_paths * sizeof(squadRoute_t)); gi.Printf( "%d squadPaths loaded\n", num_squad_paths ); } gi.FS_FreeFile(pbData); } } if (!qbUseSQDFile) #endif // #ifdef USE_PRECALCED_SQD_FILES { gentity_t *nextWp, *targWp, *branchWp; vec3_t vec; int i, j, k; int numWaypoints; int branches[1024]; int branchNextWp[1024]; int branchWpIndex[1024]; int num_branches; int lastWpIndex; num_squad_paths = 0; currWp = NULL; while(0!=(currWp = G_Find(currWp, FOFS(classname), "waypoint_squadpath"))) { if(!currWp->ownername || !currWp->ownername[0]) {//Only start on master ones continue; } for(i = 0; i < num_squad_paths; i++) { if(Q_stricmp(squadPaths[i].ownername, currWp->ownername) == 0) { gi.Printf("Warning, more than one squad path has ownername of %s\n", currWp->ownername); continue; } } if(num_squad_paths >= MAX_SQUAD_PATHS) { G_Error("Error: Too many squad paths!\n"); return; } //Ok, add a new path for(i = 0; i < 1024; i++) { branches[i] = -1; branchNextWp[i] = -1; branchWpIndex[i] = -1; } for(i = 0; i < MAX_WAYPOINTS_IN_PATH; i++) { for(j = 0; j < MAX_WAYPOINTS_IN_PATH; j++) { branchFound[i][j] = qfalse; } } Q_strncpyz(squadPaths[num_squad_paths].ownername,currWp->ownername,sizeof(squadPaths[0].ownername)); NAV_insertWaypointInPath(&squadPaths[num_squad_paths].waypoints[0], currWp); currWp->wpIndex = 1; nextWp = currWp; numWaypoints = 0; num_branches = 0; targWp = G_Find(NULL, FOFS(targetname), currWp->target);//Find the first one currWp->target = NULL; lastWpIndex = numWaypoints; while(targWp) { //Check for branches branchWp = NULL; if(NULL != (branchWp = G_Find(NULL, FOFS(targetname), nextWp->target2))) { nextWp->target2 = NULL; } else if(NULL != (branchWp = G_Find(NULL, FOFS(targetname), nextWp->target3)) ) { nextWp->target3 = NULL; } else if(NULL != (branchWp = G_Find(NULL, FOFS(targetname), nextWp->target4)) ) { nextWp->target4 = NULL; } if(branchWp) {//This waypoint branches, we'll revisit it when we're done branches[num_branches] = nextWp->s.number; branchNextWp[num_branches] = branchWp->s.number; branchWpIndex[num_branches] = lastWpIndex; num_branches++; squadPaths[num_squad_paths].waypoints[lastWpIndex].flags |= SPF_BRANCH; } //Now see what we have here if(targWp->wpIndex > 0) {//This waypoint has already been linked in, don't loop //Tell the last wp to link to a previous one NAV_LinkSquadPointNeighbors(num_squad_paths, lastWpIndex, targWp->wpIndex - 1, nextWp->targetname); //NAV_LinkSquadPointNeighbors(num_squad_paths, lastWpIndex, targWp->wpIndex, nextWp->targetname); targWp = NULL;//End of branch } else { numWaypoints++; if(numWaypoints >= MAX_WAYPOINTS_IN_PATH) { G_Error("ERROR: Squad Path %s has exceeded max number of waypoints (%d)\n", squadPaths[num_squad_paths].ownername, MAX_WAYPOINTS_IN_PATH); return; } //copy into waypoint data NAV_insertWaypointInPath(&squadPaths[num_squad_paths].waypoints[numWaypoints], targWp); //Set nextWp's first empty branchWp to targWp, and vice-versa NAV_LinkSquadPointNeighbors(num_squad_paths, lastWpIndex, numWaypoints, nextWp->targetname); targWp->wpIndex = numWaypoints + 1;//+1 so we can test for nonzero below lastWpIndex = numWaypoints; nextWp = targWp; if(nextWp->target) { targWp = G_Find(NULL, FOFS(targetname), nextWp->target); nextWp->target = NULL; } else { targWp = NULL; } } } squadPaths[num_squad_paths].waypoints[numWaypoints].flags |= SPF_END_OF_BRANCH; //Ok, now go back and handle every unprocessed branch while(num_branches) { for(i = 0; i < 1024; i++) { if(branches[i] != -1) { nextWp = &g_entities[branches[i]]; targWp = &g_entities[branchNextWp[i]]; lastWpIndex = branchWpIndex[i]; while(targWp) {//Fill in this entire branch //Look for branches off of it branchWp = NULL; if(NULL != (branchWp = G_Find(NULL, FOFS(targetname), nextWp->target2)) ) { if(branchWp == targWp) {//We're processing this branch right now (Shouldn't happen) branchWp = NULL; } else { nextWp->target2 = NULL; } } if(branchWp == NULL) { if(NULL != (branchWp = G_Find(NULL, FOFS(targetname), nextWp->target3)) ) { if(branchWp == targWp) {//We're processing this branch right now (Shouldn't happen) branchWp = NULL; } else { nextWp->target3 = NULL; } } } if(branchWp == NULL) { if(NULL != (branchWp = G_Find(NULL, FOFS(targetname), nextWp->target4)) ) { if(branchWp == targWp) {//We're processing this branch right now (Shouldn't happen) branchWp = NULL; } else { nextWp->target4 = NULL; } } } if(branchWp) {//This waypoint branches, we'll revisit it when we're done for(j = 0; j < 1024; j++) {//Look for an empty slot if(branches[j] == -1) {//fill in the new branch info and we'll get back to it later branches[j] = nextWp->s.number; branchNextWp[j] = branchWp->s.number; branchWpIndex[j] = lastWpIndex; break; } } num_branches++; squadPaths[num_squad_paths].waypoints[lastWpIndex].flags |= SPF_BRANCH; } //Ok, let's check out this waypoint if(targWp->wpIndex > 0) {//This waypoint has already been linked in, don't loop //Tell the last wp to link to a previous one NAV_LinkSquadPointNeighbors(num_squad_paths, lastWpIndex, targWp->wpIndex - 1, nextWp->targetname); //NAV_LinkSquadPointNeighbors(num_squad_paths, lastWpIndex, targWp->wpIndex, nextWp->targetname); targWp = NULL;//End of branch } else { numWaypoints++; if(numWaypoints >= MAX_WAYPOINTS_IN_PATH) { G_Error("ERROR: Squad Path %s has exceeded max number of waypoints (%d)\n", squadPaths[num_squad_paths].ownername, MAX_WAYPOINTS_IN_PATH); return; } NAV_insertWaypointInPath(&squadPaths[num_squad_paths].waypoints[numWaypoints], targWp); //Set it is the next empty branch off last wp, & vice-versa NAV_LinkSquadPointNeighbors(num_squad_paths, lastWpIndex, numWaypoints, nextWp->targetname); targWp->wpIndex = numWaypoints + 1;//+1 so we can test for nonzero below lastWpIndex = numWaypoints; nextWp = targWp; if(nextWp->target) { targWp = G_Find(NULL, FOFS(targetname), nextWp->target); nextWp->target = NULL; } else { targWp = NULL; } } } squadPaths[num_squad_paths].waypoints[numWaypoints].flags |= SPF_END_OF_BRANCH; branches[i] = -1; branchNextWp[i] = -1; branchWpIndex[i] = -1; num_branches--; } } } squadPaths[num_squad_paths].numWaypoints = numWaypoints + 1; squadPaths[num_squad_paths].waypoints[numWaypoints].flags |= SPF_END_OF_LIST; num_squad_paths++; } //Precalc distances between them all, then calc the routes for(i = 0; i < num_squad_paths; i++) { for(j = 0; j < squadPaths[i].numWaypoints; j++) { for(k = 0; k < MAX_PATH_BRANCHES; k++) { if(squadPaths[i].waypoints[j].nextWp[k] != -1) { VectorSubtract(squadPaths[i].waypoints[squadPaths[i].waypoints[j].nextWp[k]].origin, squadPaths[i].waypoints[j].origin, vec); squadPaths[i].waypoints[j].nextWpDist[k] = VectorLength(vec); } } } NAV_GenerateSquadRoutes (i); } #ifdef USE_PRECALCED_SQD_FILES if (num_squad_paths) { // // now save out this data so we can just reload it next time this map is run rather than recalculating it all... // fileHandle_t file; gi.FS_FOpenFile( sFilenameSQD, &file, FS_WRITE ); if ( file ) { int iOut; // ensure all fields are written as ints, regardless of original sizes // verification fields... iOut = SQD_VERSION_NUMBER; gi.FS_Write( &iOut, sizeof(iOut), file ); iOut = giMapChecksum; gi.FS_Write( &iOut, sizeof(iOut), file ); iOut = sizeof(squadPaths); gi.FS_Write( &iOut, sizeof(iOut), file ); iOut = sizeof(squadRoutes); gi.FS_Write( &iOut, sizeof(iOut), file ); // data fields... iOut = num_squad_paths; gi.FS_Write( &iOut, sizeof(iOut), file ); gi.FS_Write( &squadPaths, num_squad_paths * sizeof(squadPath_t), file ); gi.FS_Write( &squadRoutes,num_squad_paths * sizeof(squadRoute_t),file ); gi.FS_FCloseFile( file ); } } #endif gi.Printf( "%d squadPaths generated\n", num_squad_paths ); } //All paths made, now free all waypoint_squadpaths currWp = NULL; while(NULL != (currWp = G_Find(currWp, FOFS(classname), "waypoint_squadpath")) ) { currWp->e_ThinkFunc = thinkF_G_FreeEntity; currWp->nextthink = level.time + FRAMETIME; } } void G_SquadPathsInit (void) { int h, i, j; num_squad_paths = 0; for(h = 0; h < MAX_SQUAD_PATHS; h++) { squadPaths[h].numWaypoints = 0; for(i = 0; i < MAX_WAYPOINTS_IN_PATH; i ++) { for(j = 0; j < MAX_PATH_BRANCHES; j++) { squadPaths[h].waypoints[i].nextWp[j] = -1; } } } } /*QUAKED waypoint_squadpath (0.3 0.7 1) (-12 -12 -24) (12 12 32) A waypoint for squad paths not navigation target/targetname - Link these in a path starting with the first waypoint branch using target2, target3 and target4 IMPORTANT! don't use target2 without having a first target, and so on... ownername - bot_targetname of bot that should use this waypoint when following player WARNING: Only the start waypoint on this path should have the ownername field! leadDist - How far ahead/behind the NPC should be when the player is close to this squadpoint - being removed FIXME: these points are easy to miss because of collision avoidance, so squadtargets are probably not the best way to do this, use trigger_formation instead Future - be able to set a script or bstate for a bot to use when get here?! */ extern unsigned int waypoint_getRadius( gentity_t *ent ); void SP_waypoint_squadpath (gentity_t *ent) { VectorSet(ent->mins, DEFAULT_MINS_0, DEFAULT_MINS_1, DEFAULT_MINS_2); VectorSet(ent->maxs, DEFAULT_MAXS_0, DEFAULT_MAXS_1, DEFAULT_MAXS_2); ent->contents = CONTENTS_TRIGGER; ent->clipmask = MASK_DEADSOLID; gi.linkentity( ent ); if(!ent->target || !ent->target[0]) { if(!ent->target2 || !ent->target2[0]) { if(!ent->target3 || !ent->target3[0]) { if(!ent->target4 || !ent->target4[0]) { //Hmm... dead end } else { G_Error("ERROR: waypoint_squadpath %s has a target4 but not target, target2 or target3!!!\n", ent->targetname); G_FreeEntity(ent); return; } } else { G_Error("ERROR: waypoint_squadpath %s has a target3 but not target or target2!!!\n", ent->targetname); G_FreeEntity(ent); return; } } else { G_Error("ERROR: waypoint_squadpath %s has a target2 but not target!!!\n", ent->targetname); G_FreeEntity(ent); return; } } if(G_CheckInSolid (ent, qtrue)) { ent->maxs[2] = CROUCH_MAXS_2; if(G_CheckInSolid (ent, qtrue)) { gi.Printf(S_COLOR_RED"ERROR: waypoint_squadpath %s at %s in solid!\n", ent->targetname, vtos(ent->currentOrigin)); assert(0 && "ERROR: Waypoint_squadpath in solid!"); #ifndef FINAL_BUILD G_Error("Waypoint_squadpath %s at %s in solid!\n", ent->targetname, vtos(ent->currentOrigin)); #endif //G_FreeEntity(ent); return; } } } //CONSOLE COMMANDS=========================================================================== void NPC_SetSayState (gentity_t *self, gentity_t *to, int saying) { self->NPC->tempBehavior = BS_SAY; self->NPC->sayTarg = to; self->NPC->sayString = (saying_t)saying; } void NPC_RecieveCommand(gentity_t *commander, gentity_t *self, int command, const char *extra) { gentity_t *commTarg = NULL; gentity_t *next; if(extra && extra[0]) {//Find target of command, if we can //next = Munro;//Munro is a global pointer to the player? next = &g_entities[0]; while(next->client->follower)//This is the linked list of your followers... { next = next->client->follower; if(Q_stricmp(extra, next->script_targetname) == 0) {//Found the person we're hailing commTarg = next; break; } } } switch(command) { case CMD_REGROUP: //Get back into normal BS_FORMATION bState self->NPC->behaviorState = BS_FORMATION; //Set a position? Only if don't have one? How would we choose? //Should we have a func that finds which unfilled position you're closest to??? break; case CMD_GUARD: //Go into BS_STAND_GUARD bState self->NPC->behaviorState = BS_STAND_GUARD; break; case CMD_RETREAT: //Find farthest path point from enemy that is in PVS of leader and head there (BS_FORMATION) //set FPOS to FPOS_NONE??? How do we know when to regroup? Wait for manual command? if(self->enemy && self->NPC->iSquadPathIndex!=-1) { NPC_BuildSquadPointDistances( self, self->enemy->currentOrigin, &squadPaths[self->NPC->iSquadPathIndex], WAYPOINT_NONE ); } else {//If no squadpath, should we find some other way to retreat? goto cannot_comply; } break; case CMD_COVER: if(commTarg == NULL) { goto cannot_comply; } //self->NPC->behaviorState = BS_COVER; self->NPC->coverTarg = commTarg; break; case CMD_ESCORT: if(commTarg == NULL) { goto cannot_comply; } //self->NPC->behaviorState = BS_COVER; self->NPC->coverTarg = commTarg; break; case CMD_UNKNOWN: //TempBState- turn to player and say "copy that again, sir?" or "I didn't quite get that" or "what?" or "I think I got some interference, can you repeat that?" NPC_SetSayState(self, commander, Q_irand(SAY_BADCOMM1, SAY_BADCOMM4)); return; break; } //Say "copy" or "yes sir" or "okay" or "gotcha" or "right" NPC_SetSayState(self, commander, Q_irand(SAY_ACKCOMM1, SAY_ACKCOMM4)); return; cannot_comply: //say "can't do that, sir!" or "no way" or something NPC_SetSayState(self, commander, Q_irand(SAY_REFCOMM1, SAY_REFCOMM4)); } void Svcmd_Comm_f( void ) { char *cmdName; int command; gentity_t *next; cmdName = gi.argv(1); if(!cmdName || !cmdName[0]) {//FIXME: error message return; } //Commands will be issued to anyone who was hailed and is awaiting orders //While waiting, comm will take the first arg and translate it as a command, some // are whole commands, some require a third command, such as: // "regroup" // "stand guard" // "retreat" // "take" <"cover" or position (scout, point, flank, backup, rear)> // "cover" <"me" or NPC name> // "escort" <"me" or NPC name> //NPC will acknowledge the command and change state accordingly. //After 3 seconds or upon receipt of a command, it will clear the waiting flags // of each NPC for the next command. if(Q_stricmp("regroup", cmdName) == 0) { command = (int)CMD_REGROUP; } else if(Q_stricmp("stand guard", cmdName) == 0) { command = (int)CMD_GUARD; } else if(Q_stricmp("retreat", cmdName) == 0) { command = (int)CMD_RETREAT; } else if(Q_stricmp("cover", cmdName) == 0) { command = (int)CMD_COVER; } else if(Q_stricmp("escort", cmdName) == 0) { command = (int)CMD_ESCORT; } else { command = (int)CMD_UNKNOWN; } //next = Munro;//Munro is a global pointer to the player? next = &g_entities[0]; while(next->client && next->client->follower)//This is the linked list of your followers... { next = next->client->follower; if(next->NPC->aiFlags & NPCAI_AWAITING_COMM)//This gets cleared by a 3 sec timer on them in thier think { next->NPC->aiFlags &= ~NPCAI_AWAITING_COMM; NPC_RecieveCommand(&g_entities[0], next, command, gi.argv(2)); } } } /* void NPC_HailSquadMate (gentity_t *squadMate) FIXME: they should know who the hail came form so they know who to respond to if it's unfinished FIXME: Should we queue up multiple hails? Or only respond to last one? */ void NPC_HailSquadMate (gentity_t *self, gentity_t *squadMate) { if(squadMate->NPC->aiFlags & NPCAI_AWAITING_COMM) {//Already hailed them //FIXME: Make sure you were last one to hail them before doing this? NPC_SetSayState(squadMate, self, Q_irand(SAY_BADHAIL1, SAY_BADHAIL4)); squadMate->NPC->commWaitTime = level.time + COMM_WAIT_TIME; } else { squadMate->NPC->aiFlags |= NPCAI_AWAITING_COMM; squadMate->NPC->commWaitTime = level.time + COMM_WAIT_TIME; } } void Svcmd_Hail_f( void ) { char *targName; gentity_t *next; targName = gi.argv(1); //Comm will take a first arg of the NPC to talk to (or "all" for entire squad), // will init them to wait 3 seconds for a command. After 3 seconds, you have // to hail them again. // If no command comes in 1 second, they'll say "sir?" or "yeah?" or "what?" if(!targName || !targName[0]) {//FIXME: error message return; } if(Q_stricmp("all", targName) == 0 || Q_stricmp("everyone", targName) == 0 || Q_stricmp("squad", targName) == 0) {//Hail everyone in your squad //next = Munro;//Munro is a global pointer to the player? next = &g_entities[0]; while(next->client && next->client->follower)//This is the linked list of your followers... { next = next->client->follower; NPC_HailSquadMate(&g_entities[0], next); } } else { //next = Munro;//Munro is a global pointer to the player? next = &g_entities[0]; while(next->client && next->client->follower)//This is the linked list of your followers... { next = next->client->follower; if(Q_stricmp(targName, next->script_targetname) == 0) {//Found the person we're hailing NPC_HailSquadMate(&g_entities[0], next); break; } } } } void G_CreateFormation (gentity_t *self); void Svcmd_Form_f( void ) { G_CreateFormation(&g_entities[0]); } //===Spawn func========================================================================== /*QUAKED target_create_formation (0 0.5 0) (-4 -4 -4) (4 4 4) player fires this off and will make a formation (one time only) */ void target_create_formation_use( gentity_t *self, gentity_t *other, gentity_t *activator ) { if (self->behaviorSet[BSET_USE]) { G_ActivateBehavior(self,BSET_USE); } G_CreateFormation(&g_entities[0]); G_FreeEntity(self); } void SP_target_create_formation (gentity_t *self) { self->e_UseFunc = useF_target_create_formation_use; }