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 }
void AI_NodeReached( edict_t *self ) { if( !AI_NewNextNode( self ) ) { AI_ClearGoal( self ); } }
/* * AI_TouchedEntity * Some AI has touched some entity. Some entities are declared to never be reached until touched. * See if it's one of them and declare it reached */ void AI_TouchedEntity( edict_t *self, edict_t *ent ) { int i; nav_ents_t *goalEnt; // right now we only support this on a few trigger entities (jumpads, teleporters) if( ent->r.solid != SOLID_TRIGGER && ent->item == NULL ) return; // clear short range goal, pick a new goal ASAP if( ent == self->movetarget ) { self->movetarget = NULL; self->ai->shortRangeGoalTimeout = level.time; } if( self->ai->goalEnt && ent == self->ai->goalEnt->ent ) { if( nav.debugMode && bot_showlrgoal->integer > 1 ) G_PrintChasersf( self, "REACHED entity %s\n", ent->classname ? ent->classname : "no classname" ); AI_ClearGoal( self ); return; } if( self->ai->next_node != NODE_INVALID && ( nodes[self->ai->next_node].flags & (NODEFLAGS_REACHATTOUCH|NODEFLAGS_ENTITYREACH) ) ) { for( i = 0; i < nav.num_navigableEnts; i++ ) { if( nav.navigableEnts[i].node == self->ai->next_node && nav.navigableEnts[i].ent == ent ) { if( nav.debugMode && bot_showlrgoal->integer > 1 ) G_PrintChasersf( self, "REACHED touch node %i with entity %s\n", self->ai->next_node, ent->classname ? ent->classname : "no classname" ); AI_NodeReached( self ); return; } } FOREACH_GOALENT( goalEnt ) { i = goalEnt->id; if( goalEnt->node == self->ai->next_node && goalEnt->ent == ent ) { if( nav.debugMode && bot_showlrgoal->integer > 1 ) G_PrintChasersf( self, "REACHED touch node %i with entity %s\n", self->ai->next_node, ent->classname ? ent->classname : "no classname" ); AI_NodeReached( self ); return; } } } }
//========================================== // AI_ResetNavigation // Init bot navigation. Called at first spawn & each respawn //========================================== void AI_ResetNavigation( edict_t *self ) { self->enemy = self->ai->latched_enemy = NULL; self->ai->state_combat_timeout = 0; self->ai->is_bunnyhop = false; self->ai->nearest_node_tries = 0; self->ai->longRangeGoalTimeout = 0; self->ai->blocked_timeout = level.time + 15000; self->ai->shortRangeGoalTimeout = level.time + AI_SHORT_RANGE_GOAL_DELAY; self->movetarget = NULL; AI_ClearGoal( self ); }
/* * AI_ReachedEntity * Some nodes are declared so they are never reached until the entity says so. * This is a entity saying so. */ void AI_ReachedEntity( edict_t *self ) { nav_ents_t *goalEnt; edict_t *ent; if( ( goalEnt = AI_GetGoalentForEnt( self ) ) == NULL ) return; // find all bots which have this node as goal and tell them their goal is reached for( ent = game.edicts + 1; PLAYERNUM( ent ) < gs.maxclients; ent++ ) { if( !ent->ai || ent->ai->type == AI_INACTIVE ) continue; if( ent->ai->goal_node == goalEnt->node ) AI_ClearGoal( ent ); } }
bool AI_NewNextNode( edict_t *self ) { // reset timeout self->ai->node_timeout = 0; if( self->ai->next_node == self->ai->goal_node ) { if( nav.debugMode && bot_showlrgoal->integer > 1 ) G_PrintChasersf( self, "%s: GOAL REACHED!\n", self->ai->pers.netname ); //if botroam, setup a timeout for it /* if( nodes[self->ai->goal_node].flags & NODEFLAGS_BOTROAM ) { int i; for( i = 0; i < nav.num_broams; i++ ) //find the broam { if( nav.broams[i].node != self->ai->goal_node ) continue; //if(AIDevel.debugChased && bot_showlrgoal->integer) // G_PrintMsg (AIDevel.chaseguy, "%s: BotRoam Time Out set up for node %i\n", self->ai->pers.netname, nav.broams[i].node); //Com_Printf( "%s: BotRoam Time Out set up for node %i\n", self->ai->pers.netname, nav.broams[i].node); self->ai->status.broam_timeouts[i] = level.time + 15000; break; } } */ // don't let it wait too long to weight the inventory again AI_ClearGoal( self ); return false; // force checking for a new long range goal } // we did not reach our goal yet. just setup next node... self->ai->current_node = self->ai->next_node; if( self->ai->path.numNodes ) self->ai->path.numNodes--; self->ai->next_node = self->ai->path.nodes[self->ai->path.numNodes]; return true; }
//========================================== // BOT_DMclass_DeadFrame // ent is dead = run this think func //========================================== static void BOT_DMclass_GhostingFrame( edict_t *self ) { usercmd_t ucmd; AI_ClearGoal( self ); self->ai->blocked_timeout = level.time + 15000; self->nextThink = level.time + 100; // wait 4 seconds after entering the level if( self->r.client->level.timeStamp + 4000 > level.time || !level.canSpawnEntities ) return; if( self->r.client->team == TEAM_SPECTATOR ) { // try to join a team // note that G_Teams_JoinAnyTeam is quite slow so only call it per frame if( !self->r.client->queueTimeStamp && self == level.think_client_entity ) G_Teams_JoinAnyTeam( self, false ); if( self->r.client->team == TEAM_SPECTATOR ) // couldn't join, delay the next think self->nextThink = level.time + 2000 + (int)( 4000 * random() ); else self->nextThink = level.time + 1; return; } memset( &ucmd, 0, sizeof( ucmd ) ); // set approximate ping and show values ucmd.serverTimeStamp = game.serverTime; ucmd.msec = game.frametime; self->r.client->r.ping = 0; // ask for respawn if the minimum bot respawning time passed if( level.time > self->deathTimeStamp + 3000 ) ucmd.buttons = BUTTON_ATTACK; ClientThink( self, &ucmd, 0 ); }
//========================================== // AI_Think // think funtion for AIs //========================================== void AI_Think( edict_t *self ) { if( !self->ai || self->ai->type == AI_INACTIVE ) return; if( level.spawnedTimeStamp + 5000 > game.realtime || !level.canSpawnEntities ) { self->nextThink = level.time + game.snapFrameTime; return; } // check for being blocked if( !G_ISGHOSTING( self ) ) { AI_CategorizePosition( self ); if( VectorLengthFast( self->velocity ) > 37 ) self->ai->blocked_timeout = level.time + 10000; // if completely stuck somewhere if( self->ai->blocked_timeout < level.time ) { self->ai->pers.blockedTimeout( self ); return; } } //update status information to feed up ai if( self->ai->statusUpdateTimeout <= level.time ) AI_UpdateStatus( self ); if( AI_NodeHasTimedOut( self ) ) AI_ClearGoal( self ); if( self->ai->goal_node == NODE_INVALID ) AI_PickLongRangeGoal( self ); //if( self == level.think_client_entity ) AI_PickShortRangeGoal( self ); self->ai->pers.RunFrame( self ); // Show the path if( nav.debugMode && bot_showpath->integer && self->ai->goal_node != NODE_INVALID ) { // only draw the path of those bots which are being chased edict_t *chaser; bool chaserFound = false; for( chaser = game.edicts + 1; ENTNUM( chaser ) < gs.maxclients; chaser++ ) { if( chaser->r.client->resp.chase.active && chaser->r.client->resp.chase.target == ENTNUM( self ) ) { AITools_DrawPath( self, self->ai->goal_node ); chaserFound = true; } } if( !chaserFound && game.numBots == 1 ) AITools_DrawPath( self, self->ai->goal_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 }