//========================================== // AI_SetGoal // set the goal //jabot092 //========================================== void AI_SetGoal(edict_t *self, int goal_node) { int node; self->ai->goal_node = goal_node; node = AI_FindClosestReachableNode( self->s.origin, self, NODE_DENSITY*3, NODE_ALL ); if(node == -1) { AI_SetUpMoveWander(self); return; } //------- ASTAR ----------- //do this twice, 1 time randomly dropping some nodes out so bots take alternate routes. Second time, normal. dropnodes = true; if( !AStar_GetPath( node, goal_node, self->ai->pers.moveTypesMask, &self->ai->path ) ) { dropnodes = false; if( !AStar_GetPath( node, goal_node, self->ai->pers.moveTypesMask, &self->ai->path ) ) { AI_SetUpMoveWander(self); return; } } dropnodes = false; self->ai->current_node = self->ai->path.nodes[self->ai->path.numNodes]; //------------------------- // if(AIDevel.debugChased && bot_showlrgoal->value) // G_PrintMsg (AIDevel.chaseguy, PRINT_HIGH, "%s: GOAL: new START NODE selected %d\n", self->ai->pers.netname, node); self->ai->next_node = self->ai->current_node; // make sure we get to the nearest node first self->ai->node_timeout = 0; }
void AI_SetGoal( edict_t *self, int goal_node ) { int node; self->ai->goal_node = goal_node; node = AI_FindClosestReachableNode( self->s.origin, self, NODE_DENSITY * 3, NODE_ALL ); if( node == NODE_INVALID ) { AI_ClearGoal( self ); return; } // ASTAR if( !AStar_GetPath( node, goal_node, self->ai->status.moveTypesMask, &self->ai->path ) ) { AI_ClearGoal( self ); return; } self->ai->current_node = self->ai->path.nodes[self->ai->path.numNodes]; if( nav.debugMode && bot_showlrgoal->integer > 1 ) G_PrintChasersf( self, "%s: GOAL: new START NODE selected %d goal %d\n", self->ai->pers.netname, node, self->ai->goal_node ); self->ai->next_node = self->ai->current_node; // make sure we get to the nearest node first self->ai->node_timeout = 0; self->ai->longRangeGoalTimeout = 0; self->ai->tries = 0; // Reset the count of how many times we tried this goal }
//========================================== // AITools_ShowPlinks // Draws lines from the current node to it's plinks nodes //========================================== static void AITools_ShowPlinks( edict_t *target ) { static unsigned int debugdrawplinks_timeout; int current_node; int plink_node; int i; nav_ents_t *goalEnt; if( !target || !target->r.client || !target->r.client->level.showPLinks ) return; //do it current_node = AI_FindClosestReachableNode( target->s.origin, target, NODE_DENSITY * 3, NODE_ALL ); // draw the axis where the node is if( nodes[current_node].flags & NODEFLAGS_SERVERLINK ) AITools_DrawAxis( nodes[current_node].origin, COLOR_RGBA( 255, 25, 25, 255 ) ); else AITools_DrawAxis( nodes[current_node].origin, COLOR_RGBA( 210, 250, 250, 255 ) ); //don't draw the links every frame (flood) if( level.time < debugdrawplinks_timeout ) return; debugdrawplinks_timeout = level.time + 4 * game.snapFrameTime; if( nav.editmode || !nav.loaded ) return; FOREACH_GOALENT( goalEnt ) { i = goalEnt->id; if( goalEnt->node == current_node ) { if( !goalEnt->ent->classname ) G_CenterPrintMsg( target, "no classname" ); else G_CenterPrintMsg( target, "%s", goalEnt->ent->classname ); break; } } // no links to draw if( !pLinks[current_node].numLinks ) return; for( i = 0; i < pLinks[current_node].numLinks; i++ ) { plink_node = pLinks[current_node].nodes[i]; if( pLinks[current_node].moveType[i] == LINK_ROCKETJUMP ) AITools_DrawColorLine( nodes[current_node].origin, nodes[plink_node].origin, COLOR_RGBA( 0xff, 0x00, 0x00, 0x80 ), 0 ); else if( pLinks[current_node].moveType[i] == LINK_JUMP ) AITools_DrawColorLine( nodes[current_node].origin, nodes[plink_node].origin, COLOR_RGBA( 0x00, 0x00, 0xff, 0x80 ), 0 ); else AITools_DrawColorLine( nodes[current_node].origin, nodes[plink_node].origin, COLOR_RGBA( 0x00, 0xff, 0x00, 0x80 ), 0 ); } }
void Cmd_deleteClosestNode_f( edict_t *ent ) { int node; if( ent && nav.num_nodes > 0 ) { node = AI_FindClosestReachableNode( ent->s.origin, ent, NODE_DENSITY*3, NODE_ALL ); AI_DeleteNode( node ); } }
//========================================== // AI_PickLongRangeGoal // // Evaluate the best long range goal and send the bot on // its way. This is a good time waster, so use it sparingly. // Do not call it for every think cycle. // // jal: I don't think there is any problem by calling it, // now that we have stored the costs at the nav.costs table (I don't do it anyway) //========================================== void AI_PickLongRangeGoal( edict_t *self ) { #define WEIGHT_MAXDISTANCE_FACTOR 20000.0f #define COST_INFLUENCE 0.5f int i; float weight, bestWeight = 0.0; int current_node; float cost; float dist; nav_ents_t *goalEnt, *bestGoalEnt = NULL; AI_ClearGoal( self ); if( G_ISGHOSTING( self ) ) return; if( self->ai->longRangeGoalTimeout > level.time ) return; if( !self->r.client->ps.pmove.stats[PM_STAT_MAXSPEED] ) { return; } self->ai->longRangeGoalTimeout = level.time + AI_LONG_RANGE_GOAL_DELAY + brandom( 0, 1000 ); // look for a target current_node = AI_FindClosestReachableNode( self->s.origin, self, ( ( 1 + self->ai->nearest_node_tries ) * NODE_DENSITY ), NODE_ALL ); self->ai->current_node = current_node; if( current_node == NODE_INVALID ) { if( nav.debugMode && bot_showlrgoal->integer ) G_PrintChasersf( self, "%s: LRGOAL: Closest node not found. Tries:%i\n", self->ai->pers.netname, self->ai->nearest_node_tries ); self->ai->nearest_node_tries++; // extend search radius with each try return; } self->ai->nearest_node_tries = 0; // Run the list of potential goal entities FOREACH_GOALENT( goalEnt ) { i = goalEnt->id; if( !goalEnt->ent ) continue; if( !goalEnt->ent->r.inuse ) { goalEnt->node = NODE_INVALID; continue; } if( goalEnt->ent->r.client ) { if( G_ISGHOSTING( goalEnt->ent ) || goalEnt->ent->flags & (FL_NOTARGET|FL_BUSY) ) goalEnt->node = NODE_INVALID; else goalEnt->node = AI_FindClosestReachableNode( goalEnt->ent->s.origin, goalEnt->ent, NODE_DENSITY, NODE_ALL ); } if( goalEnt->ent->item ) { if( !G_Gametype_CanPickUpItem( goalEnt->ent->item ) ) continue; } if( goalEnt->node == NODE_INVALID ) continue; weight = self->ai->status.entityWeights[i]; if( weight <= 0.0f ) continue; // don't try to find cost for too far away objects dist = DistanceFast( self->s.origin, goalEnt->ent->s.origin ); if( dist > WEIGHT_MAXDISTANCE_FACTOR * weight/* || dist < AI_GOAL_SR_RADIUS*/ ) continue; cost = AI_FindCost( current_node, goalEnt->node, self->ai->status.moveTypesMask ); if( cost == NODE_INVALID ) continue; cost -= brandom( 0, 2000 ); // allow random variations clamp_low( cost, 1 ); weight = ( 1000 * weight ) / ( cost * COST_INFLUENCE ); // Check against cost of getting there if( weight > bestWeight ) { bestWeight = weight; bestGoalEnt = goalEnt; } } if( bestGoalEnt ) { self->ai->goalEnt = bestGoalEnt; AI_SetGoal( self, bestGoalEnt->node ); if( self->ai->goalEnt != NULL && nav.debugMode && bot_showlrgoal->integer ) G_PrintChasersf( self, "%s: selected a %s at node %d for LR goal. (weight %f)\n", self->ai->pers.netname, self->ai->goalEnt->ent->classname, self->ai->goalEnt->node, bestWeight ); return; } if( nav.debugMode && bot_showlrgoal->integer ) G_PrintChasersf( self, "%s: did not find a LR goal.\n", self->ai->pers.netname ); #undef WEIGHT_MAXDISTANCE_FACTOR #undef COST_INFLUENCE }
//========================================== // AI_CreateNodesForEntities // // Entities aren't saved into nodes files anymore. // They generate and link it's nodes at map load. //========================================== void AI_CreateNodesForEntities ( void ) { edict_t *ent; int node; nav.num_ents = 0; memset( nav.ents, 0, sizeof(nav_ents_t) * MAX_EDICTS ); // Add special entities // for( ent = game.edicts; ent < &game.edicts[game.numentities]; ent++ ) for( ent = g_edicts; ent < &g_edicts[game.maxentities]; ent++ ) { if( !ent->classname ) continue; // platforms if( !strcmp( ent->classname,"func_plat" ) ) { AI_AddNode_Platform( ent ); } // teleporters else if( !strcmp( ent->classname,"trigger_teleport" ) ) { AI_AddNode_Teleporter( ent ); } // jump pads else if( !strcmp( ent->classname,"trigger_push" ) ) { AI_AddNode_JumpPad( ent ); } // doors else if( !strcmp( ent->classname,"func_door" ) ) { AI_AddNode_Door( ent ); } } // bot roams nav.num_broams = 0; memset( nav.broams, 0, sizeof(nav_broam_t) * MAX_BOT_ROAMS ); //visit world nodes first, and put in list what we find in there for( node=0; node < nav.num_nodes; node++ ) { if( nodes[node].flags & NODEFLAGS_BOTROAM && nav.num_broams < MAX_BOT_ROAMS) { nav.broams[nav.num_broams].node = node; nav.broams[nav.num_broams].weight = 0.3; nav.num_broams++; } } //now add bot roams from entities // for(ent = game.edicts; ent < &game.edicts[game.numentities]; ent++) for( ent = g_edicts; ent < &g_edicts[game.maxentities]; ent++ ) { if( !ent->classname ) continue; if( !strcmp( ent->classname,"item_botroam" ) ) { //if we have a available node close enough to the item, use it instead of dropping a new node node = AI_FindClosestReachableNode( ent->s.origin, NULL, 48, NODE_ALL ); if( node != -1 && !(nodes[node].flags & NODEFLAGS_SERVERLINK) && !(nodes[node].flags & NODEFLAGS_LADDER) ) { float heightdiff = 0; heightdiff = ent->s.origin[2] - nodes[node].origin[2]; if( heightdiff < 0 ) heightdiff = -heightdiff; if( heightdiff < AI_STEPSIZE && nav.num_broams < MAX_BOT_ROAMS ) //near enough { nodes[node].flags |= NODEFLAGS_BOTROAM; //add node to botroam list if( ent->count ) nav.broams[nav.num_broams].weight = ent->count * 0.01;//count is a int with a value in between 0 and 100 else nav.broams[nav.num_broams].weight = 0.3; //jalfixme: add cmd to weight (dropped by console cmd, self is player) nav.broams[nav.num_broams].node = node; nav.num_broams++; continue; } } //drop a new node if( nav.num_broams < MAX_BOT_ROAMS ){ AI_AddNode_BotRoam( ent ); } } } // game items (I want all them on top of the nodes array) nav.num_items = 0; memset( nav.items, 0, sizeof(nav_item_t) * MAX_EDICTS ); // for( ent = game.edicts; ent < &game.edicts[game.numentities]; ent++ ) for( ent = g_edicts; ent < &g_edicts[game.maxentities]; ent++ ) { int item_index; if( !ent->classname || !ent->item ) continue; item_index = ITEM_INDEX( ent->item ); if(item_index == INVALID) continue; //if we have a available node close enough to the item, use it node = AI_FindClosestReachableNode( ent->s.origin, NULL, 48, NODE_ALL ); if( node != INVALID ) { if( nodes[node].flags & NODEFLAGS_SERVERLINK || nodes[node].flags & NODEFLAGS_LADDER ) node = INVALID; else { float heightdiff = 0; heightdiff = ent->s.origin[2] - nodes[node].origin[2]; if( heightdiff < 0 ) heightdiff = -heightdiff; if( heightdiff > AI_STEPSIZE ) //not near enough node = INVALID; } } //drop a new node if( node == INVALID ) node = AI_AddNode_ItemNode( ent ); if( node != INVALID ) { nav.items[nav.num_items].node = node; nav.items[nav.num_items].ent = ent; nav.items[nav.num_items].item = item_index; nav.num_items++; } } }
static void AI_AddGoalEntityNode( edict_t *ent, qboolean customReach ) { int node = NODE_INVALID; int range = NODE_DENSITY * 0.75; if( !ent->r.inuse || !ent->classname ) return; if( AI_GetGoalentForEnt( ent ) != NULL ) return; if( ent->r.client ) { nav.goalEnts[nav.num_goalEnts].node = NODE_INVALID; nav.goalEnts[nav.num_goalEnts].ent = ent; nav.num_goalEnts++; return; } // // Goal entities // if( nav.loaded ) range = AI_GOAL_SR_RADIUS; // if we have a available node close enough to the entity, use it node = AI_FindClosestReachableNode( ent->s.origin, ent, range, NODE_ALL ); if( node != NODE_INVALID ) { if( nodes[node].flags & NODE_MASK_NOREUSE ) node = NODE_INVALID; else { float heightdiff = fabs( ent->s.origin[2] - nodes[node].origin[2] ); if( heightdiff > AI_STEPSIZE + 8 ) // not near enough node = NODE_INVALID; } } // link the node is spawned after the load process is done if( nav.loaded ) { if( node == NODE_INVALID ) // (BY NOW) can not create new nodes after the initialization was done return; if( nav.debugMode && bot_showlrgoal->integer > 2 ) G_Printf( "New Goal Entity added: %s\n", ent->classname ); } else { // didn't find a node we could reuse if( node == NODE_INVALID ) node = AI_AddNode_GoalEntityNode( ent ); } if( node != NODE_INVALID ) { nav.goalEnts[nav.num_goalEnts].node = node; nav.goalEnts[nav.num_goalEnts].ent = ent; nav.num_goalEnts++; if( customReach ) nodes[node].flags |= NODEFLAGS_ENTITYREACH; } }