/* Copyright (C) 1997-2001 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -------------------------------------------------------------- The ACE Bot is a product of Steve Yeager, and is available from the ACE Bot homepage, at http://www.axionfx.com/ace. This program is a modification of the ACE Bot, and is therefore in NO WAY supported by Steve Yeager. */ #include "../g_local.h" #include "ai_local.h" #include "g_fakeclient.h" //=============================================================== // // BOT SPAWN // //=============================================================== //------------------------------------------------------------ //freedom to add as many skins & names as wished, but be //careful. LocalBotSkins & LocalBotNames MUST fit each other. //jalfixme: code a botlist.cfg to be read from disk? //------------------------------------------------------------ /* static char *LocalBotSkins[] = { "major/daemia", "major/default", "xaero/default", "lucy/default", "hunter/default", "hunter/harpy", "grunt/stripe", "grunt/default", "razor/default", "razor/id", "ranger/default", "ranger/wrack", "visor/default", "visor/gorre", "sarge/default", "sarge/roderic", "doom/default", NULL };*/ // JALFIXME : we only have 1 skin now, so I use invalid names so the randomizer check works // and force the default one later on static char *LocalBotSkins[] = { "viciious/default", "viciious/default2", "viciious/default3", "viciious/default4", "viciious/default5", "monada/default", "monada/default1", "monada/default2", "monada/default3", "monada/default4", "silverclaw/default5", "silverclaw/default1", "silverclaw/default2", "silverclaw/default3", "silverclaw/default4", NULL }; static char *LocalBotNames[] = { "Viciious", "Sid", "Pervert", "Sick", "Punk", "Blask Sister", "Monada", "Afrodita", "Goddess", "Athena", "Silver", "Cathy", "MishiMishi", "Lobita", "SisterClaw", NULL }; //------------------------------------------------------------ /* static char *LocalBotNames[] = { "Daemia", "Major", "Xaero", "Lucy", "Hunter", "Harpy", "Stripe", "Grunt", "Razor", "id", "Ranger", "Wrack", "Visor", "Gorre", "Sarge", "Roderic", "Doom", NULL }; */ //------------------------------------------------------------ typedef struct { char bot_model[MAX_INFO_STRING]; char bot_skin[MAX_INFO_STRING]; char bot_name[MAX_INFO_STRING]; } localbotskin_t; //========================================== // BOT_GetUnusedSkin // Retrieve a random unused skin & name //========================================== qboolean BOT_GetUnusedSkin( char *bot_model, char *bot_skin, char *bot_name ) { qboolean inuse; int skinnumber; char scratch[MAX_INFO_STRING]; int i, freeskins; edict_t *ent; localbotskin_t *botskins; localbotskin_t *localbotskin; //count the unused skins, and make sure there is at least 1 of them skinnumber = freeskins = 0; while( LocalBotSkins[skinnumber] != NULL ) { inuse = qfalse; for( i = 0, ent = game.edicts + 1; i < game.maxclients; i++, ent++ ) { if (!(ent->r.svflags & SVF_FAKECLIENT) || !ent->r.client) continue; //scratch = Info_ValueForKey (ent->r.client->pers.userinfo, "skin"); Q_snprintfz( scratch, sizeof(scratch), "%s/%s", Info_ValueForKey(ent->r.client->pers.userinfo, "model"), Info_ValueForKey(ent->r.client->pers.userinfo, "skin") ); if( !Q_stricmp( scratch, LocalBotSkins[skinnumber]) ) { inuse = qtrue; break; } } if( inuse == qfalse ) freeskins++; skinnumber++; } //fallback to old method if (!freeskins) return qfalse; //assign tmp memory for storing unused skins botskins = G_Malloc( sizeof(localbotskin_t) * freeskins ); //create a list of unused skins skinnumber = freeskins = 0; while( LocalBotSkins[skinnumber] != NULL ) { inuse = qfalse; for( i = 0, ent = game.edicts + 1; i < game.maxclients; i++, ent++ ) { if( !(ent->r.svflags & SVF_FAKECLIENT) || !ent->r.client ) continue; //scratch = Info_ValueForKey (ent->r.client->pers.userinfo, "skin"); Q_snprintfz( scratch, sizeof(scratch), "%s/%s", Info_ValueForKey(ent->r.client->pers.userinfo, "model"), Info_ValueForKey(ent->r.client->pers.userinfo, "skin") ); if( !Q_stricmp( scratch, LocalBotSkins[skinnumber]) ) { inuse = qtrue; break; } } //store and advance if (inuse == qfalse) { char *p; localbotskin = botskins + freeskins; p = strstr( LocalBotSkins[skinnumber], "/" ); if( !strlen(p) ) continue; p++; Q_strncpyz( localbotskin->bot_model, LocalBotSkins[skinnumber], strlen(LocalBotSkins[skinnumber]) - strlen(p) ); Q_strncpyz( localbotskin->bot_skin, p, sizeof(localbotskin->bot_skin) ); Q_strncpyz( localbotskin->bot_name, LocalBotNames[skinnumber], sizeof(localbotskin->bot_name) ); if( AIDevel.debugMode ) Com_Printf( "Free skin: %i: %s %s\n", freeskins, localbotskin->bot_skin, localbotskin->bot_name ); freeskins++; } skinnumber++; } //now get a random skin from the list skinnumber = (int)(random()*freeskins); localbotskin = botskins + skinnumber; Q_strncpyz( bot_model, localbotskin->bot_model, sizeof(localbotskin->bot_model) ); Q_strncpyz( bot_skin, localbotskin->bot_skin, sizeof(localbotskin->bot_skin) ); Q_strncpyz( bot_name, localbotskin->bot_name, sizeof(localbotskin->bot_name) ); if( AIDevel.debugMode ) Com_Printf( "Assigned bot character: %i: %s %s %s\n", skinnumber, bot_model, bot_skin, bot_name ); G_Free( botskins ); return qtrue; } //========================================== // BOT_CreateUserinfo // Creates UserInfo string to connect with //========================================== void BOT_CreateUserinfo( char *userinfo ) { char bot_skin[MAX_INFO_STRING]; char bot_name[MAX_INFO_STRING]; char bot_model[MAX_INFO_STRING]; //jalfixme: we have only one skin yet //GetUnusedSkin doesn't repeat already used skins/names if( !BOT_GetUnusedSkin( bot_model, bot_skin, bot_name ) ) { int i, botcount = 0; edict_t *ent; //count spawned bots for the names for (i = 0, ent = game.edicts + 1; i < game.maxclients; i++, ent++) { if( !ent->r.inuse || !ent->ai.type ) continue; if( ent->r.svflags & SVF_FAKECLIENT && ent->ai.type == AI_ISBOT ) botcount++; } // Set the name for the bot. Q_snprintfz( bot_name, sizeof(bot_name), "Bot%d", botcount+1 ); // randomly choose skin if( random() > 0.66f ) { Q_snprintfz( bot_model, sizeof(bot_model), "viciious" ); } else if( random() > 0.33f ) { Q_snprintfz( bot_model, sizeof(bot_model), "silverclaw" ); } else { Q_snprintfz( bot_model, sizeof(bot_model), "monada" ); } Q_snprintfz( bot_skin, sizeof(bot_skin), "default" ); } // initialize userinfo memset( userinfo, 0, sizeof(userinfo) ); // add bot's name/skin/hand to userinfo Info_SetValueForKey( userinfo, "name", bot_name ); Info_SetValueForKey( userinfo, "model", bot_model ); //Info_SetValueForKey( userinfo, "skin", bot_skin ); Info_SetValueForKey( userinfo, "skin", "default" ); // JALFIXME Info_SetValueForKey( userinfo, "hand", "2" ); Info_SetValueForKey( userinfo, "color", va( "%i %i %i", (qbyte)(random()*255), (qbyte)(random()*255), (qbyte)(random()*255) ) ); } //========================================== // BOT_NextCTFTeam // Get the emptier CTF team //========================================== /* int BOT_NextCTFTeam() { int i; int onteam1 = 0; int onteam2 = 0; edict_t *self; // Only use in CTF games if (!ctf->integer) return 0; for (i = 0; i < game.maxclients + 1; i++) { self = game.edicts +i + 1; if (self->r.inuse && self->r.client) { if (self->r.client->resp.ctf_team == CTF_TEAM1) onteam1++; else if (self->r.client->resp.ctf_team == CTF_TEAM2) onteam2++; } } if (onteam1 > onteam2) return (2); else if (onteam2 >= onteam1) return (1); return (1); }*/ //========================================== // BOT_JoinCTFTeam // Assign a team for the bot //========================================== /* qboolean BOT_JoinCTFTeam (edict_t *ent, char *team_name) { char *s; int team; edict_t *event; if (ent->r.client->resp.ctf_team != CTF_NOTEAM) return qfalse; // find what ctf team if ((team_name !=NULL) && (strcmp(team_name, "blue") == 0)) team = CTF_TEAM2; else if ((team_name !=NULL) && (strcmp(team_name, "red") == 0)) team = CTF_TEAM1; else team = BOT_NextCTFTeam(); if (team == CTF_NOTEAM) return qfalse; //join ctf team ent->r.svflags &= ~SVF_NOCLIENT; ent->r.client->resp.ctf_state = 1;//0? ent->r.client->resp.ctf_team = team; s = Info_ValueForKey (ent->r.client->pers.userinfo, "skin"); CTFAssignSkin(ent, s); PutClientInServer (ent); G_AddEvent (ent, EV_TELEPORT, 0, qtrue); // add a teleportation effect event = G_SpawnEvent ( EV_PLAYER_TELEPORT_IN, 0, ent->s.origin ); event->r.svflags = SVF_NOOLDORIGIN; event->s.ownerNum = ent - game.edicts; // hold in place briefly ent->r.client->ps.pmove.pm_flags = PMF_TIME_TELEPORT; ent->r.client->ps.pmove.pm_time = 14; G_PrintMsg (NULL, "%s%s joined the %s team.\n", ent->r.client->pers.netname, S_COLOR_WHITE, CTFTeamName(ent->r.client->resp.ctf_team)); return qtrue; }*/ /* int G_TeamFromTeamName( char *teamname ); //========================================== // ACESP_BotJoinGame // the bot connected as spectator //========================================== void ACESP_BotJoinGame (edict_t *ent, char *team_name) { //if ( BOT_JoinCTFTeam(ent, team_name) ) // return; if( team_name && team_name[0] ) G_Teams_JoinTeam( ent, G_TeamFromTeamName( team_name ) ); if( !ent->s.team ) G_Teams_JoinAnyTeam( ent, qfalse ); //respawn(ent); G_PrintMsg (NULL, "%s%s joined the game.\n", ent->r.client->pers.netname, S_COLOR_WHITE); }*/ //========================================== // BOT_Respawn // Set up bot for Spawn. Called at first spawn & each respawn //========================================== void BOT_Respawn(edict_t *self) { self->enemy = NULL; self->movetarget = NULL; AI_ResetWeights(self); AI_ResetNavigation(self); } //========================================== // BOT_InitPersistant // Persistant after respawns. To be class definition in the future //========================================== void BOT_InitPersistant( edict_t *self ) { float sv_skill; //standard stuff self->think = AI_Think; self->nextthink = level.timemsec + game.framemsec; self->ai.type = AI_ISBOT; self->classname = "bot"; self->yaw_speed = AI_DEFAULT_YAW_SPEED; self->die = player_die; // set skill based on sv_skilllevel cvar sv_skill = trap_Cvar_VariableValue( "sv_skilllevel" ); // 0 = easy, 2 = hard sv_skill += random();// so we have a float between 0 and 3 meaning the server skill if( !sv_skill ) sv_skill = 0.001f; self->ai.pers.skillLevel = sv_skill/3.0f; // the same being a fraction of 1. if( self->ai.pers.skillLevel < 0.1f ) self->ai.pers.skillLevel = 0.1f; self->yaw_speed -= 20 * (1.0f - self->ai.pers.skillLevel); //name if (self->r.client->pers.netname) self->ai.pers.netname = self->r.client->pers.netname; else self->ai.pers.netname = "SomeBot"; //class: always set up default first BOT_DMclass_InitPersistant(self); } //========================================== // BOT_DoSpawnBot // Spawn the bot //========================================== void BOT_DoSpawnBot( void ) { char userinfo[MAX_INFO_STRING]; fakeclient_t *fakeClient; edict_t *ent; if( !nav.loaded ) { Com_Printf( "AI: Can't spawn bots without a valid navigation file\n" ); if( g_numbots->integer )trap_Cvar_Set( "g_numbots", "0" ); return; } BOT_CreateUserinfo( userinfo ); fakeClient = G_SpawnFakeClient( userinfo, NULL ); if( !fakeClient || !fakeClient->ent ) return; ent = fakeClient->ent; G_SpawnAI(ent); //jabot092(2) //finish fakeclient initialization fakeClient->state = FAKECLIENT_STATE_INUSE; fakeClient->respawn = BOT_Respawn; //init this bot BOT_InitPersistant( ent ); //set up for Spawn BOT_Respawn( ent ); //stay as spectator, give random time for joining ent->nextthink = level.timemsec + random() * 8000; } //========================================== // BOT_SpawnerThink // Call the real bot spawning function //========================================== void BOT_SpawnerThink( edict_t *spawner ) { BOT_DoSpawnBot(); G_FreeEdict( spawner ); } //========================================== // BOT_SpawnBot // Used Spawn the bot //========================================== void BOT_SpawnBot( char *team_name ) { edict_t *spawner; int team; if( !nav.loaded ) { Com_Printf( "AI: Can't spawn bots without a valid navigation file\n" ); if( g_numbots->integer )trap_Cvar_Set( "g_numbots", "0" ); return; } // create a entity which will call the bot spawn spawner = G_Spawn(); spawner->think = BOT_SpawnerThink; team = GS_Teams_TeamFromName( team_name ); if( team != -1 ) spawner->s.team = team; spawner->nextthink = level.timemsec + random() * 3000; spawner->movetype = MOVETYPE_NONE; spawner->r.solid = SOLID_NOT; spawner->r.svflags |= SVF_NOCLIENT; trap_LinkEntity( spawner ); game.numBots++; } //========================================== // BOT_RemoveBot // Remove a bot by name or all bots //========================================== void BOT_RemoveBot(char *name) { int i; qboolean freed=qfalse; edict_t *ent; for( i=0, ent = game.edicts + 1; i < game.maxclients; i++, ent++ ) { if( !ent->r.inuse || ent->ai.type != AI_ISBOT ) continue; if( !Q_stricmp(ent->r.client->pers.netname,name) || !Q_stricmp(name,"all") ) { trap_DropClient(ent, DROP_TYPE_GENERAL, "BOT_RemoveBot"); freed = qtrue; } } if( !freed && Q_stricmp(name,"all") ) G_Printf( "BOT: %s not found\n", name ); }