/* * 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); } } }
/* ================== BotTeamplayReport ================== */ void BotTeamplayReport(void) { int i; char buf[MAX_INFO_STRING]; BotAI_Print(PRT_MESSAGE, S_COLOR_RED"RED\n"); for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { // if ( !botstates[i] || !botstates[i]->inuse ) continue; // trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); //if no config string or no name if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; //skip spectators if (atoi(Info_ValueForKey(buf, "t")) == TEAM_RED) { BotReportStatus(botstates[i]); } } BotAI_Print(PRT_MESSAGE, S_COLOR_BLUE"BLUE\n"); for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { // if ( !botstates[i] || !botstates[i]->inuse ) continue; // trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); //if no config string or no name if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; //skip spectators if (atoi(Info_ValueForKey(buf, "t")) == TEAM_BLUE) { BotReportStatus(botstates[i]); } } }
/* ================== BotTestAAS ================== */ void BotTestAAS(vec3_t origin) { int areanum; aas_areainfo_t info; if (bot_testsolid->integer) { if (!botlib_export->aas.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 (!botlib_export->aas.AAS_Initialized()) return; areanum = BotPointAreaNum(origin); if (!areanum) BotAI_Print(PRT_MESSAGE, "\r^1Solid! "); else { botlib_export->aas.AAS_AreaInfo(areanum, &info); BotAI_Print(PRT_MESSAGE, "\rarea %d, cluster %d ", areanum, info.cluster); } } }
//======================================================================== // // Parameter: - // Returns: - // Changes Globals: - //======================================================================== bot_character_t *BotCharacterFromHandle(int handle) { if (handle <= 0 || handle >= MAX_BOT_CHARACTERS) { BotAI_Print(PRT_FATAL, "character handle %d out of range\n", handle); return NULL; } //end if if (!botcharacters[handle].skill) { BotAI_Print(PRT_FATAL, "invalid character %d\n", handle); return NULL; } //end if return &botcharacters[handle]; } //end of the function BotCharacterFromHandle
//======================================================================== // // Parameter: - // Returns: - // Changes Globals: - //======================================================================== void BotFreeCharacter2(int handle) { if (handle <= 0 || handle >= MAX_BOT_CHARACTERS) { BotAI_Print(PRT_FATAL, "character handle %d out of range\n", handle); return; } //end if if (!botcharacters[handle].skill) { BotAI_Print(PRT_FATAL, "invalid character %d\n", handle); return; } //end if BotFreeCharacterStrings(&botcharacters[handle]); Com_Memset(&botcharacters[handle], 0, sizeof (bot_character_t)); } //end of the function BotFreeCharacter2
/* ============== AICast_ShutdownClient ============== */ int AICast_ShutdownClient(int client) { cast_state_t *cs; bot_state_t *bs; if (!(bs = botstates[client])) { return BLERR_NOERROR; } if (!bs->inuse) { BotAI_Print(PRT_ERROR, "client %d already shutdown\n", client); return BLERR_AICLIENTALREADYSHUTDOWN; } cs = AICast_GetCastState( client ); // memset( cs, 0, sizeof(cast_state_t) ); numcast--; // now do the other bot stuff #ifdef DEBUG // botai_import.DebugLineDelete(bs->debugline); #endif //DEBUG trap_BotFreeMoveState(bs->ms); //free the goal state trap_BotFreeGoalState(bs->gs); // //clear the bot state memset(bs, 0, sizeof(bot_state_t)); //set the inuse flag to qfalse bs->inuse = qfalse; //everything went ok return BLERR_NOERROR; }
/* ============== AICast_SetupClient ============== */ int AICast_SetupClient(int client) { cast_state_t *cs; bot_state_t *bs; if (!botstates[client]) { botstates[client] = G_Alloc(sizeof(bot_state_t)); memset( botstates[client], 0, sizeof(bot_state_t) ); } bs = botstates[client]; if (bs->inuse) { BotAI_Print(PRT_FATAL, "client %d already setup\n", client); return qfalse; } cs = AICast_GetCastState(client); cs->bs = bs; //allocate a goal state bs->gs = trap_BotAllocGoalState(client); bs->inuse = qtrue; bs->client = client; bs->entitynum = client; bs->setupcount = qtrue; bs->entergame_time = trap_AAS_Time(); bs->ms = trap_BotAllocMoveState(); return qtrue; }
/* ============== BotAimEnemySet Sets the bot's aim enemy to the inputted enemy and copies the inputted combat zone description for that enemy. If enemy is NULL, the bot's combat zone is instead reset to a default zone (and the input zone, which may be NULL, is ignored). "sighted" is the time at which the target was first sighted, or -1 if the target is not currently in line of sight. NOTE: The combat zone will get copied over even when the input enemy is the bot's current enemy, but additional fields will get reset when the input enemy is a change. So it's important to call this function when either the aim enemy or the enemy's combat zone changes. ============== */ void BotAimEnemySet(bot_state_t *bs, gentity_t *enemy, combat_zone_t *zone) { // If the aim enemy changed, update some related data if (bs->aim_enemy != enemy) { // Store the new enemy and their estimate health bs->aim_enemy = enemy; bs->enemy_health = 125; // Look up their last known movement decision if (enemy && enemy->client) ClientViewDir(enemy->client, bs->aim_enemy_move_dir); #ifdef DEBUG_AI if (bs->debug_flags & BOT_DEBUG_INFO_ENEMY) BotAI_Print(PRT_MESSAGE, "%s: Aim Enemy: %s\n", EntityNameFast(bs->ent), EntityNameFast(bs->aim_enemy)); #endif } // Update the enemy's combat zone if an enemy exists; otherwise use the last // enemy's zone as the default. // // NOTE: It's likely the bot will make shots after it kills an enemy because // the bots continue firing for a few milliseconds after they decide to stop. // This code will reset the aim enemy as soon as the target dies, but shots // will occur afterwards (and probably miss). Those misses should get applied // to the combat zone the enemy was in at the time of attack decision. if (enemy) memcpy(&bs->aim_zone, zone, sizeof(combat_zone_t)); }
//=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int Characteristic_Integer(int character, int index) { bot_character_t *ch; ch = BotCharacterFromHandle(character); if (!ch) return 0; //check if the index is in range if (!CheckCharacteristicIndex(character, index)) return 0; //an integer will just be returned if (ch->c[index].type == CT_INTEGER) { return ch->c[index].value.integer; } //end if //floats are casted to integers else if (ch->c[index].type == CT_FLOAT) { return (int) ch->c[index].value._float; } //end else if else { BotAI_Print(PRT_ERROR, "characteristic %d is not an integer\n", index); return 0; } //end else if // return 0; } //end of the function Characteristic_Integer
//=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== float Characteristic_Float(int character, int index) { bot_character_t *ch; ch = BotCharacterFromHandle(character); if (!ch) return 0; //check if the index is in range if (!CheckCharacteristicIndex(character, index)) return 0; //an integer will be converted to a float if (ch->c[index].type == CT_INTEGER) { return (float) ch->c[index].value.integer; } //end if //floats are just returned else if (ch->c[index].type == CT_FLOAT) { return ch->c[index].value._float; } //end else if //cannot convert a string pointer to a float else { BotAI_Print(PRT_ERROR, "characteristic %d is not a float\n", index); return 0; } //end else if // return 0; } //end of the function Characteristic_Float
/* ======================================================================================================================================= BotGPSToPosition ======================================================================================================================================= */ int BotGPSToPosition(char *buf, vec3_t position) { int i, j = 0; int num, sign; for (i = 0; i < 3; i++) { num = 0; while (buf[j] == ' ') j++; if (buf[j] == '-') { j++; sign = -1; } else { sign = 1; } while (buf[j]) { if (buf[j] >= '0' && buf[j] <= '9') { num = num * 10 + buf[j] - '0'; j++; } else { j++; break; } } BotAI_Print(PRT_MESSAGE, "%d\n", sign * num); position[i] = (float)sign * num; } return qtrue; }
//=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int CheckCharacteristicIndex(int character, int index) { bot_character_t *ch; ch = BotCharacterFromHandle(character); if (!ch) return qfalse; if (index < 0 || index >= MAX_CHARACTERISTICS) { BotAI_Print(PRT_ERROR, "characteristic %d does not exist\n", index); return qfalse; } //end if if (!ch->c[index].type) { BotAI_Print(PRT_ERROR, "characteristic %d is not initialized\n", index); return qfalse; } //end if return qtrue; } //end of the function CheckCharacteristicIndex
//=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int InterbreedFuzzySeperator_r(fuzzyseperator_t *fs1, fuzzyseperator_t *fs2, fuzzyseperator_t *fsout) { if (fs1->child) { if (!fs2->child || !fsout->child) { BotAI_Print(PRT_ERROR, "cannot interbreed weight configs, unequal child\n"); return qfalse; } //end if if (!InterbreedFuzzySeperator_r(fs2->child, fs2->child, fsout->child)) { return qfalse; } //end if } //end if else if (fs1->type == WT_BALANCE) { if (fs2->type != WT_BALANCE || fsout->type != WT_BALANCE) { BotAI_Print(PRT_ERROR, "cannot interbreed weight configs, unequal balance\n"); return qfalse; } //end if fsout->weight = (fs1->weight + fs2->weight) / 2; if (fsout->weight > fsout->maxweight) fsout->maxweight = fsout->weight; if (fsout->weight > fsout->minweight) fsout->minweight = fsout->weight; } //end else if if (fs1->next) { if (!fs2->next || !fsout->next) { BotAI_Print(PRT_ERROR, "cannot interbreed weight configs, unequal next\n"); return qfalse; } //end if if (!InterbreedFuzzySeperator_r(fs1->next, fs2->next, fsout->next)) { return qfalse; } //end if } //end if return qtrue; } //end of the function InterbreedFuzzySeperator_r
/* ================== BotMatchMessage ================== */ int BotMatchMessage(bot_state_t *bs, char *message) { bot_match_t match; match.type = 0; //if it is an unknown message if (!trap_BotFindMatch(message, &match, MTCONTEXT_MISC|MTCONTEXT_INITIALTEAMCHAT )) { if(bot_developer.integer & AIDBG_CHAT ){ G_Printf("^2no match for ^1%s\n", message); } return qfalse; } if(bot_developer.integer & AIDBG_CHAT){ G_Printf("^6match %d for^1 %s\n", match.type, message); } //react to the found message switch(match.type) { case MSG_WRONGWALL:{ BotMatch_WrongWall(bs, &match); break; } //case MSG_GOFORBALLOON:{ //someone calling for company // BotMatch_GoForBalloon(bs, &match); // break; //} case MSG_DROPCART:{ BotMatch_DropCart(bs, &match); break; } case MSG_GETITEM:{ BotMatch_GetItem(bs, &match); break; } case MSG_ENTERGAME:{ //someone entered the game BotMatch_EnterGame(bs, &match); break; } case MSG_CATCHME:{ BotMatch_CatchMe(bs, &match); break; } default:{ if(bot_developer.integer) BotAI_Print(PRT_MESSAGE, "unknown match type %d\n",match.type); break; } } return qtrue; }
/* ================== Svcmd_BotTeamplayReport_f ================== */ void Svcmd_BotTeamplayReport_f(void) { int i; if (!bot_report.integer) { BotAI_Print(PRT_MESSAGE, "Must set bot_report 1 before using botreport command.\n"); return; } if (gametype >= GT_TEAM) { BotAI_Print(PRT_MESSAGE, S_COLOR_RED"RED\n"); for (i = 0; i < level.maxplayers; i++) { // if ( !botstates[i] || !botstates[i]->inuse ) continue; // if (BotTeam(botstates[i]) == TEAM_RED) { BotReportStatus(botstates[i]); } } BotAI_Print(PRT_MESSAGE, S_COLOR_BLUE"BLUE\n"); for (i = 0; i < level.maxplayers; i++) { // if ( !botstates[i] || !botstates[i]->inuse ) continue; // if (BotTeam(botstates[i]) == TEAM_BLUE) { BotReportStatus(botstates[i]); } } } else { for (i = 0; i < level.maxplayers; i++) { // if ( !botstates[i] || !botstates[i]->inuse ) continue; // BotReportStatus(botstates[i]); } } }
/* =============== BotMatchMessage This function returns if the message could be matched. It does not mean the bot actually processed the message. NOTE: Death messages are sent as an EV_OBITUARY event, not actual console messages. As such, they are processed by BotCheckEvents() in ai_scan.c. =============== */ qboolean BotMatchMessage(bot_state_t *bs, char *message) { bot_match_t match; char name[MAX_MESSAGE_SIZE]; gentity_t *sender; // Try to match this message as a CTF teamchat message match.type = 0; if (!trap_BotFindMatch(message, &match, MTCONTEXT_MISC | MTCONTEXT_INITIALTEAMCHAT | MTCONTEXT_CTF)) return qfalse; // Ignore messages in deathmatch modes, but return true because it's a real message if ( !(game_style & GS_TEAM) ) return qtrue; // Check if this message is a team management message trap_BotMatchVariable(&match, NETNAME, name, sizeof(name)); sender = TeammateFromName(bs, name); if (BotMatch_Team(bs, &match, sender)) return qtrue; // Ignore messages not from a teammate if (!sender) { Bot_InitialChat(bs, "whois", name, NULL); trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); return qtrue; } // Ignore other messages if they aren't intended for this bot if (!BotAddresseeMatch(bs, &match)) return qtrue; // Check if this message is an order if (BotMatch_Order(bs, &match, sender)) return qtrue; // Check if this message is a subteam request if (BotMatch_Subteam(bs, &match, sender)) return qtrue; // Still return true because the message matched-- our code just elected not to process it BotAI_Print(PRT_WARNING, "Unknown match type %i\n", match.type); return qtrue; }
//=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int Characteristic_BInteger(int character, int index, int min, int max) { int value; bot_character_t *ch; ch = BotCharacterFromHandle(character); if (!ch) return 0; if (min > max) { BotAI_Print(PRT_ERROR, "cannot bound characteristic %d between %d and %d\n", index, min, max); return 0; } //end if value = Characteristic_Integer(character, index); if (value < min) return min; if (value > max) return max; return value; } //end of the function Characteristic_BInteger
//=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int BotLoadCharacter(char *charfile, float skill) { int firstskill, secondskill, handle; //make sure the skill is in the valid range if (skill < 1.0) skill = 1.0; else if (skill > 5.0) skill = 5.0; //skill 1, 4 and 5 should be available in the character files if (skill == 1.0 || skill == 4.0 || skill == 5.0) { return BotLoadCharacterSkill(charfile, skill); } //end if //check if there's a cached skill handle = BotFindCachedCharacter(charfile, skill); if (handle) { BotAI_Print(PRT_DEVELOPER, "loaded cached skill %f from %s\n", skill, charfile); return handle; } //end if if (skill < 4.0) { //load skill 1 and 4 firstskill = BotLoadCharacterSkill(charfile, 1); if (!firstskill) return 0; secondskill = BotLoadCharacterSkill(charfile, 4); if (!secondskill) return firstskill; } //end if else { //load skill 4 and 5 firstskill = BotLoadCharacterSkill(charfile, 4); if (!firstskill) return 0; secondskill = BotLoadCharacterSkill(charfile, 5); if (!secondskill) return firstskill; } //end else //interpolate between the two skills handle = BotInterpolateCharacters(firstskill, secondskill, skill); if (!handle) return 0; #if 0 // ZTM: FIXME: add new bot logfile for game to write to? //write the character to the log file BotDumpCharacter(&botcharacters[handle]); #endif // return handle; } //end of the function BotLoadCharacter
/* ================= BotCreateWaypoint Creates a new waypoint with the inputted name. This function's caller must set up the waypoint's goal. ================= */ bot_waypoint_t *BotCreateWaypoint(char *name) { bot_waypoint_t *wp; // Make sure a new waypoint can be allocated wp = botai_freewaypoints; if (!wp) { BotAI_Print(PRT_WARNING, "BotCreateWaypoint: Out of waypoints\n"); return NULL; } botai_freewaypoints = botai_freewaypoints->next; // Set all waypoint information except the goal Q_strncpyz( wp->name, name, sizeof(wp->name) ); wp->next = NULL; wp->prev = NULL; return wp; }
//=========================================================================== // config1 and config2 are interbreeded and stored in configout // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void InterbreedWeightConfigs(weightconfig_t *config1, weightconfig_t *config2, weightconfig_t *configout) { int i; if (config1->numweights != config2->numweights || config1->numweights != configout->numweights) { BotAI_Print(PRT_ERROR, "cannot interbreed weight configs, unequal numweights\n"); return; } //end if for (i = 0; i < config1->numweights; i++) { InterbreedFuzzySeperator_r(config1->weights[i].firstseperator, config2->weights[i].firstseperator, configout->weights[i].firstseperator); } //end for } //end of the function InterbreedWeightConfigs
//=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void Characteristic_String(int character, int index, char *buf, int size) { bot_character_t *ch; ch = BotCharacterFromHandle(character); if (!ch) return; //check if the index is in range if (!CheckCharacteristicIndex(character, index)) return; //an integer will be converted to a float if (ch->c[index].type == CT_STRING) { strncpy(buf, ch->c[index].value.string, size-1); buf[size-1] = '\0'; } //end if else { BotAI_Print(PRT_ERROR, "characteristic %d is not a string\n", index); } //end else if } //end of the function Characteristic_String
/* ================== BotReportStatus ================== */ void BotReportStatus(bot_state_t *bs) { char buf[MAX_INFO_STRING]; char netname[MAX_MESSAGE_SIZE]; char leader[MAX_MESSAGE_SIZE]; char carrying[MAX_MESSAGE_SIZE]; char action[MAX_MESSAGE_SIZE]; char node[MAX_MESSAGE_SIZE]; trap_GetConfigstring(CS_BOTINFO+bs->playernum, buf, sizeof(buf)); if (!*buf) { return; } PlayerName(bs->playernum, netname, sizeof(netname)); Q_strncpyz( leader, Info_ValueForKey(buf, "l"), sizeof(leader) ); Q_strncpyz( carrying, Info_ValueForKey(buf, "c"), sizeof(carrying) ); Q_strncpyz( action, Info_ValueForKey(buf, "a"), sizeof(action) ); Q_strncpyz( node, Info_ValueForKey(buf, "n"), sizeof(node) ); BotAI_Print(PRT_MESSAGE, "%-20s%-1s%-2s: %s (%s)\n", netname, leader, carrying, action, node); }
/* ============== BotEntityInfo ============== */ void BotEntityInfo(int entnum, aas_entityinfo_t *info) { gentity_t *ent; if (entnum < 0 || entnum >= level.num_entities) { BotAI_Print(PRT_FATAL, "BotEntityInfo: entnum %d out of range\n", entnum); Com_Memset(info, 0, sizeof(aas_entityinfo_t)); return; } //end if ent = &g_entities[entnum]; info->valid = ent->botvalid; info->type = ent->s.eType; info->flags = ent->s.eFlags; info->update_time = ent->update_time; info->ltime = ent->ltime; info->number = entnum; VectorCopy(ent->visorigin, info->origin); VectorCopy(ent->lastAngles, info->angles); VectorCopy(ent->lastvisorigin, info->lastvisorigin); VectorCopy(ent->s.mins, info->mins); VectorCopy(ent->s.maxs, info->maxs); info->groundent = ent->s.groundEntityNum; if (ent->s.collisionType == CT_SUBMODEL) info->solid = SOLID_BSP; else info->solid = SOLID_BBOX; info->modelindex = ent->s.modelindex; info->modelindex2 = ent->s.modelindex2; info->frame = ent->s.frame; info->event = ent->s.event; info->eventParm = ent->s.eventParm; info->powerups = ent->s.powerups; info->weapon = ent->s.weapon; info->legsAnim = ent->s.legsAnim; info->torsoAnim = ent->s.torsoAnim; }
/* ============== BotAI ============== */ int BotAI(int playernum, float thinktime) { bot_state_t *bs; char buf[1024], *args; int j; EA_ResetInput(playernum); // bs = botstates[playernum]; if (!bs || !bs->inuse) { BotAI_Print(PRT_FATAL, "BotAI: player %d is not setup\n", playernum); return qfalse; } //retrieve the current player state if (!BotAI_GetPlayerState( playernum, &bs->cur_ps )) { BotAI_Print(PRT_FATAL, "BotAI: failed to get player state for player %d\n", playernum); return qfalse; } //retrieve any waiting server commands while( trap_BotGetServerCommand(playernum, buf, sizeof(buf)) ) { //have buf point to the command and args to the command arguments args = strchr( buf, ' '); if (!args) continue; *args++ = '\0'; //remove color espace sequences from the arguments RemoveColorEscapeSequences( args ); if (!Q_stricmp(buf, "cp ")) { /*CenterPrintf*/ } else if (!Q_stricmp(buf, "cs")) { /*ConfigStringModified*/ } else if (!Q_stricmp(buf, "print")) { //remove first and last quote from the chat message memmove(args, args+1, strlen(args)); args[strlen(args)-1] = '\0'; BotQueueConsoleMessage(bs->cs, CMS_NORMAL, args); } else if (!Q_stricmp(buf, "chat") || !Q_stricmp(buf, "tell")) { //remove first and last quote from the chat message memmove(args, args+1, strlen(args)); args[strlen(args)-1] = '\0'; BotQueueConsoleMessage(bs->cs, CMS_CHAT, args); } else if (!Q_stricmp(buf, "tchat")) { //remove first and last quote from the chat message memmove(args, args+1, strlen(args)); args[strlen(args)-1] = '\0'; BotQueueConsoleMessage(bs->cs, CMS_CHAT, args); } #ifdef MISSIONPACK else if (!Q_stricmp(buf, "vchat")) { BotVoiceChatCommand(bs, SAY_ALL, args); } else if (!Q_stricmp(buf, "vtchat")) { BotVoiceChatCommand(bs, SAY_TEAM, args); } else if (!Q_stricmp(buf, "vtell")) { BotVoiceChatCommand(bs, SAY_TELL, args); } #endif else if (!Q_stricmp(buf, "scores")) { /*FIXME: parse scores?*/ } else if (!Q_stricmp(buf, "clientLevelShot")) { /*ignore*/ } } //add the delta angles to the bot's current view angles for (j = 0; j < 3; j++) { bs->viewangles[j] = AngleMod(bs->viewangles[j] + SHORT2ANGLE(bs->cur_ps.delta_angles[j])); } //increase the local time of the bot bs->ltime += thinktime; // bs->thinktime = thinktime; //origin of the bot VectorCopy(bs->cur_ps.origin, bs->origin); //eye coordinates of the bot VectorCopy(bs->cur_ps.origin, bs->eye); bs->eye[2] += bs->cur_ps.viewheight; //get the area the bot is in bs->areanum = BotPointAreaNum(bs->origin); //the real AI BotDeathmatchAI(bs, thinktime); //set the weapon selection every AI frame EA_SelectWeapon(bs->playernum, bs->weaponnum); //subtract the delta angles for (j = 0; j < 3; j++) { bs->viewangles[j] = AngleMod(bs->viewangles[j] - SHORT2ANGLE(bs->cur_ps.delta_angles[j])); } //everything was ok 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; }
/* ============== 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; }
/* ============== BotAI ============== */ int BotAI( int client, float thinktime ) { bot_state_t *bs; char buf[1024], *args; int j; trap_EA_ResetInput( client, NULL ); // bs = botstates[client]; if ( !bs || !bs->inuse ) { BotAI_Print( PRT_FATAL, "client %d hasn't been setup\n", client ); return BLERR_AICLIENTNOTSETUP; } //retrieve the current client state BotAI_GetClientState( client, &bs->cur_ps ); //retrieve any waiting console messages while ( trap_BotGetServerCommand( client, buf, sizeof( buf ) ) ) { //have buf point to the command and args to the command arguments args = strchr( buf, ' ' ); if ( !args ) { continue; } *args++ = '\0'; //remove color espace sequences from the arguments Q_CleanStr( args ); //botai_import.Print(PRT_MESSAGE, "ConsoleMessage: \"%s\"\n", buf); if ( !Q_stricmp( buf, "cp " ) ) { /*CenterPrintf*/ } else if ( !Q_stricmp( buf, "cs" ) ) { /*ConfigStringModified*/ } else if ( !Q_stricmp( buf, "print" ) ) { trap_BotQueueConsoleMessage( bs->cs, CMS_NORMAL, args ); } else if ( !Q_stricmp( buf, "chat" ) ) { trap_BotQueueConsoleMessage( bs->cs, CMS_CHAT, args ); } else if ( !Q_stricmp( buf, "tchat" ) ) { trap_BotQueueConsoleMessage( bs->cs, CMS_CHAT, args ); } else if ( !Q_stricmp( buf, "scores" ) ) { /*FIXME: parse scores?*/ } else if ( !Q_stricmp( buf, "clientLevelShot" ) ) { /*ignore*/ } } //add the delta angles to the bot's current view angles for ( j = 0; j < 3; j++ ) { bs->viewangles[j] = AngleMod( bs->viewangles[j] + SHORT2ANGLE( bs->cur_ps.delta_angles[j] ) ); } //increase the local time of the bot bs->ltime += thinktime; // bs->thinktime = thinktime; //origin of the bot VectorCopy( bs->cur_ps.origin, bs->origin ); //eye coordinates of the bot VectorCopy( bs->cur_ps.origin, bs->eye ); bs->eye[2] += bs->cur_ps.viewheight; //get the area the bot is in bs->areanum = BotPointAreaNum( bs->origin ); //the real AI BotDeathmatchAI( bs, thinktime ); //set the weapon selection every AI frame trap_EA_SelectWeapon( bs->client, bs->weaponnum ); //subtract the delta angles for ( j = 0; j < 3; j++ ) { bs->viewangles[j] = AngleMod( bs->viewangles[j] - SHORT2ANGLE( bs->cur_ps.delta_angles[j] ) ); } //everything was ok return BLERR_NOERROR; }
/* ======================================================================================================================================= BotPrintTeamGoal ======================================================================================================================================= */ void BotPrintTeamGoal(bot_state_t *bs) { char netname[MAX_NETNAME]; float t; ClientName(bs->client, netname, sizeof(netname)); t = bs->teamgoal_time - trap_AAS_Time(); switch (bs->ltgtype) { case LTG_TEAMHELP: { BotAI_Print(PRT_MESSAGE, "%s: I'm gonna help a team mate for %1.0f secs\n", netname, t); break; } case LTG_TEAMACCOMPANY: { BotAI_Print(PRT_MESSAGE, "%s: I'm gonna accompany a team mate for %1.0f secs\n", netname, t); break; } case LTG_GETFLAG: { BotAI_Print(PRT_MESSAGE, "%s: I'm gonna get the flag for %1.0f secs\n", netname, t); break; } case LTG_RUSHBASE: { BotAI_Print(PRT_MESSAGE, "%s: I'm gonna rush to the base for %1.0f secs\n", netname, t); break; } case LTG_RETURNFLAG: { BotAI_Print(PRT_MESSAGE, "%s: I'm gonna try to return the flag for %1.0f secs\n", netname, t); break; } case LTG_DEFENDKEYAREA: { BotAI_Print(PRT_MESSAGE, "%s: I'm gonna defend a key area for %1.0f secs\n", netname, t); break; } case LTG_GETITEM: { BotAI_Print(PRT_MESSAGE, "%s: I'm gonna get an item for %1.0f secs\n", netname, t); break; } case LTG_KILL: { BotAI_Print(PRT_MESSAGE, "%s: I'm gonna kill someone for %1.0f secs\n", netname, t); break; } case LTG_CAMP: case LTG_CAMPORDER: { BotAI_Print(PRT_MESSAGE, "%s: I'm gonna camp for %1.0f secs\n", netname, t); break; } case LTG_PATROL: { BotAI_Print(PRT_MESSAGE, "%s: I'm gonna patrol for %1.0f secs\n", netname, t); break; } default: { if (bs->ctfroam_time > trap_AAS_Time()) { t = bs->ctfroam_time - trap_AAS_Time(); BotAI_Print(PRT_MESSAGE, "%s: I'm gonna roam for %1.0f secs\n", netname, t); } else { BotAI_Print(PRT_MESSAGE, "%s: I've got a regular goal\n", netname); } } } }
//=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== weightconfig_t *ReadWeightConfig(char *filename) { int newindent, avail = 0, n; pc_token_t token; int source; fuzzyseperator_t *fs; weightconfig_t *config = NULL; int starttime; starttime = trap_Milliseconds(); if (!bot_reloadcharacters.integer) { avail = -1; for( n = 0; n < MAX_WEIGHT_FILES; n++ ) { config = weightFileList[n]; if( !config ) { if( avail == -1 ) { avail = n; } //end if continue; } //end if if( strcmp( filename, config->filename ) == 0 ) { //BotAI_Print( PRT_MESSAGE, "retained %s\n", filename ); return config; } //end if } //end for if( avail == -1 ) { BotAI_Print( PRT_ERROR, "weightFileList was full trying to load %s\n", filename ); return NULL; } //end if } //end if source = trap_PC_LoadSource(filename, BOTFILESBASEFOLDER); if (!source) { BotAI_Print(PRT_ERROR, "counldn't load %s\n", filename); return NULL; } //end if // config = (weightconfig_t *) trap_HeapMalloc(sizeof(weightconfig_t)); config->numweights = 0; Q_strncpyz( config->filename, filename, sizeof(config->filename) ); //parse the item config file while(trap_PC_ReadToken(source, &token)) { if (!strcmp(token.string, "weight")) { if (config->numweights >= MAX_WEIGHTS) { PC_SourceWarning(source, "too many fuzzy weights"); break; } //end if if (!PC_ExpectTokenType(source, TT_STRING, 0, &token)) { FreeWeightConfig(config); trap_PC_FreeSource(source); return NULL; } //end if config->weights[config->numweights].name = (char *) trap_HeapMalloc(strlen(token.string) + 1); strcpy(config->weights[config->numweights].name, token.string); if (!PC_ExpectAnyToken(source, &token)) { FreeWeightConfig(config); trap_PC_FreeSource(source); return NULL; } //end if newindent = qfalse; if (!strcmp(token.string, "{")) { newindent = qtrue; if (!PC_ExpectAnyToken(source, &token)) { FreeWeightConfig(config); trap_PC_FreeSource(source); return NULL; } //end if } //end if if (!strcmp(token.string, "switch")) { fs = ReadFuzzySeperators_r(source); if (!fs) { FreeWeightConfig(config); trap_PC_FreeSource(source); return NULL; } //end if config->weights[config->numweights].firstseperator = fs; } //end if else if (!strcmp(token.string, "return")) { fs = (fuzzyseperator_t *) trap_HeapMalloc(sizeof(fuzzyseperator_t)); fs->index = 0; fs->value = MAX_INVENTORYVALUE; fs->next = NULL; fs->child = NULL; if (!ReadFuzzyWeight(source, fs)) { trap_HeapFree(fs); FreeWeightConfig(config); trap_PC_FreeSource(source); return NULL; } //end if config->weights[config->numweights].firstseperator = fs; } //end else if else { PC_SourceError(source, "invalid name %s", token.string); FreeWeightConfig(config); trap_PC_FreeSource(source); return NULL; } //end else if (newindent) { if (!PC_ExpectTokenString(source, "}")) { FreeWeightConfig(config); trap_PC_FreeSource(source); return NULL; } //end if } //end if config->numweights++; } //end if else { PC_SourceError(source, "invalid name %s", token.string); FreeWeightConfig(config); trap_PC_FreeSource(source); return NULL; } //end else } //end while //free the source at the end of a pass trap_PC_FreeSource(source); //if the file was located in a pak file BotAI_Print(PRT_DEVELOPER, "loaded %s\n", filename); BotAI_Print(PRT_DEVELOPER, "weights loaded in %d msec\n", trap_Milliseconds() - starttime); // if (!bot_reloadcharacters.integer) { weightFileList[avail] = config; } //end if // return config; } //end of the function ReadWeightConfig
/* ================== BotReportStatus ================== */ void BotReportStatus(bot_state_t *bs) { char goalname[MAX_MESSAGE_SIZE]; char netname[MAX_MESSAGE_SIZE]; char *leader, flagstatus[32]; // ClientName(bs->client, netname, sizeof(netname)); if (Q_stricmp(netname, bs->teamleader) == 0) leader = "L"; else leader = " "; strcpy(flagstatus, " "); if (gametype == GT_CTF) { if (BotCTFCarryingFlag(bs)) { if (BotTeam(bs) == TEAM_RED) strcpy(flagstatus, S_COLOR_RED"F "); else strcpy(flagstatus, S_COLOR_BLUE"F "); } } #if 1 //def MPACK else if (gametype == GT_1FCTF) { if (Bot1FCTFCarryingFlag(bs)) { if (BotTeam(bs) == TEAM_RED) strcpy(flagstatus, S_COLOR_RED"F "); else strcpy(flagstatus, S_COLOR_BLUE"F "); } } else if (gametype == GT_HARVESTER) { if (BotHarvesterCarryingCubes(bs)) { if (BotTeam(bs) == TEAM_RED) Com_sprintf(flagstatus, sizeof(flagstatus), S_COLOR_RED"%2d", bs->inventory[INVENTORY_REDCUBE]); else Com_sprintf(flagstatus, sizeof(flagstatus), S_COLOR_BLUE"%2d", bs->inventory[INVENTORY_BLUECUBE]); } } #endif switch(bs->ltgtype) { case LTG_TEAMHELP: { EasyClientName(bs->teammate, goalname, sizeof(goalname)); BotAI_Print(PRT_MESSAGE, "%-20s%s%s: helping %s\n", netname, leader, flagstatus, goalname); break; } case LTG_TEAMACCOMPANY: { EasyClientName(bs->teammate, goalname, sizeof(goalname)); BotAI_Print(PRT_MESSAGE, "%-20s%s%s: accompanying %s\n", netname, leader, flagstatus, goalname); break; } case LTG_DEFENDKEYAREA: { trap_BotGoalName(bs->teamgoal.number, goalname, sizeof(goalname)); BotAI_Print(PRT_MESSAGE, "%-20s%s%s: defending %s\n", netname, leader, flagstatus, goalname); break; } case LTG_GETITEM: { trap_BotGoalName(bs->teamgoal.number, goalname, sizeof(goalname)); BotAI_Print(PRT_MESSAGE, "%-20s%s%s: getting item %s\n", netname, leader, flagstatus, goalname); break; } case LTG_KILL: { ClientName(bs->teamgoal.entitynum, goalname, sizeof(goalname)); BotAI_Print(PRT_MESSAGE, "%-20s%s%s: killing %s\n", netname, leader, flagstatus, goalname); break; } case LTG_CAMP: case LTG_CAMPORDER: { BotAI_Print(PRT_MESSAGE, "%-20s%s%s: camping\n", netname, leader, flagstatus); break; } case LTG_PATROL: { BotAI_Print(PRT_MESSAGE, "%-20s%s%s: patrolling\n", netname, leader, flagstatus); break; } case LTG_GETFLAG: { BotAI_Print(PRT_MESSAGE, "%-20s%s%s: capturing flag\n", netname, leader, flagstatus); break; } case LTG_RUSHBASE: { BotAI_Print(PRT_MESSAGE, "%-20s%s%s: rushing base\n", netname, leader, flagstatus); break; } case LTG_RETURNFLAG: { BotAI_Print(PRT_MESSAGE, "%-20s%s%s: returning flag\n", netname, leader, flagstatus); break; } case LTG_ATTACKENEMYBASE: { BotAI_Print(PRT_MESSAGE, "%-20s%s%s: attacking the enemy base\n", netname, leader, flagstatus); break; } case LTG_HARVEST: { BotAI_Print(PRT_MESSAGE, "%-20s%s%s: harvesting\n", netname, leader, flagstatus); break; } default: { BotAI_Print(PRT_MESSAGE, "%-20s%s%s: roaming\n", netname, leader, flagstatus); break; } } }