974 lines
28 KiB
C++
974 lines
28 KiB
C++
//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
|
|
|
|
<squadtarget - Entity to use when squadmate gets here> - 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;
|
|
}
|