/* Copyright (C) 2015 Marco "eukara" Hladik Copyright (C) 1996-1997 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. */ //TODO: Support for COOP behaviour // Handle botname parsing via COM routines // Weapons - make them less Xuake specific #include "globaldef.h" #ifdef GLOBOT #include "bot.h" globot_t globot; // This struct is used to store global stuff that aint client specific qboolean qUsesBotnames; int iMaxBotnames; char cBotName[32][16]; // eukara - 32 names max, 16 characters per name /* ====== Random This function only returns a random number in the range 0 - 1 ====== */ float Random (void) { return (rand ()&0x7fff) / ((float)0x7fff); } /* =========== RandomRange This function is pretty much the same as Random except that it returns a value in the range min - max =========== */ float RandomRange (float min, float max) { return (Random() * (max - min)) + min; } /* ======= Nextent This function is used to cycle thro all avaliable clients (this is just a recoded PF_nextent) ======= */ edict_t *Nextent (edict_t *edict) { int e; edict_t *ent; e = NUM_FOR_EDICT (edict); // Get the edictnum while (1) // Loop until we get a return { e++; // Increase e with 1 if (e == sv.num_edicts) // If gone through all edict's return sv.edicts; // then return ent = EDICT_NUM (e); // Get the edict from the new edictnum if (!ent->free) // If it exists return ent; // then return it } } /* ========= Traceline This function is used to decide if a bot can see his enemy or not (this is just a recoded PF_traceline) ========= */ qboolean Traceline (vec3_t v1, vec3_t v2, edict_t *ent, edict_t *enemy) { trace_t trace; trace = SV_Move (v1, vec3_origin, vec3_origin, v2, true, ent); if (trace.fraction == 1) // If the trace found its way to the enemy return true; // Then the bot can see him return false; // Otherwise he cant } /* ========== CalcAngles This function is used to decide if the bot should turn around or not (this is just a recoded PF_vectoangles) ========== */ void CalcAngles (vec3_t oldvector, vec3_t newvector) { float forward; float yaw, pitch; if (oldvector[1] == 0 && oldvector[0] == 0) { yaw = 0; if (oldvector[2] > 0) pitch = 90; else pitch = 270; } else { yaw = (int) (atan2(oldvector[1], oldvector[0]) * 180 / M_PI); if (yaw < 0) yaw += 360; forward = sqrt (oldvector[0]*oldvector[0] + oldvector[1]*oldvector[1]); pitch = (int) (atan2(oldvector[2], forward) * 180 / M_PI); if (pitch < 0) pitch += 360; } newvector[0] = pitch; newvector[1] = yaw; newvector[2] = 0; } /* ======= BotInit This function is used to calcualte the maximum amount of clients and to set some default values ======= */ void BotInit (void) { client_t *client; int i; globot.world = PROG_TO_EDICT(pr_global_struct->world); // Find the world entity globot.MaxClients = 0; // Reset the MaxClients counter for (i=0, client=svs.clients; iedict->bot.ClientNo = -1; // We dont want anyone to have a client number until they have connected client->edict->bot.menudone = false;// Of course we have not gone past the Team Fortress menu yet client->edict->bot.Active = false;// Of course noone is active... the game havent started yet :) client->edict->bot.isbot = false;// And noone is a bot either... or human... } for (i=1; i<16; i++) globot.botactive[i] = false; if (globot.MaxClients > 16) // We dont allow more then 16 players right now globot.MaxClients = 16; } /* =========== PickBotName This function is used to give each bot a different name TODO: Read and parse names from a file =========== */ char *PickBotName (int r) { if (r == 1) return "system32"; else if (r == 2) return "/dev/null"; else if (r == 3) return "Philip"; else if (r == 4) return "Travis"; else if (r == 5) return "Shawn"; else if (r == 6) return "Steve"; else if (r == 7) return "Dave"; else if (r == 8) return "Croft"; else if (r == 9) return "James"; else if (r == 10) return "Scott"; else if (r == 11) return "Ridley"; else if (r == 12) return "Mister"; else if (r == 13) return "Anubis"; else if (r == 14) return "Radon"; else if (r == 15) return "Maxwell"; return "player"; } /* ============ UpdateClient This function is used to "fake" a real client so the bot shows up on the scoreboard ============ */ void UpdateClient (client_t *client, int ClientNo) { MSG_WriteByte (&sv.reliable_datagram, svc_updatename); MSG_WriteByte (&sv.reliable_datagram, ClientNo); MSG_WriteString (&sv.reliable_datagram, client->name); MSG_WriteByte (&sv.reliable_datagram, svc_updatefrags); MSG_WriteByte (&sv.reliable_datagram, ClientNo); MSG_WriteShort (&sv.reliable_datagram, client->old_frags); MSG_WriteByte (&sv.reliable_datagram, svc_updatecolors); MSG_WriteByte (&sv.reliable_datagram, ClientNo); MSG_WriteByte (&sv.reliable_datagram, client->colors); } /* ========== BotConnect This function is used to connect the bot's ========== */ void BotConnect (client_t *client, int ClientNo, int color, char *name) { edict_t *self = PROG_TO_EDICT(pr_global_struct->self); // Make a backup of the current QC self edict_t *bot = client->edict; int randombot; bot->bot.isbot = true; // And yes this is a bot bot->bot.Active = true; // and hes active bot->bot.enemy = bot; // Now why is he chasing himself? bot->bot.connecttime = sv.time; bot->bot.ClientNo = ClientNo; // Now we get a clientnumber randombot = ceil (RandomRange (0, 15)); while (globot.botactive[randombot]) randombot = ceil (RandomRange (0, 15)); globot.botactive[randombot] = true; if (name[0] != '0') strcpy (client->name, name); else { if(qUsesBotnames == true && cBotName[randombot]) strcpy (client->name, cBotName[randombot]); else strcpy (client->name, PickBotName (randombot)); } if (color != 666) { client->colors = color * 14 + color; // The bot must have a color bot->v.team = color + 1; // And to be in a team } else { client->colors = randombot * 14 + randombot; // The bot must have a color bot->v.team = randombot + 1; // And to be in a team } client->old_frags = 0; // And since he just joined he cant have got any frags yet bot->v.colormap = ClientNo; // Without this he wont be using any colored clothes bot->v.netname = client->name - pr_strings; // Everyone wants a name UpdateClient (client, ClientNo); // Update the scoreboard pr_global_struct->self = EDICT_TO_PROG(bot); // Update the QC self to be the bot PR_ExecuteProgram (pr_global_struct->SetNewParms); // Now call some QC functions PR_ExecuteProgram (pr_global_struct->ClientConnect); // Now call some more QC functions PR_ExecuteProgram (pr_global_struct->PutClientInServer); // Now call yet some more QC functions pr_global_struct->self = EDICT_TO_PROG (self); // Get back to the backup } /* ============== NextFreeClient This function is used to find an empty client spot ============== */ void NextFreeClient (void) { client_t *client; int i, color; char name[32]; if (Cmd_Argc() == 2) { color = Q_atoi(Cmd_Argv(1)); sprintf (name, "0"); } else if (Cmd_Argc() == 3) { color = Q_atoi(Cmd_Argv(1)); sprintf (name, "%s", Cmd_Argv(2)); } else { color = 666; sprintf (name, "0"); } for (i=0, client=svs.clients; iedict->bot.Active) // We found a free client slot { BotConnect (client, i, color, name); // so why not connect a bot? return; // We are done now } } SV_BroadcastPrintf ("Unable to connect a bot, server is full.\n"); // No free client slots = no more bots allowed } /* ======= MoveBot This function is used to get the bot to actually move and shoot and kill and destroy and wreak havoc and slaughter and... oh sorry ======= */ void MoveBot (client_t *client, qboolean fire, edict_t *enemy) { if (fire) // If he has an enemy { vec3_t origin; client->edict->v.button0 = 1; // Shoot, slaughter, kill, destroy!!!! VectorSubtract (enemy->v.origin, client->edict->v.origin, origin); if (Length(origin) > 200 || // If further away then 200 units from enemy (int)client->edict->v.weapon & 4096) // or is using the axe client->cmd.forwardmove = 400; // Then chase after the enemy else // If closer then 200 units to enemy and not using the axe client->cmd.forwardmove = -400; // Then stay at a distance client->cmd.sidemove += (rand()&127)-64; // Make him strafe if (client->cmd.sidemove > 400) // If strafing to fast client->cmd.sidemove = 400; // Then limit the strafe speed else if (client->cmd.sidemove < -400) // If strafing to fast client->cmd.sidemove = -400; // Then limit the strafe speed } else // If he has no enemy { client->edict->v.button0 = 0; // Then why bother to shoot? client->cmd.forwardmove = 400; // Let him run } switch ((int)skill->value) { case 1: // Medium client->edict->v.v_angle[0] = client->edict->v.angles[0] + (rand()&15)-8; client->edict->v.v_angle[1] = client->edict->v.angles[1] + (rand()&15)-8; // Adjust him to aim where he looks and make it not 100% accurate client->edict->v.v_angle[2] = client->edict->v.angles[2] + (rand()&15)-8; break; case 2: // Hard client->edict->v.v_angle[0] = client->edict->v.angles[0] + (rand()&7)-4; client->edict->v.v_angle[1] = client->edict->v.angles[1] + (rand()&7)-4; // Adjust him to aim where he looks and make it not 100% accurate client->edict->v.v_angle[2] = client->edict->v.angles[2] + (rand()&7)-4; break; case 3: // Nightmare client->edict->v.v_angle[0] = client->edict->v.angles[0] + (rand()&3)-2; client->edict->v.v_angle[1] = client->edict->v.angles[1] + (rand()&3)-2; // Adjust him to aim where he looks and make it not 100% accurate client->edict->v.v_angle[2] = client->edict->v.angles[2] + (rand()&3)-2; break; default: // Easy client->edict->v.v_angle[0] = client->edict->v.angles[0] + (rand()&31)-16; client->edict->v.v_angle[1] = client->edict->v.angles[1] + (rand()&31)-16; // Adjust him to aim where he looks and make it not 100% accurate client->edict->v.v_angle[2] = client->edict->v.angles[2] + (rand()&31)-16; break; } } /* ============== SearchForEnemy This function is used to search for something for the bot to shoot at ============== */ void SearchForEnemy (client_t *client) { edict_t *bot = client->edict; edict_t *nmy = bot->bot.enemy; vec3_t eyes1, eyes2, origin, test; int test2; int num; if (nmy != bot && // If he has an enemy that aint himself nmy->v.health > 0) // and has some health { VectorAdd (nmy->v.origin, nmy->v.view_ofs, eyes1); // We want the origin of the enemies eyes VectorAdd (bot->v.origin, bot->v.view_ofs, eyes2); // We want the origin of the bots eyes if (Traceline (eyes1, eyes2, bot, nmy)) // If the bot can see his enemy { VectorSubtract (eyes1, eyes2, origin); // Get a nice vector CalcAngles (origin, test); // And use it to see in what direction the enemy is test2 = test[1] - bot->v.angles[1]; // Another shortcut if (test2 > -80 && test2 < 80) // If enemy is in front of the bot so he can see him { VectorCopy (test, bot->v.angles); // Then turn towards the enemy MoveBot (client, true, nmy); // and start running and shooting return; // We are done here now... } } } // Guess we had no enemy or couldnt see him anymore bot->bot.enemy = bot; // Set enemy to the bot himself again nmy = Nextent(globot.world); // Prepare to loop through clients num = 0; while (num < globot.MaxClients) // Keep looping as long as there are clients { if (nmy->bot.Active && // Enemy is playing nmy != bot && // and is not the bot himself nmy->v.health > 0 && // and is alive nmy->v.team != bot->v.team) // and in another team { VectorAdd (nmy->v.origin, nmy->v.view_ofs, eyes1); // We want the origin of the clients eyes VectorAdd (bot->v.origin, bot->v.view_ofs, eyes2); // We want the origin of the bots eyes if (Traceline (eyes1, eyes2, bot, nmy)) // If the bot can see his client { VectorSubtract (eyes1, eyes2, origin); // Get a nice vector CalcAngles (origin, test); // And use it to see in what direction the client is test2 = test[1] - bot->v.angles[1]; // Another shortcut if (test2 > -60 && test2 < 60) // If client is in front of the bot so he can see him { bot->bot.enemy = nmy; // Then set him as the enemy bot->bot.chase = -1; // and stop chasing VectorCopy (test, bot->v.angles); // Then turn to the enemy MoveBot (client, true, nmy); // and start running, jumping and shooting return; // We are done here now... } } } num++; nmy = Nextent(nmy); // If the client was'nt visible then continue the loop with the next client } // Guess we found no enemies bot->v.button0 = 0; // So why waste ammo? num = bot->bot.chase; // Are we already chasing someone? if (num == -1) // If we arent num = (ceil (rand()%globot.MaxClients)) + NUM_FOR_EDICT(globot.world); // Then find someone to chase nmy = EDICT_NUM(num); // And find that client if (nmy->bot.Active && // Enemy is playing nmy != bot && // and is not the bot himself nmy->v.health > 0 && // and is alive nmy->v.team != bot->v.team) // and in another team { bot->bot.chase = num; // Then start chasing him VectorSubtract (nmy->v.origin, bot->v.origin, origin); // Get a nice vector CalcAngles (origin, test); // And use it to see in what direction the client is bot->v.angles[0] = 0; // This is reset so it doesnt look like hes running into the floor if the chase client is below him bot->v.angles[1] = test[1]; // Then turn the bot that way bot->v.angles[2] = 0; MoveBot (client, false, nmy); // And get him running return; } bot->bot.chase = -1; // Guess we found noone to chase either... so we'll stand here and see if we find anyone the next frame } /* ============ SwitchWeapon This is the function where the bot checks what ammo and weapons he has... And switches to the best weapon avaliable... ============ */ void SwitchWeapon (edict_t *bot) { int items = (int)bot->v.items; int weapon = (int)bot->v.weapon; if (bot->v.ammo_rockets >= 1 && // If the bot has some rockets items & IT_ROCKET_LAUNCHER && // and the Rocket Launcher weapon != IT_ROCKET_LAUNCHER) // and aint using it { bot->v.impulse = 7; // Then use the Rocket Launcher return; } if (bot->v.ammo_cells >= 1 && // If the bot has some cells bot->v.waterlevel <= 1 && // and is not in water items & IT_LIGHTNING && // and the Lightning Gun weapon != IT_LIGHTNING) // and aint using it { bot->v.impulse = 8; // Then use the Lightning Gun return; } if (bot->v.ammo_nails >= 2 && // If the bot has some nails items & IT_SUPER_NAILGUN && // and the Super Nailgun weapon != IT_SUPER_NAILGUN) // and aint using it { bot->v.impulse = 5; // Then use the Super Nailgun return; } if (bot->v.ammo_rockets >= 1 && // If the bot has some rockets items & IT_GRENADE_LAUNCHER && // and the Grenade Launcher weapon != IT_GRENADE_LAUNCHER) // and aint using it { bot->v.impulse = 6; // Then use the Grenade Launcher return; } if (bot->v.ammo_nails >= 1 && // If the bot has some nails items & IT_NAILGUN && // and the Nailgun weapon != IT_NAILGUN) // and aint using it { bot->v.impulse = 4; // Then use the Nailgun return; } if (bot->v.ammo_shells >= 2 && // If the bot has some sheels items & IT_SUPER_SHOTGUN && // and the Super Shotgun weapon != IT_SUPER_SHOTGUN) // and aint using it { bot->v.impulse = 3; // Then use the Super Shotgun return; } if (bot->v.ammo_shells >= 1 && // If the bot has some shells items & IT_SHOTGUN && // and the Shotgun weapon != IT_SHOTGUN) // and aint using it { bot->v.impulse = 2; // Then use the Shotgun return; } } /* =========== BotPreFrame This function is where everything starts... From here we search for enemies to hunt and shoot and make the bot to respawn if killed... =========== */ void BotPreFrame (client_t *client) { edict_t *bot = client->edict; client->cmd.forwardmove = 0; // Stop all bots from running SwitchWeapon (bot); if (bot->v.deadflag == DEAD_NO) // If the bot is alive SearchForEnemy (client); // Then search for an enemy or someone to chase else { bot->v.button0 = 0; // If dead then clear all buttons bot->v.button1 = 0; bot->v.button2 = 0; } if (bot->v.deadflag == DEAD_RESPAWNABLE) // If dead and ready to respawn bot->v.button1 = 1; // Then respawn } /* ============ BotPostFrame This function is used to check if the bot is running into a wall ============ */ void BotPostFrame (client_t *client) { edict_t *bot = client->edict; if (bot->bot.chase != -1) // If we are chasing someone { if ((bot->v.velocity[0] < 20 && bot->v.velocity[0] > -20) && // And if our speed is slow (bot->v.velocity[1] < 20 && bot->v.velocity[1] > -20)) // (running against a wall) { bot->bot.chase = -1; // Then find a new client to chase } } // This last piece here is only used by Team Fortress if (!bot->bot.menudone) // If we have not gone past the class selection menu { if (bot->v.health > 5) // If we have more health then 5 bot->bot.menudone = true; // Then this is not Team Fortress so there is no menu // Looks like we are playing Team Fortress after all else if (sv.time > bot->bot.connecttime + 2) // If bot has been active more then 2 seconds then choose a random class and leave the menu bot->v.impulse = 10; else if (sv.time > bot->bot.connecttime + 1 && // If bot has been active between 1 and 2 secs teamplay->value) // and we are playing teamplay bot->v.impulse = 5; // Then choose a random team } } /* ======== Bot_Init This function is what allows us to use the command "addbot" from the console ======== */ void Bot_Init (void) { char name[MAX_OSPATH]; Cmd_AddCommand ("addbot", NextFreeClient); // eukara - loading botnames start printf("[CLIENT] Looking for botnames.cfg...\n"); sprintf (name, "%s/botnames.cfg", com_gamedir); FILE *fd = fopen(name,"r"); if (fd == NULL) { printf("[CLIENT] No bot definition file found.\n"); qUsesBotnames = false; return; } qUsesBotnames = true; iMaxBotnames = 0; // id is OK as os while ((fscanf(fd, "%16[^\n]\n", cBotName[iMaxBotnames]) == 1) && !feof(fd)) { // only load 16 chars... iMaxBotnames++; } if(iMaxBotnames < svs.maxclients) // Just to be safe. { printf("[CLIENT] Not enough bot names in botnames.cfg\n"); qUsesBotnames = false; } // eukara - loading botnames end } #endif