/* * BotTestAAS */ void BotTestAAS(Vec3 origin) { int areanum; aas_areainfo_t info; trap_cvarupdate(&bot_testsolid); trap_cvarupdate(&bot_testclusters); if(bot_testsolid.integer){ if(!trap_AAS_Initialized()) return; areanum = BotPointAreaNum(origin); if(areanum) BotAI_Print(PRT_MESSAGE, "\remtpy area"); else BotAI_Print(PRT_MESSAGE, "\r^1SOLID area"); }else if(bot_testclusters.integer){ if(!trap_AAS_Initialized()) return; areanum = BotPointAreaNum(origin); if(!areanum) BotAI_Print(PRT_MESSAGE, "\r^1Solid! "); else{ trap_AAS_AreaInfo(areanum, &info); BotAI_Print(PRT_MESSAGE, "\rarea %d, cluster %d ", areanum, info.cluster); } } }
/* =============== Svcmd_AddBot_f =============== */ void Svcmd_AddBot_f( void ) { float skill; int delay; char name[MAX_TOKEN_CHARS]; char altname[MAX_TOKEN_CHARS]; char string[MAX_TOKEN_CHARS]; char team[MAX_TOKEN_CHARS]; // are bots enabled? if ( !trap_Cvar_VariableIntegerValue( "bot_enable" ) || !trap_AAS_Initialized() ) { return; } // name trap_Argv( 1, name, sizeof( name ) ); if ( !name[0] ) { trap_Printf( "Usage: Addbot <botname> [skill 1-5] [team] [msec delay] [altname]\n" ); return; } // skill trap_Argv( 2, string, sizeof( string ) ); if ( !string[0] ) { skill = 4; } else { skill = atof( string ); } // team trap_Argv( 3, team, sizeof( team ) ); // delay trap_Argv( 4, string, sizeof( string ) ); if ( !string[0] ) { delay = 0; } else { delay = atoi( string ); } // alternative name trap_Argv( 5, altname, sizeof( altname ) ); G_AddBot( name, skill, team, delay, altname ); // if this was issued during gameplay and we are playing locally, // go ahead and load the bot's media immediately if ( level.time - level.startTime > 1000 && trap_Cvar_VariableIntegerValue( "cl_running" ) ) { trap_SendServerCommand( -1, "loaddefered\n" ); // FIXME: spelled wrong, but not changing for demo } }
/* =============== G_BotConnect =============== */ qboolean G_BotConnect( int clientNum, qboolean restart ) { bot_settings_t settings; char userinfo[MAX_INFO_STRING]; trap_GetUserinfo( clientNum, userinfo, sizeof(userinfo) ); Q_strncpyz( settings.characterfile, Info_ValueForKey( userinfo, "characterfile" ), sizeof(settings.characterfile) ); settings.skill = atof( Info_ValueForKey( userinfo, "skill" ) ); Q_strncpyz( settings.team, Info_ValueForKey( userinfo, "team" ), sizeof(settings.team) ); if (!trap_AAS_Initialized() || !BotAISetupClient( clientNum, &settings, restart )) { trap_DropClient( clientNum, "BotAISetupClient failed" ); return qfalse; } return qtrue; }
void AICast_Init (void) { vmCvar_t cvar; int i; numSecrets = 0; numcast = 0; numSpawningCast = 0; saveGamePending = qtrue; trap_Cvar_Register( &aicast_debug, "aicast_debug", "0", 0 ); trap_Cvar_Register( &aicast_debugname, "aicast_debugname", "", 0 ); trap_Cvar_Register( &aicast_scripts, "aicast_scripts", "1", 0 ); // (aicast_thinktime / sv_fps) * aicast_maxthink = number of cast's to think between each aicast frame // so.. // (100 / 20) * 6 = 30 // // so if the level has more than 30 AI cast's, they could start to bunch up, resulting in slower thinks trap_Cvar_Register( &cvar, "aicast_thinktime", "50", 0 ); aicast_thinktime = trap_Cvar_VariableIntegerValue( "aicast_thinktime" ); trap_Cvar_Register( &cvar, "aicast_maxthink", "12", 0 ); aicast_maxthink = trap_Cvar_VariableIntegerValue( "aicast_maxthink" ); aicast_maxclients = trap_Cvar_VariableIntegerValue( "sv_maxclients" ); aicast_skillscale = (float)trap_Cvar_VariableIntegerValue( "g_gameSkill" ) / (float)GSKILL_MAX; caststates = G_Alloc( aicast_maxclients * sizeof(cast_state_t) ); memset( caststates, 0, sizeof(caststates) ); for (i=0; i<MAX_CLIENTS; i++) { caststates[i].entityNum = i; } // try and load in the AAS now, so we can interact with it during spawning of entities i = 0; trap_AAS_SetCurrentWorld(0); while (!trap_AAS_Initialized() && (i++ < 10)) { trap_BotLibStartFrame((float) level.time / 1000); } }
/* ================== BotAIStartFrame ================== */ int BotAIStartFrame(int time) { int i; gentity_t *ent; bot_entitystate_t state; int elapsed_time, thinktime; static int local_time; static int botlib_residual; static int lastbotthink_time; G_CheckBotSpawn(); trap_Cvar_Update(&bot_rocketjump); trap_Cvar_Update(&bot_grapple); trap_Cvar_Update(&bot_fastchat); trap_Cvar_Update(&bot_nochat); trap_Cvar_Update(&bot_testrchat); trap_Cvar_Update(&bot_thinktime); trap_Cvar_Update(&bot_memorydump); trap_Cvar_Update(&bot_saveroutingcache); trap_Cvar_Update(&bot_pause); trap_Cvar_Update(&bot_report); trap_Cvar_Update(&bot_droppedweight); trap_Cvar_Update(&bot_offhandgrapple); trap_Cvar_Update(&bot_shownodechanges); trap_Cvar_Update(&bot_showteamgoals); trap_Cvar_Update(&bot_reloadcharacters); BotUpdateInfoConfigStrings(); if (bot_pause.integer) { // execute bot user commands every frame for( i = 0; i < level.maxplayers; i++ ) { if( !botstates[i] || !botstates[i]->inuse ) { continue; } if( g_entities[i].player->pers.connected != CON_CONNECTED ) { continue; } botstates[i]->lastucmd.forwardmove = 0; botstates[i]->lastucmd.rightmove = 0; botstates[i]->lastucmd.upmove = 0; botstates[i]->lastucmd.buttons = 0; botstates[i]->lastucmd.serverTime = time; trap_BotUserCommand(botstates[i]->playernum, &botstates[i]->lastucmd); } return qtrue; } if (bot_memorydump.integer) { trap_BotLibVarSet("memorydump", "1"); trap_Cvar_SetValue("bot_memorydump", 0); } if (bot_saveroutingcache.integer) { trap_BotLibVarSet("saveroutingcache", "1"); trap_Cvar_SetValue("bot_saveroutingcache", 0); } //check if bot interbreeding is activated BotInterbreeding(); //cap the bot think time if (bot_thinktime.integer > 200) { trap_Cvar_SetValue("bot_thinktime", 200); } //if the bot think time changed we should reschedule the bots if (bot_thinktime.integer != lastbotthink_time) { lastbotthink_time = bot_thinktime.integer; BotScheduleBotThink(); } elapsed_time = time - local_time; local_time = time; botlib_residual += elapsed_time; if (elapsed_time > bot_thinktime.integer) thinktime = elapsed_time; else thinktime = bot_thinktime.integer; // update the bot library if ( botlib_residual >= thinktime ) { botlib_residual -= thinktime; trap_BotLibStartFrame((float) time / 1000); if (!trap_AAS_Initialized()) return qfalse; //update entities in the botlib for (i = 0; i < MAX_GENTITIES; i++) { ent = &g_entities[i]; ent->botvalid = qfalse; if (!ent->inuse) { trap_BotLibUpdateEntity(i, NULL); continue; } if (!ent->r.linked) { trap_BotLibUpdateEntity(i, NULL); continue; } if (ent->r.svFlags & SVF_NOCLIENT) { trap_BotLibUpdateEntity(i, NULL); continue; } // do not update missiles if (ent->s.eType == ET_MISSILE && ent->s.weapon != WP_GRAPPLING_HOOK) { trap_BotLibUpdateEntity(i, NULL); continue; } // do not update event only entities if (ent->s.eType > ET_EVENTS) { trap_BotLibUpdateEntity(i, NULL); continue; } #ifdef MISSIONPACK // never link prox mine triggers if (ent->s.contents == CONTENTS_TRIGGER) { if (ent->touch == ProximityMine_Trigger) { trap_BotLibUpdateEntity(i, NULL); continue; } } #endif // ent->botvalid = qtrue; ent->update_time = trap_AAS_Time() - ent->ltime; ent->ltime = trap_AAS_Time(); // memset(&state, 0, sizeof(bot_entitystate_t)); // VectorCopy(ent->r.currentOrigin, state.origin); if (i < MAX_CLIENTS) { VectorCopy(ent->s.apos.trBase, state.angles); } else { VectorCopy(ent->r.currentAngles, state.angles); } VectorCopy( ent->r.absmin, state.absmins ); VectorCopy( ent->r.absmax, state.absmaxs ); state.type = ent->s.eType; state.flags = ent->s.eFlags; // if (ent->s.collisionType == CT_SUBMODEL) { state.solid = SOLID_BSP; //if the angles of the model changed if ( !VectorCompare( state.angles, ent->lastAngles ) ) { VectorCopy(state.angles, ent->lastAngles); state.relink = qtrue; } } else { state.solid = SOLID_BBOX; VectorCopy(state.angles, ent->lastAngles); } //previous frame visorigin VectorCopy( ent->visorigin, ent->lastvisorigin ); //if the origin changed if ( !VectorCompare( state.origin, ent->visorigin ) ) { VectorCopy( state.origin, ent->visorigin ); state.relink = qtrue; } //if the bounding box size changed if (!VectorCompare(ent->s.mins, ent->lastMins) || !VectorCompare(ent->s.maxs, ent->lastMaxs)) { VectorCopy( ent->s.mins, ent->lastMins ); VectorCopy( ent->s.maxs, ent->lastMaxs ); state.relink = qtrue; } // trap_BotLibUpdateEntity(i, &state); } BotAIRegularUpdate(); } floattime = trap_AAS_Time(); // execute scheduled bot AI for( i = 0; i < MAX_CLIENTS; i++ ) { if( !botstates[i] || !botstates[i]->inuse ) { continue; } // botstates[i]->botthink_residual += elapsed_time; // if ( botstates[i]->botthink_residual >= thinktime ) { botstates[i]->botthink_residual -= thinktime; if (!trap_AAS_Initialized()) return qfalse; if (g_entities[i].player->pers.connected == CON_CONNECTED) { BotAI(i, (float) thinktime / 1000); } } } // execute bot user commands every frame for( i = 0; i < MAX_CLIENTS; i++ ) { if( !botstates[i] || !botstates[i]->inuse ) { continue; } if( g_entities[i].player->pers.connected != CON_CONNECTED ) { continue; } BotUpdateInput(botstates[i], time, elapsed_time); trap_BotUserCommand(botstates[i]->playernum, &botstates[i]->lastucmd); } return qtrue; }
/* ============== BotAISetupPlayer ============== */ int BotAISetupPlayer(int playernum, struct bot_settings_s *settings, qboolean restart) { char filename[MAX_PATH], name[MAX_PATH], gender[MAX_PATH]; bot_state_t *bs; int errnum; if (!botstates[playernum]) botstates[playernum] = trap_Alloc(sizeof(bot_state_t), NULL); bs = botstates[playernum]; if (!bs) { return qfalse; } if (bs && bs->inuse) { BotAI_Print(PRT_FATAL, "BotAISetupPlayer: player %d already setup\n", playernum); return qfalse; } if (!trap_AAS_Initialized()) { BotAI_Print(PRT_FATAL, "AAS not initialized\n"); return qfalse; } //load the bot character bs->character = BotLoadCharacter(settings->characterfile, settings->skill); if (!bs->character) { BotAI_Print(PRT_FATAL, "couldn't load skill %f from %s\n", settings->skill, settings->characterfile); return qfalse; } //copy the settings memcpy(&bs->settings, settings, sizeof(bot_settings_t)); //allocate a goal state bs->gs = BotAllocGoalState(playernum); //load the item weights Characteristic_String(bs->character, CHARACTERISTIC_ITEMWEIGHTS, filename, MAX_PATH); errnum = BotLoadItemWeights(bs->gs, filename); if (errnum != BLERR_NOERROR) { BotFreeGoalState(bs->gs); BotAI_Print(PRT_FATAL, "BotLoadItemWeights failed\n"); return qfalse; } //allocate a weapon state bs->ws = BotAllocWeaponState(playernum); //load the weapon weights Characteristic_String(bs->character, CHARACTERISTIC_WEAPONWEIGHTS, filename, MAX_PATH); errnum = BotLoadWeaponWeights(bs->ws, filename); if (errnum != BLERR_NOERROR) { BotFreeGoalState(bs->gs); BotFreeWeaponState(bs->ws); BotAI_Print(PRT_FATAL, "BotLoadWeaponWeights failed\n"); return qfalse; } //allocate a chat state bs->cs = BotAllocChatState(); //load the chat file Characteristic_String(bs->character, CHARACTERISTIC_CHAT_FILE, filename, MAX_PATH); Characteristic_String(bs->character, CHARACTERISTIC_CHAT_NAME, name, MAX_PATH); errnum = BotLoadChatFile(bs->cs, filename, name); if (errnum != BLERR_NOERROR) { BotFreeChatState(bs->cs); BotFreeGoalState(bs->gs); BotFreeWeaponState(bs->ws); BotAI_Print(PRT_FATAL, "BotLoadChatFile failed\n"); return qfalse; } //get the gender characteristic Characteristic_String(bs->character, CHARACTERISTIC_GENDER, gender, MAX_PATH); //set the chat gender if (*gender == 'f' || *gender == 'F') BotSetChatGender(bs->cs, CHAT_GENDERFEMALE); else if (*gender == 'm' || *gender == 'M') BotSetChatGender(bs->cs, CHAT_GENDERMALE); else BotSetChatGender(bs->cs, CHAT_GENDERLESS); bs->inuse = qtrue; bs->playernum = playernum; bs->entitynum = playernum; bs->setupcount = 4; bs->entergame_time = FloatTime(); bs->ms = BotAllocMoveState(playernum); bs->walker = Characteristic_BFloat(bs->character, CHARACTERISTIC_WALKER, 0, 1); bs->revenge_enemy = -1; numbots++; trap_Cvar_Update( &bot_testichat ); if (bot_testichat.integer) { BotChatTest(bs); } //NOTE: reschedule the bot thinking BotScheduleBotThink(); //if interbreeding start with a mutation if (bot_interbreed) { BotMutateGoalFuzzyLogic(bs->gs, 1); } // if we kept the bot state if (restart) { BotReadSessionData(bs); } //bot has been setup succesfully return qtrue; }
/* =============== G_CheckMinimumPlayers =============== */ void G_CheckMinimumPlayers( void ) { int minplayers; int humanplayers, botplayers; static int checkminimumplayers_time; if (level.intermissiontime) return; //only check once each 10 seconds //if (checkminimumplayers_time > level.time - 10000) { if (checkminimumplayers_time > level.time - (1000 + (bot_minplayersTime.integer * 100))) { // leilei - faster time return; } checkminimumplayers_time = level.time; trap_Cvar_Update(&bot_minplayers); minplayers = bot_minplayers.integer; if (minplayers <= 0) return; if (!trap_AAS_Initialized()) { minplayers = 0; checkminimumplayers_time = level.time+600*1000; return; //If no AAS then don't even try } if (g_gametype.integer >= GT_TEAM && g_ffa_gt!=1) { if (minplayers >= g_maxclients.integer / 2) { minplayers = (g_maxclients.integer / 2) -1; } humanplayers = G_CountHumanPlayers( TEAM_RED ); botplayers = G_CountBotPlayers( TEAM_RED ); // if (humanplayers + botplayers < minplayers) { G_AddRandomBot( TEAM_RED ); } else if (humanplayers + botplayers > minplayers && botplayers) { G_RemoveRandomBot( TEAM_RED ); } // humanplayers = G_CountHumanPlayers( TEAM_BLUE ); botplayers = G_CountBotPlayers( TEAM_BLUE ); // if (humanplayers + botplayers < minplayers) { G_AddRandomBot( TEAM_BLUE ); } else if (humanplayers + botplayers > minplayers && botplayers) { G_RemoveRandomBot( TEAM_BLUE ); } } else if (g_gametype.integer == GT_TOURNAMENT ) { if (minplayers >= g_maxclients.integer) { minplayers = g_maxclients.integer-1; } humanplayers = G_CountHumanPlayers( -1 ); botplayers = G_CountBotPlayers( -1 ); // if (humanplayers + botplayers < minplayers) { G_AddRandomBot( TEAM_FREE ); } else if (humanplayers + botplayers > minplayers && botplayers) { // try to remove spectators first if (!G_RemoveRandomBot( TEAM_SPECTATOR )) { // just remove the bot that is playing G_RemoveRandomBot( -1 ); } } } else if (g_gametype.integer == GT_FFA || g_gametype.integer == GT_LMS) { if (minplayers >= g_maxclients.integer) { minplayers = g_maxclients.integer-1; } humanplayers = G_CountHumanPlayers( TEAM_FREE ); botplayers = G_CountBotPlayers( TEAM_FREE ); // if (humanplayers + botplayers < minplayers) { G_AddRandomBot( TEAM_FREE ); } else if (humanplayers + botplayers > minplayers && botplayers) { G_RemoveRandomBot( TEAM_FREE ); } } }
/* =============== G_AddRandomBot =============== */ void G_AddRandomBot( int team ) { int i, n, num; float skill; char *value, netname[36], *teamstr; gclient_t *cl; if (!trap_AAS_Initialized()) return; //If no AAS then don't even try num = 0; for ( n = 0; n < g_numBots ; n++ ) { value = Info_ValueForKey( g_botInfos[n], "name" ); // for ( i=0 ; i< g_maxclients.integer ; i++ ) { cl = level.clients + i; if ( cl->pers.connected != CON_CONNECTED ) { continue; } if ( !(g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT) ) { continue; } if ( team >= 0 && cl->sess.sessionTeam != team ) { continue; } if ( !Q_stricmp( value, cl->pers.netname ) ) { break; } } if (i >= g_maxclients.integer) { num++; } } num = random() * num; for ( n = 0; n < g_numBots ; n++ ) { value = Info_ValueForKey( g_botInfos[n], "name" ); // for ( i=0 ; i< g_maxclients.integer ; i++ ) { cl = level.clients + i; if ( cl->pers.connected != CON_CONNECTED ) { continue; } if ( !(g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT) ) { continue; } if ( team >= 0 && cl->sess.sessionTeam != team ) { continue; } if ( !Q_stricmp( value, cl->pers.netname ) ) { break; } } if (i >= g_maxclients.integer) { num--; if (num <= 0) { skill = trap_Cvar_VariableValue( "g_spSkill" ); if (team == TEAM_RED) teamstr = "red"; else if (team == TEAM_BLUE) teamstr = "blue"; else teamstr = ""; strncpy(netname, value, sizeof(netname)-1); netname[sizeof(netname)-1] = '\0'; Q_CleanStr(netname); trap_SendConsoleCommand( EXEC_INSERT, va("addbot %s %f %s %i\n", netname, skill, teamstr, 0) ); return; } } } }
/* ================== BotAIStartFrame ================== */ int BotAIStartFrame( int time ) { int i; gentity_t *ent; bot_entitystate_t state; //entityState_t entitystate; //vec3_t mins = {-15, -15, -24}, maxs = {15, 15, 32}; int elapsed_time, thinktime; static int local_time; static int botlib_residual; static int lastbotthink_time; if ( g_gametype.integer != GT_SINGLE_PLAYER ) { G_CheckBotSpawn(); } trap_Cvar_Update( &bot_rocketjump ); trap_Cvar_Update( &bot_grapple ); trap_Cvar_Update( &bot_fastchat ); trap_Cvar_Update( &bot_nochat ); trap_Cvar_Update( &bot_testrchat ); trap_Cvar_Update( &bot_thinktime ); // Ridah, set the default AAS world trap_AAS_SetCurrentWorld( 0 ); trap_Cvar_Update( &memorydump ); if ( memorydump.integer ) { trap_BotLibVarSet( "memorydump", "1" ); trap_Cvar_Set( "memorydump", "0" ); } //if the bot think time changed we should reschedule the bots if ( bot_thinktime.integer != lastbotthink_time ) { lastbotthink_time = bot_thinktime.integer; BotScheduleBotThink(); } elapsed_time = time - local_time; local_time = time; botlib_residual += elapsed_time; if ( elapsed_time > bot_thinktime.integer ) { thinktime = elapsed_time; } else { thinktime = bot_thinktime.integer;} // update the bot library if ( botlib_residual >= thinktime ) { botlib_residual -= thinktime; trap_BotLibStartFrame( (float) time / 1000 ); // Ridah, only check the default world trap_AAS_SetCurrentWorld( 0 ); if ( !trap_AAS_Initialized() ) { return BLERR_NOERROR; } //update entities in the botlib for ( i = 0; i < MAX_GENTITIES; i++ ) { // Ridah, in single player, we only need client entity information if ( g_gametype.integer == GT_SINGLE_PLAYER && i > level.maxclients ) { break; } ent = &g_entities[i]; if ( !ent->inuse ) { continue; } if ( !ent->r.linked ) { continue; } if ( ent->r.svFlags & SVF_NOCLIENT ) { continue; } // memset( &state, 0, sizeof( bot_entitystate_t ) ); // VectorCopy( ent->r.currentOrigin, state.origin ); VectorCopy( ent->r.currentAngles, state.angles ); VectorCopy( ent->s.origin2, state.old_origin ); VectorCopy( ent->r.mins, state.mins ); VectorCopy( ent->r.maxs, state.maxs ); state.type = ent->s.eType; state.flags = ent->s.eFlags; if ( ent->r.bmodel ) { state.solid = SOLID_BSP; } else { state.solid = SOLID_BBOX;} state.groundent = ent->s.groundEntityNum; state.modelindex = ent->s.modelindex; state.modelindex2 = ent->s.modelindex2; state.frame = ent->s.frame; //state.event = ent->s.event; //state.eventParm = ent->s.eventParm; state.powerups = ent->s.powerups; state.legsAnim = ent->s.legsAnim; state.torsoAnim = ent->s.torsoAnim; // state.weapAnim = ent->s.weapAnim; //----(SA) //----(SA) didn't want to comment in as I wasn't sure of any implications of changing the aas_entityinfo_t and bot_entitystate_t structures. state.weapon = ent->s.weapon; /* if (!BotAI_GetEntityState(i, &entitystate)) continue; // memset(&state, 0, sizeof(bot_entitystate_t)); // VectorCopy(entitystate.pos.trBase, state.origin); VectorCopy(entitystate.angles, state.angles); VectorCopy(ent->s.origin2, state.old_origin); //VectorCopy(ent->r.mins, state.mins); //VectorCopy(ent->r.maxs, state.maxs); state.type = entitystate.eType; state.flags = entitystate.eFlags; if (ent->r.bmodel) state.solid = SOLID_BSP; else state.solid = SOLID_BBOX; state.modelindex = entitystate.modelindex; state.modelindex2 = entitystate.modelindex2; state.frame = entitystate.frame; state.event = entitystate.event; state.eventParm = entitystate.eventParm; state.powerups = entitystate.powerups; state.legsAnim = entitystate.legsAnim; state.torsoAnim = entitystate.torsoAnim; state.weapon = entitystate.weapon; */ // trap_BotLibUpdateEntity( i, &state ); } BotAIRegularUpdate(); } // Ridah, in single player, don't need bot's thinking if ( g_gametype.integer == GT_SINGLE_PLAYER ) { return BLERR_NOERROR; } // execute scheduled bot AI for ( i = 0; i < MAX_CLIENTS; i++ ) { if ( !botstates[i] || !botstates[i]->inuse ) { continue; } // Ridah if ( g_entities[i].r.svFlags & SVF_CASTAI ) { continue; } // done. // botstates[i]->botthink_residual += elapsed_time; // if ( botstates[i]->botthink_residual >= thinktime ) { botstates[i]->botthink_residual -= thinktime; if ( !trap_AAS_Initialized() ) { return BLERR_NOERROR; } if ( g_entities[i].client->pers.connected == CON_CONNECTED ) { BotAI( i, (float) thinktime / 1000 ); } } } // execute bot user commands every frame for ( i = 0; i < MAX_CLIENTS; i++ ) { if ( !botstates[i] || !botstates[i]->inuse ) { continue; } // Ridah if ( g_entities[i].r.svFlags & SVF_CASTAI ) { continue; } // done. if ( g_entities[i].client->pers.connected != CON_CONNECTED ) { continue; } BotUpdateInput( botstates[i], time ); trap_BotUserCommand( botstates[i]->client, &botstates[i]->lastucmd ); } return BLERR_NOERROR; }
/* ============== BotAISetupClient ============== */ int BotAISetupClient( int client, struct bot_settings_s *settings ) { char filename[MAX_PATH], name[MAX_PATH], gender[MAX_PATH]; bot_state_t *bs; int errnum; if ( !botstates[client] ) { botstates[client] = G_Alloc( sizeof( bot_state_t ) ); } bs = botstates[client]; if ( bs && bs->inuse ) { BotAI_Print( PRT_FATAL, "client %d already setup\n", client ); return qfalse; } if ( !trap_AAS_Initialized() ) { BotAI_Print( PRT_FATAL, "AAS not initialized\n" ); return qfalse; } //load the bot character bs->character = trap_BotLoadCharacter( settings->characterfile, settings->skill ); if ( !bs->character ) { BotAI_Print( PRT_FATAL, "couldn't load skill %f from %s\n", settings->skill, settings->characterfile ); return qfalse; } //copy the settings memcpy( &bs->settings, settings, sizeof( bot_settings_t ) ); //allocate a goal state bs->gs = trap_BotAllocGoalState( client ); //load the item weights trap_Characteristic_String( bs->character, CHARACTERISTIC_ITEMWEIGHTS, filename, MAX_PATH ); errnum = trap_BotLoadItemWeights( bs->gs, filename ); if ( errnum != BLERR_NOERROR ) { trap_BotFreeGoalState( bs->gs ); return qfalse; } //allocate a weapon state bs->ws = trap_BotAllocWeaponState(); //load the weapon weights trap_Characteristic_String( bs->character, CHARACTERISTIC_WEAPONWEIGHTS, filename, MAX_PATH ); errnum = trap_BotLoadWeaponWeights( bs->ws, filename ); if ( errnum != BLERR_NOERROR ) { trap_BotFreeGoalState( bs->gs ); trap_BotFreeWeaponState( bs->ws ); return qfalse; } //allocate a chat state bs->cs = trap_BotAllocChatState(); //load the chat file trap_Characteristic_String( bs->character, CHARACTERISTIC_CHAT_FILE, filename, MAX_PATH ); trap_Characteristic_String( bs->character, CHARACTERISTIC_CHAT_NAME, name, MAX_PATH ); errnum = trap_BotLoadChatFile( bs->cs, filename, name ); if ( errnum != BLERR_NOERROR ) { trap_BotFreeChatState( bs->cs ); trap_BotFreeGoalState( bs->gs ); trap_BotFreeWeaponState( bs->ws ); return qfalse; } //get the gender characteristic trap_Characteristic_String( bs->character, CHARACTERISTIC_GENDER, gender, MAX_PATH ); //set the chat gender if ( *gender == 'f' || *gender == 'F' ) { trap_BotSetChatGender( bs->cs, CHAT_GENDERFEMALE ); } else if ( *gender == 'm' || *gender == 'M' ) { trap_BotSetChatGender( bs->cs, CHAT_GENDERMALE ); } else { trap_BotSetChatGender( bs->cs, CHAT_GENDERLESS );} bs->inuse = qtrue; bs->client = client; bs->entitynum = client; bs->setupcount = 4; bs->entergame_time = trap_AAS_Time(); bs->ms = trap_BotAllocMoveState(); bs->walker = trap_Characteristic_BFloat( bs->character, CHARACTERISTIC_WALKER, 0, 1 ); numbots++; if ( trap_Cvar_VariableIntegerValue( "bot_testichat" ) ) { trap_BotLibVarSet( "bot_testichat", "1" ); BotChatTest( bs ); } //NOTE: reschedule the bot thinking BotScheduleBotThink(); // return qtrue; }
/* ================== BotAIStartFrame ================== */ int BotAIStartFrame(int time) { int i; gentity_t *ent; bot_entitystate_t state; int elapsed_time, thinktime; static int local_time; static int botlib_residual; static int lastbotthink_time; G_CheckBotSpawn(); trap_Cvar_Update(&bot_rocketjump); trap_Cvar_Update(&bot_grapple); trap_Cvar_Update(&bot_fastchat); trap_Cvar_Update(&bot_nochat); trap_Cvar_Update(&bot_testrchat); trap_Cvar_Update(&bot_thinktime); trap_Cvar_Update(&bot_memorydump); trap_Cvar_Update(&bot_saveroutingcache); trap_Cvar_Update(&bot_pause); trap_Cvar_Update(&bot_report); if (bot_report.integer) { // BotTeamplayReport(); // trap_Cvar_Set("bot_report", "0"); BotUpdateInfoConfigStrings(); } if (bot_pause.integer) { // execute bot user commands every frame for( i = 0; i < MAX_CLIENTS; i++ ) { if( !botstates[i] || !botstates[i]->inuse ) { continue; } if( g_entities[i].client->pers.connected != CON_CONNECTED ) { continue; } botstates[i]->lastucmd.forwardmove = 0; botstates[i]->lastucmd.rightmove = 0; botstates[i]->lastucmd.upmove = 0; botstates[i]->lastucmd.buttons = 0; botstates[i]->lastucmd.serverTime = time; trap_BotUserCommand(botstates[i]->client, &botstates[i]->lastucmd); } return qtrue; } if (bot_memorydump.integer) { trap_BotLibVarSet("memorydump", "1"); trap_Cvar_Set("bot_memorydump", "0"); } if (bot_saveroutingcache.integer) { trap_BotLibVarSet("saveroutingcache", "1"); trap_Cvar_Set("bot_saveroutingcache", "0"); } //check if bot interbreeding is activated BotInterbreeding(); //cap the bot think time if (bot_thinktime.integer > 200) { trap_Cvar_Set("bot_thinktime", "200"); } //if the bot think time changed we should reschedule the bots if (bot_thinktime.integer != lastbotthink_time) { lastbotthink_time = bot_thinktime.integer; BotScheduleBotThink(); } elapsed_time = time - local_time; local_time = time; botlib_residual += elapsed_time; if (elapsed_time > bot_thinktime.integer) thinktime = elapsed_time; else thinktime = bot_thinktime.integer; // update the bot library if ( botlib_residual >= thinktime ) { botlib_residual -= thinktime; trap_BotLibStartFrame((float) time / 1000); if (!trap_AAS_Initialized()) return qfalse; //update entities in the botlib for (i = 0; i < MAX_GENTITIES; i++) { ent = &g_entities[i]; if (!ent->inuse) { trap_BotLibUpdateEntity(i, NULL); continue; } if (!ent->r.linked) { trap_BotLibUpdateEntity(i, NULL); continue; } if (ent->r.svFlags & SVF_NOCLIENT) { trap_BotLibUpdateEntity(i, NULL); continue; } // do not update missiles if (ent->s.eType == ET_MISSILE && ent->s.weapon != WP_GRAPPLING_HOOK) { trap_BotLibUpdateEntity(i, NULL); continue; } // do not update event only entities if (ent->s.eType > ET_EVENTS) { trap_BotLibUpdateEntity(i, NULL); continue; } #if 1 //def MPACK // never link prox mine triggers if (ent->r.contents == CONTENTS_TRIGGER) { if (ent->touch == ProximityMine_Trigger) { trap_BotLibUpdateEntity(i, NULL); continue; } } #endif // memset(&state, 0, sizeof(bot_entitystate_t)); // VectorCopy(ent->r.currentOrigin, state.origin); if (i < MAX_CLIENTS) { VectorCopy(ent->s.apos.trBase, state.angles); } else { VectorCopy(ent->r.currentAngles, state.angles); } VectorCopy(ent->s.origin2, state.old_origin); VectorCopy(ent->r.mins, state.mins); VectorCopy(ent->r.maxs, state.maxs); state.type = ent->s.eType; state.flags = ent->s.eFlags; if (ent->r.bmodel) state.solid = SOLID_BSP; else state.solid = SOLID_BBOX; state.groundent = ent->s.groundEntityNum; state.modelindex = ent->s.modelindex; state.modelindex2 = ent->s.modelindex2; state.frame = ent->s.frame; state.event = ent->s.event; state.eventParm = ent->s.eventParm; state.powerups = ent->s.powerups; state.legsAnim = ent->s.legsAnim; state.torsoAnim = ent->s.torsoAnim; state.weapon = ent->s.weapon; // trap_BotLibUpdateEntity(i, &state); } BotAIRegularUpdate(); } floattime = trap_AAS_Time(); // execute scheduled bot AI for( i = 0; i < MAX_CLIENTS; i++ ) { if( !botstates[i] || !botstates[i]->inuse ) { continue; } // botstates[i]->botthink_residual += elapsed_time; // if ( botstates[i]->botthink_residual >= thinktime ) { botstates[i]->botthink_residual -= thinktime; if (!trap_AAS_Initialized()) return qfalse; if (g_entities[i].client->pers.connected == CON_CONNECTED) { BotAI(i, (float) thinktime / 1000); } } } // execute bot user commands every frame for( i = 0; i < MAX_CLIENTS; i++ ) { if( !botstates[i] || !botstates[i]->inuse ) { continue; } if( g_entities[i].client->pers.connected != CON_CONNECTED ) { continue; } BotUpdateInput(botstates[i], time, elapsed_time); trap_BotUserCommand(botstates[i]->client, &botstates[i]->lastucmd); } return qtrue; }
/* ============ AICast_Think entry point for all cast AI ============ */ void AICast_Think( int client, float thinktime ) { gentity_t *ent; cast_state_t *cs; int i; int animIndex; animation_t *anim; // if (saveGamePending || (strlen( g_missionStats.string ) > 2 )) { // return; // } // // get the cast ready for processing // cs = AICast_GetCastState( client ); ent = &g_entities[client]; // // make sure we are using the right AAS data for this entity (one's that don't get set will default to the player's AAS data) trap_AAS_SetCurrentWorld( cs->aasWorldIndex ); // // make sure we have a valid navigation system // if ( !trap_AAS_Initialized() ) { return; } // trap_EA_ResetInput( client, NULL ); cs->aiFlags &= ~AIFL_VIEWLOCKED; //cs->bs->weaponnum = ent->client->ps.weapon; // // turn off flags that are set each frame if needed ent->client->ps.eFlags &= ~( EF_NOSWINGANGLES | EF_MONSTER_EFFECT | EF_MONSTER_EFFECT2 | EF_MONSTER_EFFECT3 ); // conditional flags if ( ent->aiCharacter == AICHAR_ZOMBIE ) { if ( COM_BitCheck( ent->client->ps.weapons, WP_MONSTER_ATTACK1 ) ) { cs->aiFlags |= AIFL_NO_FLAME_DAMAGE; SET_FLAMING_ZOMBIE( ent->s, 1 ); } else { SET_FLAMING_ZOMBIE( ent->s, 0 ); } } // // if we're dead, do special stuff only if ( ent->health <= 0 || cs->revivingTime || cs->rebirthTime ) { // if ( cs->revivingTime && cs->revivingTime < level.time ) { // start us thinking again ent->client->ps.pm_type = PM_NORMAL; cs->revivingTime = 0; } // if ( cs->rebirthTime && cs->rebirthTime < level.time ) { vec3_t mins, maxs; int touch[10], numTouch; float oldmaxZ; oldmaxZ = ent->r.maxs[2]; // make sure the area is clear AIChar_SetBBox( ent, cs ); VectorAdd( ent->r.currentOrigin, ent->r.mins, mins ); VectorAdd( ent->r.currentOrigin, ent->r.maxs, maxs ); trap_UnlinkEntity( ent ); numTouch = trap_EntitiesInBox( mins, maxs, touch, 10 ); if ( numTouch ) { for ( i = 0; i < numTouch; i++ ) { //if (!g_entities[touch[i]].client || g_entities[touch[i]].r.contents == CONTENTS_BODY) if ( g_entities[touch[i]].r.contents & MASK_PLAYERSOLID ) { break; } } if ( i == numTouch ) { numTouch = 0; } } if ( numTouch == 0 ) { // ok to spawn // give them health when they start reviving, so we won't gib after // just a couple shots while reviving ent->health = ent->client->ps.stats[STAT_HEALTH] = ent->client->ps.stats[STAT_MAX_HEALTH] = ( ( cs->attributes[STARTING_HEALTH] - 50 ) > 30 ? ( cs->attributes[STARTING_HEALTH] - 50 ) : 30 ); ent->r.contents = CONTENTS_BODY; ent->clipmask = MASK_PLAYERSOLID; ent->takedamage = qtrue; ent->waterlevel = 0; ent->watertype = 0; ent->flags = 0; ent->die = AICast_Die; ent->client->ps.eFlags &= ~EF_DEAD; ent->s.eFlags &= ~EF_DEAD; cs->rebirthTime = 0; cs->deathTime = 0; // play the revive animation cs->revivingTime = level.time + BG_AnimScriptEvent( &ent->client->ps, ANIM_ET_REVIVE, qfalse, qtrue );; } else { // can't spawn yet, so set bbox back, and wait ent->r.maxs[2] = oldmaxZ; ent->client->ps.maxs[2] = ent->r.maxs[2]; } trap_LinkEntity( ent ); } // ZOMBIE should set effect flag if really dead if ( cs->aiCharacter == AICHAR_ZOMBIE && !ent->r.contents ) { ent->client->ps.eFlags |= EF_MONSTER_EFFECT2; } // if ( ent->health > GIB_HEALTH && cs->deathTime && cs->deathTime < ( level.time - 3000 ) ) { /* // been dead for long enough, set our animation to the end frame switch ( ent->s.legsAnim & ~ANIM_TOGGLEBIT ) { case BOTH_DEATH1: case BOTH_DEAD1: anim = BOTH_DEAD1; break; case BOTH_DEATH2: case BOTH_DEAD2: anim = BOTH_DEAD2; break; case BOTH_DEATH3: case BOTH_DEAD3: anim = BOTH_DEAD3; break; default: G_Error( "%s has unknown death animation\n", ent->classname); } ent->client->ps.torsoAnim = ( ( ent->client->ps.torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim; ent->client->ps.legsAnim = ( ( ent->client->ps.legsAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim; */ cs->deathTime = 0; ent->r.svFlags &= ~SVF_BROADCAST; } // // no more thinking required return; } // // set some anim conditions if ( cs->secondDeadTime ) { BG_UpdateConditionValue( cs->entityNum, ANIM_COND_SECONDLIFE, qtrue, qfalse ); } else { BG_UpdateConditionValue( cs->entityNum, ANIM_COND_SECONDLIFE, qfalse, qfalse ); } // set health value if ( ent->health <= 0.25 * cs->attributes[STARTING_HEALTH] ) { BG_UpdateConditionValue( cs->entityNum, ANIM_COND_HEALTH_LEVEL, 3, qfalse ); } else if ( ent->health <= 0.5 * cs->attributes[STARTING_HEALTH] ) { BG_UpdateConditionValue( cs->entityNum, ANIM_COND_HEALTH_LEVEL, 2, qfalse ); } else { BG_UpdateConditionValue( cs->entityNum, ANIM_COND_HEALTH_LEVEL, 1, qfalse ); } // cs->speedScale = 1.0; // reset each frame, set if required cs->actionFlags = 0; // FIXME: move this to a Cast AI movement init function! //retrieve the current client state BotAI_GetClientState( client, &( cs->bs->cur_ps ) ); // // setup movement speeds for the given state // walking animIndex = BG_GetAnimScriptAnimation( cs->entityNum, ent->client->ps.aiState, ANIM_MT_WALK ); if ( animIndex >= 0 ) { anim = BG_GetAnimationForIndex( cs->entityNum, animIndex ); cs->attributes[WALKING_SPEED] = anim->moveSpeed; } // crouching animIndex = BG_GetAnimScriptAnimation( cs->entityNum, ent->client->ps.aiState, ANIM_MT_WALKCR ); if ( animIndex >= 0 ) { anim = BG_GetAnimationForIndex( cs->entityNum, animIndex ); cs->attributes[CROUCHING_SPEED] = anim->moveSpeed; } // running animIndex = BG_GetAnimScriptAnimation( cs->entityNum, ent->client->ps.aiState, ANIM_MT_RUN ); if ( animIndex >= 0 ) { anim = BG_GetAnimationForIndex( cs->entityNum, animIndex ); cs->attributes[RUNNING_SPEED] = anim->moveSpeed; } // update crouch speed scale ent->client->ps.crouchSpeedScale = cs->attributes[CROUCHING_SPEED] / cs->attributes[RUNNING_SPEED]; // // only enable headlook if we want to this frame ent->client->ps.eFlags &= ~EF_HEADLOOK; if ( cs->bs->enemy >= 0 ) { ent->client->ps.eFlags &= ~EF_STAND_IDLE2; // never use alt idle if fighting } // // check for dead leader if ( cs->leaderNum >= 0 && g_entities[cs->leaderNum].health <= 0 ) { cs->leaderNum = -1; } // #if 0 // HACK for village2, if they are stuck, find a good position (there is a friendly guy placed inside a table) { trace_t tr; vec3_t org; trap_Trace( &tr, cs->bs->cur_ps.origin, cs->bs->cur_ps.mins, cs->bs->cur_ps.maxs, cs->bs->cur_ps.origin, cs->entityNum, CONTENTS_SOLID ); while ( tr.startsolid ) { VectorCopy( cs->bs->cur_ps.origin, org ); org[0] += 96 * crandom(); org[1] += 96 * crandom(); org[2] += 16 * crandom(); trap_Trace( &tr, org, cs->bs->cur_ps.mins, cs->bs->cur_ps.maxs, org, cs->entityNum, CONTENTS_SOLID ); G_SetOrigin( &g_entities[cs->entityNum], org ); VectorCopy( org, g_entities[cs->entityNum].client->ps.origin ); } } #endif //add the delta angles to the cast's current view angles for ( i = 0; i < 3; i++ ) { cs->bs->viewangles[i] = AngleMod( cs->bs->viewangles[i] + SHORT2ANGLE( cs->bs->cur_ps.delta_angles[i] ) ); } // //increase the local time of the cast cs->bs->ltime += thinktime; // cs->bs->thinktime = thinktime; //origin of the cast VectorCopy( cs->bs->cur_ps.origin, cs->bs->origin ); //eye coordinates of the cast VectorCopy( cs->bs->cur_ps.origin, cs->bs->eye ); cs->bs->eye[2] += cs->bs->cur_ps.viewheight; //get the area the cast is in cs->bs->areanum = BotPointAreaNum( cs->bs->origin ); // clear flags each frame cs->bs->flags = 0; // // check enemy health if ( cs->bs->enemy >= 0 && g_entities[cs->bs->enemy].health <= 0 ) { cs->bs->enemy = -1; } // // if the previous movetype was temporary, set it back if ( cs->movestateType == MSTYPE_TEMPORARY ) { cs->movestate = MS_DEFAULT; cs->movestateType = MSTYPE_NONE; } // crouching? if ( ( cs->bs->attackcrouch_time > trap_AAS_Time() ) && ( ( cs->lastAttackCrouch > level.time - 500 ) || ( cs->thinkFuncChangeTime < level.time - 1000 ) ) ) { // if we are not moving, and we are firing, always stand, unless we are allowed to crouch + fire if ( VectorLength( cs->bs->cur_ps.velocity ) || ( cs->lastWeaponFired < level.time - 2000 ) || ( cs->aiFlags & AIFL_ATTACK_CROUCH ) ) { cs->lastAttackCrouch = level.time; trap_EA_Crouch( cs->bs->client ); } } // //if (cs->bs->enemy >= 0) { //update the attack inventory values AICast_UpdateBattleInventory( cs, cs->bs->enemy ); //} // // if we don't have ammo for the current weapon, get rid of it if ( !( COM_BitCheck( cs->bs->cur_ps.weapons, cs->bs->weaponnum ) ) || !AICast_GotEnoughAmmoForWeapon( cs, cs->bs->weaponnum ) ) { // select a weapon AICast_ChooseWeapon( cs, qfalse ); // if still no ammo, select a blank weapon //if (!AICast_GotEnoughAmmoForWeapon( cs, cs->bs->weaponnum )) { // cs->bs->weaponnum = WP_NONE; //} } // // in query mode, we do special handling (pause scripting, check for transition to alert/combat, etc) if ( cs->aiState == AISTATE_QUERY ) { AICast_QueryThink( cs ); } else if ( cs->pauseTime < level.time ) { // do the thinking AICast_ProcessAIFunctions( cs, thinktime ); // // make sure the correct weapon is selected trap_EA_SelectWeapon( cs->bs->client, cs->bs->weaponnum ); // // process current script if it exists cs->castScriptStatusCurrent = cs->castScriptStatus; AICast_ScriptRun( cs, qfalse ); } // // set special movestate if necessary if ( cs->movestateType != MSTYPE_NONE ) { switch ( cs->movestate ) { case MS_WALK: cs->actionFlags |= CASTACTION_WALK; break; case MS_CROUCH: trap_EA_Crouch( cs->entityNum ); break; default: break; // TTimo gcc: MS_DEFAULT MS_RUN not handled in switch } } // //subtract the delta angles for ( i = 0; i < 3; i++ ) { cs->bs->viewangles[i] = AngleMod( cs->bs->viewangles[i] - SHORT2ANGLE( cs->bs->cur_ps.delta_angles[i] ) ); } }
/* * BotAISetupClient */ int BotAISetupClient(int client, struct bot_settings_s *settings, qbool restart) { char filename[MAX_PATH], name[MAX_PATH], gender[MAX_PATH]; bot_state_t *bs; int errnum; if(!botstates[client]) botstates[client] = G_Alloc(sizeof(bot_state_t)); bs = botstates[client]; if(bs && bs->inuse){ BotAI_Print(PRT_FATAL, "BotAISetupClient: client %d already setup\n", client); return qfalse; } if(!trap_AAS_Initialized()){ BotAI_Print(PRT_FATAL, "AAS not initialized\n"); return qfalse; } /* load the bot character */ bs->character = trap_BotLoadCharacter(settings->characterfile, settings->skill); if(!bs->character){ BotAI_Print(PRT_FATAL, "couldn't load skill %f from %s\n", settings->skill, settings->characterfile); return qfalse; } /* copy the settings */ memcpy(&bs->settings, settings, sizeof(bot_settings_t)); /* allocate a goal state */ bs->gs = trap_BotAllocGoalState(client); /* load the item weights */ trap_Characteristic_String(bs->character, CHARACTERISTIC_ITEMWEIGHTS, filename, MAX_PATH); errnum = trap_BotLoadItemWeights(bs->gs, filename); if(errnum != BLERR_NOERROR){ trap_BotFreeGoalState(bs->gs); return qfalse; } /* allocate a weapon state */ bs->ws = trap_BotAllocWeaponState(); /* load the weapon weights */ trap_Characteristic_String(bs->character, CHARACTERISTIC_WEAPONWEIGHTS, filename, MAX_PATH); errnum = trap_BotLoadWeaponWeights(bs->ws, filename); if(errnum != BLERR_NOERROR){ trap_BotFreeGoalState(bs->gs); trap_BotFreeWeaponState(bs->ws); return qfalse; } /* allocate a chat state */ bs->cs = trap_BotAllocChatState(); /* load the chat file */ trap_Characteristic_String(bs->character, CHARACTERISTIC_CHAT_FILE, filename, MAX_PATH); trap_Characteristic_String(bs->character, CHARACTERISTIC_CHAT_NAME, name, MAX_PATH); errnum = trap_BotLoadChatFile(bs->cs, filename, name); if(errnum != BLERR_NOERROR){ trap_BotFreeChatState(bs->cs); trap_BotFreeGoalState(bs->gs); trap_BotFreeWeaponState(bs->ws); return qfalse; } /* get the gender characteristic */ trap_Characteristic_String(bs->character, CHARACTERISTIC_GENDER, gender, MAX_PATH); /* set the chat gender */ if(*gender == 'f' || *gender == 'F') trap_BotSetChatGender( bs->cs, CHAT_GENDERFEMALE); else if(*gender == 'm' || *gender == 'M') trap_BotSetChatGender( bs->cs, CHAT_GENDERMALE); else trap_BotSetChatGender(bs->cs, CHAT_GENDERLESS); bs->inuse = qtrue; bs->client = client; bs->entitynum = client; bs->setupcount = 4; bs->entergame_time = FloatTime(); bs->ms = trap_BotAllocMoveState(); bs->walker = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_WALKER, 0, 1); numbots++; if(trap_cvargeti("bot_testichat")){ trap_BotLibVarSet("bot_testichat", "1"); BotChatTest(bs); } /* NOTE: reschedule the bot thinking */ BotScheduleBotThink(); /* if interbreeding start with a mutation */ if(bot_interbreed) trap_BotMutateGoalFuzzyLogic(bs->gs, 1); /* if we kept the bot client */ if(restart) BotReadSessionData(bs); /* bot has been setup succesfully */ return qtrue; }