/* =========================================================================== Return to Castle Wolfenstein single player GPL Source Code Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. This file is part of the Return to Castle Wolfenstein single player GPL Source Code (“RTCW SP Source Code”). RTCW SP Source Code is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. RTCW SP Source Code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with RTCW SP Source Code. If not, see . In addition, the RTCW SP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW SP Source Code. If not, please request a copy in writing from id Software at the address below. If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. =========================================================================== */ //=========================================================================== // // Name: be_aas_routetable.c // Function: Area Awareness System, Route-table defines // Programmer: Ridah // Tab Size: 3 //=========================================================================== #include "../game/q_shared.h" #include "l_memory.h" #include "l_script.h" #include "l_precomp.h" #include "l_struct.h" #include "l_libvar.h" #include "l_utils.h" #include "aasfile.h" #include "../game/botlib.h" #include "../game/be_aas.h" #include "be_interface.h" #include "be_aas_def.h" // ugly hack to turn off route-tables, can't find a way to check cvar's int disable_routetable = 0; // this must be enabled for the route-tables to work, but it's not fully operational yet #define CHECK_TRAVEL_TIMES //#define DEBUG_ROUTETABLE #define FILTERAREAS // enable this to use the built-in route-cache system to find the routes #define USE_ROUTECACHE // enable this to disable Rocket/BFG Jumping, Grapple Hook #define FILTER_TRAVEL // hmm, is there a cleaner way of finding out memory usage? extern int totalmemorysize; static int memorycount, cachememory; // globals to reduce function parameters static unsigned short int *filtered_areas, childcount, num_parents; static unsigned short int *rev_filtered_areas; // misc defines unsigned short CRC_ProcessString( unsigned char *data, int length ); //=========================================================================== // Memory debugging/optimization void *AAS_RT_GetClearedMemory( unsigned long size ) { void *ptr; memorycount += size; // ptr = GetClearedMemory(size); //ptr = GetClearedHunkMemory(size); // Ryan - 01102k, need to use this, since the routetable calculations use up a lot of memory // this will be a non-issue once we transfer the remnants of the routetable over to the aasworld ptr = malloc( size ); memset( ptr, 0, size ); return ptr; } void AAS_RT_FreeMemory( void *ptr ) { int before; before = totalmemorysize; // FreeMemory( ptr ); // Ryan - 01102k free( ptr ); memorycount -= before - totalmemorysize; } void AAS_RT_PrintMemoryUsage() { #ifdef AAS_RT_MEMORY_USAGE botimport.Print( PRT_MESSAGE, "\n" ); // TODO: print the usage from each of the aas_rt_t lumps #endif } //=========================================================================== //=========================================================================== // return the number of unassigned areas that are in the given area's visible list // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_RT_GetValidVisibleAreasCount( aas_area_buildlocalinfo_t *localinfo, aas_area_childlocaldata_t **childlocaldata ) { int i, cnt; cnt = 1; // assume it can reach itself for ( i = 0; i < localinfo->numvisible; i++ ) { if ( childlocaldata[localinfo->visible[i]] ) { continue; } cnt++; } return cnt; } //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== static aas_rt_route_t **routetable; int AAS_AreaRouteToGoalArea( int areanum, vec3_t origin, int goalareanum, int travelflags, int *traveltime, int *reachnum ); //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_RT_CalcTravelTimesToGoalArea( int goalarea ) { int i; // TTimo: unused // static int tfl = TFL_DEFAULT & ~(TFL_JUMPPAD|TFL_ROCKETJUMP|TFL_BFGJUMP|TFL_GRAPPLEHOOK|TFL_DOUBLEJUMP|TFL_RAMPJUMP|TFL_STRAFEJUMP|TFL_LAVA); //----(SA) modified since slime is no longer deadly // static int tfl = TFL_DEFAULT & ~(TFL_JUMPPAD|TFL_ROCKETJUMP|TFL_BFGJUMP|TFL_GRAPPLEHOOK|TFL_DOUBLEJUMP|TFL_RAMPJUMP|TFL_STRAFEJUMP|TFL_SLIME|TFL_LAVA); aas_rt_route_t *rt; int reach, travel; for ( i = 0; i < childcount; i++ ) { rt = &routetable[i][-1 + rev_filtered_areas[goalarea]]; if ( AAS_AreaRouteToGoalArea( filtered_areas[i], ( *aasworld ).areas[filtered_areas[i]].center, goalarea, ~RTB_BADTRAVELFLAGS, &travel, &reach ) ) { rt->reachable_index = reach; rt->travel_time = travel; } else { rt->reachable_index = -1; rt->travel_time = 0; } } } //=========================================================================== // calculate the initial route-table for each filtered area to all other areas // // FIXME: this isn't fully operational yet, for some reason not all routes are found // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_RT_CalculateRouteTable( aas_rt_route_t **parmroutetable ) { int i; routetable = parmroutetable; for ( i = 0; i < childcount; i++ ) { AAS_RT_CalcTravelTimesToGoalArea( filtered_areas[i] ); } } //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_RT_AddParentLink( aas_area_childlocaldata_t *child, int parentindex, int childindex ) { aas_parent_link_t *oldparentlink; oldparentlink = child->parentlink; child->parentlink = (aas_parent_link_t *) AAS_RT_GetClearedMemory( sizeof( aas_parent_link_t ) ); child->parentlink->childindex = (unsigned short int)childindex; child->parentlink->parent = (unsigned short int)parentindex; child->parentlink->next = oldparentlink; } //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_RT_WriteShort( unsigned short int si, fileHandle_t fp ) { unsigned short int lsi; lsi = LittleShort( si ); botimport.FS_Write( &lsi, sizeof( lsi ), fp ); } //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_RT_WriteByte( int si, fileHandle_t fp ) { unsigned char uc; uc = si; botimport.FS_Write( &uc, sizeof( uc ), fp ); } //=========================================================================== // writes the current route-table data to a .rtb file in tne maps folder // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_RT_WriteRouteTable() { int ident, version; unsigned short crc_aas; fileHandle_t fp; char filename[MAX_QPATH]; // open the file for writing Com_sprintf( filename, MAX_QPATH, "maps/%s.rtb", ( *aasworld ).mapname ); botimport.Print( PRT_MESSAGE, "\nsaving route-table to %s\n", filename ); botimport.FS_FOpenFile( filename, &fp, FS_WRITE ); if ( !fp ) { AAS_Error( "Unable to open file: %s\n", filename ); return; } // ident ident = LittleLong( RTBID ); botimport.FS_Write( &ident, sizeof( ident ), fp ); // version version = LittleLong( RTBVERSION ); botimport.FS_Write( &version, sizeof( version ), fp ); // crc crc_aas = CRC_ProcessString( (unsigned char *)( *aasworld ).areas, sizeof( aas_area_t ) * ( *aasworld ).numareas ); botimport.FS_Write( &crc_aas, sizeof( crc_aas ), fp ); // save the table data // children botimport.FS_Write( &( *aasworld ).routetable->numChildren, sizeof( int ), fp ); botimport.FS_Write( ( *aasworld ).routetable->children, ( *aasworld ).routetable->numChildren * sizeof( aas_rt_child_t ), fp ); // parents botimport.FS_Write( &( *aasworld ).routetable->numParents, sizeof( int ), fp ); botimport.FS_Write( ( *aasworld ).routetable->parents, ( *aasworld ).routetable->numParents * sizeof( aas_rt_parent_t ), fp ); // parentChildren botimport.FS_Write( &( *aasworld ).routetable->numParentChildren, sizeof( int ), fp ); botimport.FS_Write( ( *aasworld ).routetable->parentChildren, ( *aasworld ).routetable->numParentChildren * sizeof( unsigned short int ), fp ); // visibleParents botimport.FS_Write( &( *aasworld ).routetable->numVisibleParents, sizeof( int ), fp ); botimport.FS_Write( ( *aasworld ).routetable->visibleParents, ( *aasworld ).routetable->numVisibleParents * sizeof( unsigned short int ), fp ); // parentLinks botimport.FS_Write( &( *aasworld ).routetable->numParentLinks, sizeof( int ), fp ); botimport.FS_Write( ( *aasworld ).routetable->parentLinks, ( *aasworld ).routetable->numParentLinks * sizeof( aas_rt_parent_link_t ), fp ); botimport.FS_FCloseFile( fp ); return; } //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_RT_DBG_Read( void *buf, int size, int fp ) { botimport.FS_Read( buf, size, fp ); } //=========================================================================== // reads the given file, and creates the structures required for the route-table system // // Parameter: - // Returns: qtrue if succesful, qfalse if not // Changes Globals: - //=========================================================================== #define DEBUG_READING_TIME qboolean AAS_RT_ReadRouteTable( fileHandle_t fp ) { int ident, version, i; unsigned short int crc, crc_aas; aas_rt_t *routetable; aas_rt_child_t *child; aas_rt_parent_t *parent; aas_rt_parent_link_t *plink; unsigned short int *psi; qboolean doswap; #ifdef DEBUG_READING_TIME int pretime; pretime = Sys_MilliSeconds(); #endif routetable = ( *aasworld ).routetable; doswap = ( LittleLong( 1 ) != 1 ); // check ident AAS_RT_DBG_Read( &ident, sizeof( ident ), fp ); ident = LittleLong( ident ); if ( ident != RTBID ) { AAS_Error( "File is not an RTB file\n" ); botimport.FS_FCloseFile( fp ); return qfalse; } // check version AAS_RT_DBG_Read( &version, sizeof( version ), fp ); version = LittleLong( version ); if ( version != RTBVERSION ) { AAS_Error( "File is version %i not %i\n", version, RTBVERSION ); botimport.FS_FCloseFile( fp ); return qfalse; } // read the CRC check on the AAS data AAS_RT_DBG_Read( &crc, sizeof( crc ), fp ); crc = LittleShort( crc ); // calculate a CRC on the AAS areas crc_aas = CRC_ProcessString( (unsigned char *)( *aasworld ).areas, sizeof( aas_area_t ) * ( *aasworld ).numareas ); if ( crc != crc_aas ) { AAS_Error( "Route-table is from different AAS file, ignoring.\n" ); botimport.FS_FCloseFile( fp ); return qfalse; } // read the route-table // children botimport.FS_Read( &routetable->numChildren, sizeof( int ), fp ); routetable->numChildren = LittleLong( routetable->numChildren ); routetable->children = (aas_rt_child_t *) AAS_RT_GetClearedMemory( routetable->numChildren * sizeof( aas_rt_child_t ) ); botimport.FS_Read( routetable->children, routetable->numChildren * sizeof( aas_rt_child_t ), fp ); child = &routetable->children[0]; if ( doswap ) { for ( i = 0; i < routetable->numChildren; i++, child++ ) { child->areanum = LittleShort( child->areanum ); child->numParentLinks = LittleLong( child->numParentLinks ); child->startParentLinks = LittleLong( child->startParentLinks ); } } // parents botimport.FS_Read( &routetable->numParents, sizeof( int ), fp ); routetable->numParents = LittleLong( routetable->numParents ); routetable->parents = (aas_rt_parent_t *) AAS_RT_GetClearedMemory( routetable->numParents * sizeof( aas_rt_parent_t ) ); botimport.FS_Read( routetable->parents, routetable->numParents * sizeof( aas_rt_parent_t ), fp ); parent = &routetable->parents[0]; if ( doswap ) { for ( i = 0; i < routetable->numParents; i++, parent++ ) { parent->areanum = LittleShort( parent->areanum ); parent->numParentChildren = LittleLong( parent->numParentChildren ); parent->startParentChildren = LittleLong( parent->startParentChildren ); parent->numVisibleParents = LittleLong( parent->numVisibleParents ); parent->startVisibleParents = LittleLong( parent->startVisibleParents ); } } // parentChildren botimport.FS_Read( &routetable->numParentChildren, sizeof( int ), fp ); routetable->numParentChildren = LittleLong( routetable->numParentChildren ); routetable->parentChildren = (unsigned short int *) AAS_RT_GetClearedMemory( routetable->numParentChildren * sizeof( unsigned short int ) ); botimport.FS_Read( routetable->parentChildren, routetable->numParentChildren * sizeof( unsigned short int ), fp ); psi = &routetable->parentChildren[0]; if ( doswap ) { for ( i = 0; i < routetable->numParentChildren; i++, psi++ ) { *psi = LittleShort( *psi ); } } // visibleParents botimport.FS_Read( &routetable->numVisibleParents, sizeof( int ), fp ); routetable->numVisibleParents = LittleLong( routetable->numVisibleParents ); routetable->visibleParents = (unsigned short int *) AAS_RT_GetClearedMemory( routetable->numVisibleParents * sizeof( unsigned short int ) ); botimport.FS_Read( routetable->visibleParents, routetable->numVisibleParents * sizeof( unsigned short int ), fp ); psi = &routetable->visibleParents[0]; if ( doswap ) { for ( i = 0; i < routetable->numVisibleParents; i++, psi++ ) { *psi = LittleShort( *psi ); } } // parentLinks botimport.FS_Read( &routetable->numParentLinks, sizeof( int ), fp ); routetable->numParentLinks = LittleLong( routetable->numParentLinks ); routetable->parentLinks = (aas_rt_parent_link_t *) AAS_RT_GetClearedMemory( routetable->numParentLinks * sizeof( aas_rt_parent_link_t ) ); botimport.FS_Read( routetable->parentLinks, routetable->numParentLinks * sizeof( aas_parent_link_t ), fp ); plink = &routetable->parentLinks[0]; if ( doswap ) { for ( i = 0; i < routetable->numParentLinks; i++, plink++ ) { plink->childIndex = LittleShort( plink->childIndex ); plink->parent = LittleShort( plink->parent ); } } // build the areaChildIndexes routetable->areaChildIndexes = (unsigned short int *) AAS_RT_GetClearedMemory( ( *aasworld ).numareas * sizeof( unsigned short int ) ); child = routetable->children; for ( i = 0; i < routetable->numChildren; i++, child++ ) { routetable->areaChildIndexes[child->areanum] = i + 1; } botimport.Print( PRT_MESSAGE, "Total Parents: %d\n", routetable->numParents ); botimport.Print( PRT_MESSAGE, "Total Children: %d\n", routetable->numChildren ); botimport.Print( PRT_MESSAGE, "Total Memory Used: %d\n", memorycount ); #ifdef DEBUG_READING_TIME botimport.Print( PRT_MESSAGE, "Route-Table read time: %i\n", Sys_MilliSeconds() - pretime ); #endif botimport.FS_FCloseFile( fp ); return qtrue; } int AAS_RT_NumParentLinks( aas_area_childlocaldata_t *child ) { aas_parent_link_t *plink; int i; i = 0; plink = child->parentlink; while ( plink ) { i++; plink = plink->next; } return i; } //=========================================================================== // main routine to build the route-table // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_CreateAllRoutingCache( void ); void AAS_RT_BuildRouteTable( void ) { int i,j,k; aas_area_t *srcarea; aas_areasettings_t *srcsettings; // vec3_t vec; unsigned int totalcount; unsigned int noroutecount; aas_area_buildlocalinfo_t **area_localinfos; aas_area_buildlocalinfo_t *localinfo; aas_area_childlocaldata_t **area_childlocaldata; aas_area_childlocaldata_t *child; aas_area_parent_t *area_parents[MAX_PARENTS]; aas_area_parent_t *thisparent; int bestchild, bestcount, bestparent, cnt; int memoryend; unsigned short int *visibleParents; #ifdef CHECK_TRAVEL_TIMES aas_rt_route_t **filteredroutetable; unsigned short int traveltime; #endif fileHandle_t fp; char filename[MAX_QPATH]; // not used anymore return; // create the routetable in this aasworld aasworld->routetable = (aas_rt_t *) AAS_RT_GetClearedMemory( sizeof( aas_rt_t ) ); // Try to load in a prepared route-table Com_sprintf( filename, MAX_QPATH, "maps/%s.rtb", ( *aasworld ).mapname ); botimport.Print( PRT_MESSAGE, "\n---------------------------------\n" ); botimport.Print( PRT_MESSAGE, "\ntrying to load %s\n", filename ); botimport.FS_FOpenFile( filename, &fp, FS_READ ); if ( fp ) { // read in the table.. if ( AAS_RT_ReadRouteTable( fp ) ) { AAS_RT_PrintMemoryUsage(); botimport.Print( PRT_MESSAGE, "\nAAS Route-Table loaded.\n" ); botimport.Print( PRT_MESSAGE, "---------------------------------\n\n" ); return; } else { botimport.Print( PRT_MESSAGE, "\nUnable to load %s, building route-table..\n", filename ); } } else { botimport.Print( PRT_MESSAGE, "file not found, building route-table\n\n" ); } botimport.Print( PRT_MESSAGE, "\n-------------------------------------\nRoute-table memory usage figures..\n\n" ); totalcount = 0; childcount = 0; noroutecount = 0; childcount = 0; num_parents = 0; memorycount = 0; cachememory = 0; filtered_areas = (unsigned short int *) AAS_RT_GetClearedMemory( ( *aasworld ).numareas * sizeof( unsigned short int ) ); rev_filtered_areas = (unsigned short int *) AAS_RT_GetClearedMemory( ( *aasworld ).numareas * sizeof( unsigned short int ) ); // to speed things up, build a list of FILTERED areas first // do this so we can check for filtered areas AAS_CreateAllRoutingCache(); for ( i = 0; i < ( *aasworld ).numareas; i++ ) { srcarea = &( *aasworld ).areas[i]; srcsettings = &( *aasworld ).areasettings[i]; #ifdef FILTERAREAS if ( !( srcsettings->areaflags & ( AREA_USEFORROUTING ) ) ) { continue; } if ( !( srcsettings->areaflags & ( AREA_GROUNDED | AREA_LIQUID | AREA_LADDER ) ) ) { continue; } #endif rev_filtered_areas[i] = childcount + 1; filtered_areas[childcount++] = (unsigned short int)i; } #ifdef CHECK_TRAVEL_TIMES // allocate and calculate the travel times filteredroutetable = (aas_rt_route_t **) AAS_RT_GetClearedMemory( childcount * sizeof( aas_rt_route_t * ) ); for ( i = 0; i < childcount; i++ ) filteredroutetable[i] = (aas_rt_route_t *) AAS_RT_GetClearedMemory( childcount * sizeof( aas_rt_route_t ) ); AAS_RT_CalculateRouteTable( filteredroutetable ); #endif // CHECK_TRAVEL_TIMES // allocate for the temporary build local data area_localinfos = (aas_area_buildlocalinfo_t **) AAS_RT_GetClearedMemory( childcount * sizeof( aas_area_buildlocalinfo_t * ) ); for ( i = 0; i < childcount; i++ ) { srcarea = &( *aasworld ).areas[filtered_areas[i]]; srcsettings = &( *aasworld ).areasettings[filtered_areas[i]]; // allocate memory for this area area_localinfos[i] = (aas_area_buildlocalinfo_t *) AAS_RT_GetClearedMemory( sizeof( aas_area_buildlocalinfo_t ) ); localinfo = area_localinfos[i]; for ( j = 0; j < childcount; j++ ) { if ( i == j ) { continue; } #ifdef CHECK_TRAVEL_TIMES // make sure travel time is reasonable // Get the travel time from i to j traveltime = (int)filteredroutetable[i][j].travel_time; if ( !traveltime ) { noroutecount++; continue; } if ( traveltime > MAX_LOCALTRAVELTIME ) { continue; } #endif // CHECK_TRAVEL_TIMES // Add it to the list localinfo->visible[localinfo->numvisible++] = j; totalcount++; if ( localinfo->numvisible >= MAX_VISIBLE_AREAS ) { botimport.Print( PRT_MESSAGE, "MAX_VISIBLE_AREAS exceeded, lower MAX_VISIBLE_RANGE\n" ); break; } } } // now calculate the best list of locale's // allocate for the long-term child data area_childlocaldata = (aas_area_childlocaldata_t **) AAS_RT_GetClearedMemory( childcount * sizeof( aas_area_childlocaldata_t * ) ); for ( i = 0; i < childcount; i++ ) { area_childlocaldata[i] = (aas_area_childlocaldata_t *) AAS_RT_GetClearedMemory( sizeof( aas_area_childlocaldata_t ) ); area_childlocaldata[i]->areanum = filtered_areas[i]; } while ( 1 ) { bestchild = -1; bestcount = 99999; // find the area with the least number of visible areas for ( i = 0; i < childcount; i++ ) { if ( area_childlocaldata[i]->parentlink ) { continue; // already has been allocated to a parent } cnt = AAS_RT_GetValidVisibleAreasCount( area_localinfos[i], area_childlocaldata ); if ( cnt < bestcount ) { bestcount = area_localinfos[i]->numvisible; bestchild = i; } } if ( bestchild < 0 ) { break; // our job is done } localinfo = area_localinfos[bestchild]; // look through this area's list of visible areas, and pick the one with the most VALID visible areas bestparent = bestchild; for ( i = 0; i < localinfo->numvisible; i++ ) { if ( area_childlocaldata[localinfo->visible[i]]->parentlink ) { continue; // already has been allocated to a parent } // calculate how many of children are valid cnt = AAS_RT_GetValidVisibleAreasCount( area_localinfos[localinfo->visible[i]], area_childlocaldata ); if ( cnt > bestcount ) { bestcount = cnt; bestparent = localinfo->visible[i]; } } // now setup this parent, and assign all it's children localinfo = area_localinfos[bestparent]; // we use all children now, not just valid ones bestcount = localinfo->numvisible; area_parents[num_parents] = (aas_area_parent_t *) AAS_RT_GetClearedMemory( sizeof( aas_area_parent_t ) ); thisparent = area_parents[num_parents]; thisparent->areanum = filtered_areas[bestparent]; thisparent->children = (unsigned short int *) AAS_RT_GetClearedMemory( ( localinfo->numvisible + 1 ) * sizeof( unsigned short int ) ); // first, add itself to the list (yes, a parent is a child of itself) child = area_childlocaldata[bestparent]; AAS_RT_AddParentLink( child, num_parents, thisparent->numchildren ); thisparent->children[thisparent->numchildren++] = filtered_areas[bestparent]; // loop around all the parent's visible list, and make them children if they're aren't already assigned to a parent for ( i = 0; i < localinfo->numvisible; i++ ) { // create the childlocaldata child = area_childlocaldata[localinfo->visible[i]]; // Ridah, only one parent per child in the new system if ( child->parentlink ) { continue; // already has been allocated to a parent } if ( child->areanum != thisparent->areanum ) { AAS_RT_AddParentLink( child, num_parents, thisparent->numchildren ); thisparent->children[thisparent->numchildren++] = filtered_areas[localinfo->visible[i]]; } } // now setup the list of children and the route-tables for ( i = 0; i < thisparent->numchildren; i++ ) { child = area_childlocaldata[-1 + rev_filtered_areas[thisparent->children[i]]]; localinfo = area_localinfos[-1 + rev_filtered_areas[thisparent->children[i]]]; child->parentlink->routeindexes = (unsigned short int *) AAS_RT_GetClearedMemory( thisparent->numchildren * sizeof( unsigned short int ) ); // now setup the indexes for ( j = 0; j < thisparent->numchildren; j++ ) { // find this child in our list of visibles if ( j == child->parentlink->childindex ) { continue; } for ( k = 0; k < localinfo->numvisible; k++ ) { if ( thisparent->children[j] == filtered_areas[localinfo->visible[k]] ) { // found a match child->parentlink->routeindexes[j] = (unsigned short int)k; break; } } if ( k == localinfo->numvisible ) { // didn't find it, so add it to our list if ( localinfo->numvisible >= MAX_VISIBLE_AREAS ) { botimport.Print( PRT_MESSAGE, "MAX_VISIBLE_AREAS exceeded, lower MAX_VISIBLE_RANGE\n" ); } else { localinfo->visible[localinfo->numvisible] = -1 + rev_filtered_areas[thisparent->children[j]]; child->parentlink->routeindexes[j] = (unsigned short int)localinfo->numvisible; localinfo->numvisible++; } } } } num_parents++; } // place all the visible areas from each child, into their childlocaldata route-table for ( i = 0; i < childcount; i++ ) { localinfo = area_localinfos[i]; child = area_childlocaldata[i]; child->numlocal = localinfo->numvisible; child->localroutes = (aas_rt_route_t *) AAS_RT_GetClearedMemory( localinfo->numvisible * sizeof( aas_rt_route_t ) ); for ( j = 0; j < localinfo->numvisible; j++ ) { child->localroutes[j] = filteredroutetable[i][localinfo->visible[j]]; } child->parentroutes = (aas_rt_route_t *) AAS_RT_GetClearedMemory( num_parents * sizeof( aas_rt_route_t ) ); for ( j = 0; j < num_parents; j++ ) { child->parentroutes[j] = filteredroutetable[i][-1 + rev_filtered_areas[area_parents[j]->areanum]]; } } // build the visibleParents lists visibleParents = (unsigned short int *) AAS_RT_GetClearedMemory( num_parents * sizeof( unsigned short int ) ); for ( i = 0; i < num_parents; i++ ) { area_parents[i]->numVisibleParents = 0; for ( j = 0; j < num_parents; j++ ) { if ( i == j ) { continue; } if ( !AAS_inPVS( ( *aasworld ).areas[area_parents[i]->areanum].center, ( *aasworld ).areas[area_parents[j]->areanum].center ) ) { continue; } visibleParents[area_parents[i]->numVisibleParents] = j; area_parents[i]->numVisibleParents++; } // now copy the list over to the current src area area_parents[i]->visibleParents = (unsigned short int *) AAS_RT_GetClearedMemory( area_parents[i]->numVisibleParents * sizeof( unsigned short int ) ); memcpy( area_parents[i]->visibleParents, visibleParents, area_parents[i]->numVisibleParents * sizeof( unsigned short int ) ); } AAS_RT_FreeMemory( visibleParents ); // before we free the main childlocaldata, go through and assign the aas_area's to their appropriate childlocaldata // this would require modification of the aas_area_t structure, so for now, we'll just place them in a global array, for external reference // aasworld->routetable->area_childlocaldata_list = (aas_area_childlocaldata_t **) AAS_RT_GetClearedMemory( (*aasworld).numareas * sizeof(aas_area_childlocaldata_t *) ); // for (i=0; iroutetable->area_childlocaldata_list[filtered_areas[i]] = area_childlocaldata[i]; // } // copy the list of parents to a global structure for now (should eventually go into the (*aasworld) structure // aasworld->routetable->area_parents_global = (aas_area_parent_t **) AAS_RT_GetClearedMemory( num_parents * sizeof(aas_area_parent_t *) ); // memcpy( aasworld->routetable->area_parents_global, area_parents, num_parents * sizeof(aas_area_parent_t *) ); // ................................................ // Convert the data into the correct format { aas_rt_t *rt; aas_rt_child_t *child; aas_rt_parent_t *parent; aas_rt_parent_link_t *plink; unsigned short int *psi; aas_area_childlocaldata_t *chloc; aas_area_parent_t *apar; aas_parent_link_t *oplink; int localRoutesCount, parentRoutesCount, parentChildrenCount, visibleParentsCount, parentLinkCount, routeIndexesCount; rt = ( *aasworld ).routetable; localRoutesCount = 0; parentRoutesCount = 0; parentChildrenCount = 0; visibleParentsCount = 0; parentLinkCount = 0; routeIndexesCount = 0; // areaChildIndexes rt->areaChildIndexes = (unsigned short int *) AAS_RT_GetClearedMemory( ( *aasworld ).numareas * sizeof( unsigned short int ) ); for ( i = 0; i < childcount; i++ ) { rt->areaChildIndexes[filtered_areas[i]] = i + 1; } // children rt->numChildren = childcount; rt->children = (aas_rt_child_t *) AAS_RT_GetClearedMemory( rt->numChildren * sizeof( aas_rt_child_t ) ); child = rt->children; for ( i = 0; i < childcount; i++, child++ ) { chloc = area_childlocaldata[i]; child->areanum = chloc->areanum; child->numParentLinks = AAS_RT_NumParentLinks( chloc ); child->startParentLinks = parentLinkCount; parentLinkCount += child->numParentLinks; } // parents rt->numParents = num_parents; rt->parents = (aas_rt_parent_t *) AAS_RT_GetClearedMemory( rt->numParents * sizeof( aas_rt_parent_t ) ); parent = rt->parents; for ( i = 0; i < num_parents; i++, parent++ ) { apar = area_parents[i]; parent->areanum = apar->areanum; parent->numParentChildren = apar->numchildren; parent->numVisibleParents = apar->numVisibleParents; parent->startParentChildren = parentChildrenCount; parent->startVisibleParents = visibleParentsCount; parentChildrenCount += parent->numParentChildren; visibleParentsCount += parent->numVisibleParents; } // parentChildren rt->numParentChildren = parentChildrenCount; rt->parentChildren = (unsigned short int *) AAS_RT_GetClearedMemory( parentChildrenCount * sizeof( unsigned short int ) ); psi = rt->parentChildren; for ( i = 0; i < num_parents; i++ ) { apar = area_parents[i]; for ( j = 0; j < apar->numchildren; j++, psi++ ) { *psi = apar->children[j]; } } // visibleParents rt->numVisibleParents = visibleParentsCount; rt->visibleParents = (unsigned short int *) AAS_RT_GetClearedMemory( rt->numVisibleParents * sizeof( unsigned short int ) ); psi = rt->visibleParents; for ( i = 0; i < num_parents; i++ ) { apar = area_parents[i]; for ( j = 0; j < apar->numVisibleParents; j++, psi++ ) { *psi = apar->visibleParents[j]; } } // parentLinks rt->numParentLinks = parentLinkCount; rt->parentLinks = (aas_rt_parent_link_t *) AAS_RT_GetClearedMemory( parentLinkCount * sizeof( aas_rt_parent_link_t ) ); plink = rt->parentLinks; for ( i = 0; i < childcount; i++ ) { chloc = area_childlocaldata[i]; for ( oplink = chloc->parentlink; oplink; plink++, oplink = oplink->next ) { plink->childIndex = oplink->childindex; plink->parent = oplink->parent; } } } // ................................................ // write the newly created table AAS_RT_WriteRouteTable(); botimport.Print( PRT_MESSAGE, "Child Areas: %i\nTotal Parents: %i\nAverage VisAreas: %i\n", (int)childcount, num_parents, (int)( childcount / num_parents ) ); botimport.Print( PRT_MESSAGE, "NoRoute Ratio: %i%%\n", (int)( ( 100.0 * noroutecount ) / ( 1.0 * childcount * childcount ) ) ); memoryend = memorycount; // clear allocated memory // causes crashes in route-caching //#ifdef USE_ROUTECACHE // AAS_FreeRoutingCaches(); //#endif for ( i = 0; i < childcount; i++ ) { AAS_RT_FreeMemory( area_localinfos[i] ); #ifdef CHECK_TRAVEL_TIMES AAS_RT_FreeMemory( filteredroutetable[i] ); #endif } { aas_parent_link_t *next, *trav; // kill the client areas for ( i = 0; i < childcount; i++ ) { // kill the parent links next = area_childlocaldata[i]->parentlink; // TTimo: suggest () around assignment used as truth value while ( ( trav = next ) ) { next = next->next; AAS_RT_FreeMemory( trav->routeindexes ); AAS_RT_FreeMemory( trav ); } AAS_RT_FreeMemory( area_childlocaldata[i]->localroutes ); AAS_RT_FreeMemory( area_childlocaldata[i]->parentroutes ); AAS_RT_FreeMemory( area_childlocaldata[i] ); } // kill the parents for ( i = 0; i < num_parents; i++ ) { AAS_RT_FreeMemory( area_parents[i]->children ); AAS_RT_FreeMemory( area_parents[i]->visibleParents ); AAS_RT_FreeMemory( area_parents[i] ); } } AAS_RT_FreeMemory( area_localinfos ); AAS_RT_FreeMemory( area_childlocaldata ); AAS_RT_FreeMemory( filtered_areas ); AAS_RT_FreeMemory( rev_filtered_areas ); #ifdef CHECK_TRAVEL_TIMES AAS_RT_FreeMemory( filteredroutetable ); #endif // check how much memory we've used, and intend to keep AAS_RT_PrintMemoryUsage(); botimport.Print( PRT_MESSAGE, "Route-Table Permanent Memory Usage: %i\n", memorycount ); botimport.Print( PRT_MESSAGE, "Route-Table Calculation Usage: %i\n", memoryend + cachememory ); botimport.Print( PRT_MESSAGE, "---------------------------------\n" ); } //=========================================================================== // free permanent memory used by route-table system // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_RT_ShutdownRouteTable( void ) { if ( !aasworld->routetable ) { return; } // free the dynamic lists AAS_RT_FreeMemory( aasworld->routetable->areaChildIndexes ); AAS_RT_FreeMemory( aasworld->routetable->children ); AAS_RT_FreeMemory( aasworld->routetable->parents ); AAS_RT_FreeMemory( aasworld->routetable->parentChildren ); AAS_RT_FreeMemory( aasworld->routetable->visibleParents ); // AAS_RT_FreeMemory( aasworld->routetable->localRoutes ); // AAS_RT_FreeMemory( aasworld->routetable->parentRoutes ); AAS_RT_FreeMemory( aasworld->routetable->parentLinks ); // AAS_RT_FreeMemory( aasworld->routetable->routeIndexes ); // AAS_RT_FreeMemory( aasworld->routetable->parentTravelTimes ); // kill the table AAS_RT_FreeMemory( aasworld->routetable ); aasworld->routetable = NULL; } //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== aas_rt_parent_link_t *AAS_RT_GetFirstParentLink( aas_rt_child_t *child ) { return &aasworld->routetable->parentLinks[child->startParentLinks]; } //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== aas_rt_child_t *AAS_RT_GetChild( int areanum ) { int i; i = (int)aasworld->routetable->areaChildIndexes[areanum] - 1; if ( i >= 0 ) { return &aasworld->routetable->children[i]; } else { return NULL; } } //=========================================================================== // returns a route between the areas // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_AreaRouteToGoalArea( int areanum, vec3_t origin, int goalareanum, int travelflags, int *traveltime, int *reachnum ); aas_rt_route_t *AAS_RT_GetRoute( int srcnum, vec3_t origin, int destnum ) { #define GETROUTE_NUMROUTES 64 static aas_rt_route_t routes[GETROUTE_NUMROUTES]; // cycle through these, so we don't overlap static int routeIndex = 0; aas_rt_route_t *thisroute; int reach, traveltime; aas_rt_t *rt; static int tfl = TFL_DEFAULT & ~( TFL_JUMPPAD | TFL_ROCKETJUMP | TFL_BFGJUMP | TFL_GRAPPLEHOOK | TFL_DOUBLEJUMP | TFL_RAMPJUMP | TFL_STRAFEJUMP | TFL_LAVA ); //----(SA) modified since slime is no longer deadly // static int tfl = TFL_DEFAULT & ~(TFL_JUMPPAD|TFL_ROCKETJUMP|TFL_BFGJUMP|TFL_GRAPPLEHOOK|TFL_DOUBLEJUMP|TFL_RAMPJUMP|TFL_STRAFEJUMP|TFL_SLIME|TFL_LAVA); if ( !( rt = aasworld->routetable ) ) { // no route table present return NULL; } if ( disable_routetable ) { return NULL; } if ( ++routeIndex >= GETROUTE_NUMROUTES ) { routeIndex = 0; } thisroute = &routes[routeIndex]; if ( AAS_AreaRouteToGoalArea( srcnum, origin, destnum, tfl, &traveltime, &reach ) ) { thisroute->reachable_index = reach; thisroute->travel_time = traveltime; return thisroute; } else { return NULL; } } //=========================================================================== // draws the route-table from src to dest // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== #include "../game/be_ai_goal.h" int BotGetReachabilityToGoal( vec3_t origin, int areanum, int entnum, int lastgoalareanum, int lastareanum, int *avoidreach, float *avoidreachtimes, int *avoidreachtries, bot_goal_t *goal, int travelflags, int movetravelflags ); void AAS_RT_ShowRoute( vec3_t srcpos, int srcnum, int destnum ) { #ifdef DEBUG #define MAX_RT_AVOID_REACH 1 AAS_ClearShownPolygons(); AAS_ClearShownDebugLines(); AAS_ShowAreaPolygons( srcnum, 1, qtrue ); AAS_ShowAreaPolygons( destnum, 4, qtrue ); { static int lastgoalareanum, lastareanum; static int avoidreach[MAX_RT_AVOID_REACH]; static float avoidreachtimes[MAX_RT_AVOID_REACH]; static int avoidreachtries[MAX_RT_AVOID_REACH]; int reachnum; bot_goal_t goal; aas_reachability_t reach; goal.areanum = destnum; VectorCopy( botlibglobals.goalorigin, goal.origin ); reachnum = BotGetReachabilityToGoal( srcpos, srcnum, -1, lastgoalareanum, lastareanum, avoidreach, avoidreachtimes, avoidreachtries, &goal, TFL_DEFAULT | TFL_FUNCBOB, TFL_DEFAULT | TFL_FUNCBOB ); AAS_ReachabilityFromNum( reachnum, &reach ); AAS_ShowReachability( &reach ); } #endif } /* ================= AAS_RT_GetHidePos "src" is hiding ent, "dest" is the enemy ================= */ int AAS_NearestHideArea( int srcnum, vec3_t origin, int areanum, int enemynum, vec3_t enemyorigin, int enemyareanum, int travelflags ); qboolean AAS_RT_GetHidePos( vec3_t srcpos, int srcnum, int srcarea, vec3_t destpos, int destnum, int destarea, vec3_t returnPos ) { static int tfl = TFL_DEFAULT & ~( TFL_JUMPPAD | TFL_ROCKETJUMP | TFL_BFGJUMP | TFL_GRAPPLEHOOK | TFL_DOUBLEJUMP | TFL_RAMPJUMP | TFL_STRAFEJUMP | TFL_LAVA ); //----(SA) modified since slime is no longer deadly // static int tfl = TFL_DEFAULT & ~(TFL_JUMPPAD|TFL_ROCKETJUMP|TFL_BFGJUMP|TFL_GRAPPLEHOOK|TFL_DOUBLEJUMP|TFL_RAMPJUMP|TFL_STRAFEJUMP|TFL_SLIME|TFL_LAVA); #if 1 // use MrE's breadth first method int hideareanum; // int pretime; // disabled this so grenade hiding works //if (!srcarea || !destarea) // return qfalse; // pretime = -Sys_MilliSeconds(); hideareanum = AAS_NearestHideArea( srcnum, srcpos, srcarea, destnum, destpos, destarea, tfl ); if ( !hideareanum ) { // botimport.Print(PRT_MESSAGE, "Breadth First HidePos FAILED: %i ms\n", pretime + Sys_MilliSeconds()); return qfalse; } // we found a valid hiding area VectorCopy( ( *aasworld ).areawaypoints[hideareanum], returnPos ); // botimport.Print(PRT_MESSAGE, "Breadth First HidePos: %i ms\n", pretime + Sys_MilliSeconds()); return qtrue; #else // look around at random parent areas, if any of them have a center point // that isn't visible from "destpos", then return it's position in returnPos int i, j, pathArea, dir; unsigned short int destTravelTime; aas_rt_parent_t *srcParent, *travParent, *destParent; aas_rt_child_t *srcChild, *destChild, *travChild; aas_rt_route_t *route; vec3_t destVec; float destTravelDist; static float lastTime; static int frameCount, maxPerFrame = 2; int firstreach; aas_reachability_t *reachability, *reach; qboolean startVisible; unsigned short int bestTravelTime, thisTravelTime, elapsedTravelTime; #define MAX_HIDE_TRAVELTIME 1000 // 10 seconds is a very long way away unsigned char destVisLookup[MAX_PARENTS]; unsigned short int *destVisTrav; aas_rt_t *rt; const int MAX_CHECK_VISPARENTS = 100; int visparents_count, total_parents_checked; int thisParentIndex; int pretime; if ( !( rt = aasworld->routetable ) ) { // no route table present return qfalse; } /* if (lastTime > (AAS_Time() - 0.1)) { if (frameCount++ > maxPerFrame) { return qfalse; } } else { frameCount = 0; lastTime = AAS_Time(); } */ pretime = -Sys_MilliSeconds(); // is the src area grounded? if ( !( srcChild = AAS_RT_GetChild( srcarea ) ) ) { return qfalse; } // does it have a parent? // all valid areas have a parent // if (!srcChild->numParentLinks) { // return qfalse; // } // get the dest (enemy) area if ( !( destChild = AAS_RT_GetChild( destarea ) ) ) { return qfalse; } destParent = &rt->parents[ rt->parentLinks[destChild->startParentLinks].parent ]; // // populate the destVisAreas memset( destVisLookup, 0, sizeof( destVisLookup ) ); destVisTrav = rt->visibleParents + destParent->startVisibleParents; for ( i = 0; i < destParent->numVisibleParents; i++, destVisTrav++ ) { destVisLookup[*destVisTrav] = 1; } // // use the first parent to source the vis areas from srcParent = &rt->parents[ rt->parentLinks[srcChild->startParentLinks].parent ]; // // set the destTravelTime if ( route = AAS_RT_GetRoute( srcarea, srcpos, destarea ) ) { destTravelTime = route->travel_time; } else { destTravelTime = 0; } bestTravelTime = MAX_HIDE_TRAVELTIME; // ignore any routes longer than 10 seconds away // set the destVec VectorSubtract( destpos, srcpos, destVec ); destTravelDist = VectorNormalize( destVec ); // // randomize the direction we traverse the list, so the hiding spot isn't always the same if ( rand() % 2 ) { dir = 1; // forward } else { dir = -1; // reverse } // randomize the starting area if ( srcParent->numVisibleParents ) { i = rand() % srcParent->numVisibleParents; } else { // prevent divide by zero i = 0; } // // setup misc stuff reachability = ( *aasworld ).reachability; startVisible = botimport.AICast_VisibleFromPos( destpos, destnum, srcpos, srcnum, qfalse ); // // set the firstreach to prevent having to do an array and pointer lookup for each destination firstreach = ( *aasworld ).areasettings[srcarea].firstreachablearea; // // just check random parent areas, traversing the route until we find an area that can't be seen from the dest area for ( visparents_count = 0, total_parents_checked = 0; visparents_count < MAX_CHECK_VISPARENTS && total_parents_checked < rt->numParents; total_parents_checked++ ) { thisParentIndex = rand() % rt->numParents; travParent = &rt->parents[ thisParentIndex ]; // // never go to the enemy's areas if ( travParent->areanum == destarea ) { continue; } // // if it's visible from dest, ignore it if ( destVisLookup[thisParentIndex] ) { continue; } // visparents_count++; // they might be visible, check to see if the path to the area, takes us towards the // enemy we are trying to hide from { qboolean invalidRoute; vec3_t curPos, lastVec; #define GETHIDE_MAX_CHECK_PATHS 15 // invalidRoute = qfalse; // initialize the pathArea pathArea = srcarea; VectorCopy( srcpos, curPos ); // now evaluate the path for ( j = 0; j < GETHIDE_MAX_CHECK_PATHS; j++ ) { // get the reachability to the travParent if ( !( route = AAS_RT_GetRoute( pathArea, curPos, travParent->areanum ) ) ) { // we can't get to the travParent, so don't bother checking it invalidRoute = qtrue; break; } // set the pathArea reach = &reachability[route->reachable_index]; // how far have we travelled so far? elapsedTravelTime = AAS_AreaTravelTimeToGoalArea( pathArea, curPos, reach->areanum, tfl ); // add the travel to the center of the area elapsedTravelTime += AAS_AreaTravelTime( reach->areanum, reach->end, ( *aasworld ).areas[reach->areanum].center ); // have we gone too far already? if ( elapsedTravelTime > bestTravelTime ) { invalidRoute = qtrue; break; } else { thisTravelTime = route->travel_time; } // // if this travel would have us do something wierd if ( ( reach->traveltype == TRAVEL_WALKOFFLEDGE ) && ( reach->traveltime > 500 ) ) { invalidRoute = qtrue; break; } // pathArea = reach->areanum; VectorCopy( reach->end, curPos ); // // if this moves us into the enemies area, skip it if ( pathArea == destarea ) { invalidRoute = qtrue; break; } // if we are very close, don't get any closer under any circumstances { vec3_t vec; float dist; // VectorSubtract( destpos, reachability[firstreach + route->reachable_index].end, vec ); dist = VectorNormalize( vec ); // if ( destTravelTime < 400 ) { if ( dist < destTravelDist ) { invalidRoute = qtrue; break; } if ( DotProduct( destVec, vec ) < 0.2 ) { invalidRoute = qtrue; break; } } else { if ( dist < destTravelDist * 0.7 ) { invalidRoute = qtrue; break; } } // // check the directions to make sure we're not trying to run through them if ( j > 0 ) { if ( DotProduct( vec, lastVec ) < 0.2 ) { invalidRoute = qtrue; break; } } else if ( DotProduct( destVec, vec ) < 0.2 ) { invalidRoute = qtrue; break; } // VectorCopy( vec, lastVec ); } // // if this area isn't in the visible list for the enemy's area, it's a good hiding spot if ( !( travChild = AAS_RT_GetChild( pathArea ) ) ) { invalidRoute = qtrue; break; } if ( !destVisLookup[rt->parentLinks[travChild->startParentLinks].parent] ) { // success ? if ( !botimport.AICast_VisibleFromPos( destpos, destnum, ( *aasworld ).areas[pathArea].center, srcnum, qfalse ) ) { // SUCESS !! travParent = &rt->parents[rt->parentLinks[travChild->startParentLinks].parent]; break; } } else { // if we weren't visible when starting, make sure we don't move into their view if ( !startVisible ) { //botimport.AICast_VisibleFromPos( destpos, destnum, reachability[firstreach + route->reachable_index].end, srcnum, qfalse )) { invalidRoute = qtrue; break; } } // // if this is the travParent, then stop checking if ( pathArea == travParent->areanum ) { invalidRoute = qtrue; // we didn't find a hiding spot break; } } // end for areas in route // // if the route is invalid, skip this travParent if ( invalidRoute ) { continue; } } // // now last of all, check that this area is a safe hiding spot // if (botimport.AICast_VisibleFromPos( destpos, destnum, (*aasworld).areas[travParent->areanum].center, srcnum, qfalse )) { // continue; // } // // we've found a good hiding spot, so use it VectorCopy( ( *aasworld ).areas[travParent->areanum].center, returnPos ); bestTravelTime = elapsedTravelTime; // if ( thisTravelTime < 300 ) { botimport.Print( PRT_MESSAGE, "Fuzzy RT HidePos: %i ms\n", pretime + Sys_MilliSeconds() ); return qtrue; } } // // did we find something? if ( bestTravelTime < MAX_HIDE_TRAVELTIME ) { botimport.Print( PRT_MESSAGE, "Fuzzy RT HidePos: %i ms\n", pretime + Sys_MilliSeconds() ); return qtrue; } // // couldn't find anything botimport.Print( PRT_MESSAGE, "Fuzzy RT HidePos FAILED: %i ms\n", pretime + Sys_MilliSeconds() ); return qfalse; #endif } /* ================= AAS_RT_GetReachabilityIndex ================= */ int AAS_RT_GetReachabilityIndex( int areanum, int reachIndex ) { // return (*aasworld).areasettings[areanum].firstreachablearea + reachIndex; return reachIndex; }