/***************************************************************** Eraser Bot source code - by Ryan Feltrin, Added to by Acrid- .............................................................. This file is Copyright(c) 1998, Ryan Feltrin, All Rights Reserved. .............................................................. All other files are Copyright(c) Id Software, Inc. Please see liscense.txt in the source directory for the copyright information regarding those files belonging to Id Software, Inc. .............................................................. Should you decide to release a modified version of Eraser, you MUST include the following text (minus the BEGIN and END lines) in the documentation for your modification. --- BEGIN --- The Eraser Bot is a product of Ryan Feltrin, and is available from the Eraser Bot homepage, at http://impact.frag.com. This program is a modification of the Eraser Bot, and is therefore in NO WAY supported by Ryan Feltrin. This program MUST NOT be sold in ANY form. If you have paid for this product, you should contact Ryan Feltrin immediately, via the Eraser Bot homepage. --- END --- .............................................................. You will find p_trail.c has not been included with the Eraser source code release. This is NOT an error. I am unable to distribute this file because it contains code that is bound by legal documents, and signed by myself, never to be released to the public. Sorry guys, but law is law. I have therefore include the compiled version of these files in .obj form in the src\Release and src\Debug directories. So while you cannot edit and debug code within these files, you can still compile this source as-is. Although these will only work in MSVC v5.0, linux versions can be made available upon request. NOTE: When compiling this source, you will get a warning message from the compiler, regarding the missing p_trail.c file. Just ignore it, it will still compile fine. .............................................................. I, Ryan Feltrin/Acrid-, hold no responsibility for any harm caused by the use of this source code. I also am NOT willing to provide any form of help or support for this source code. It is provided as-is, as a service by me, with no documentation, other then the comments contained within the code. If you have any queries, I suggest you visit the "official" Eraser source web-board, at http://www.telefragged.com/epidemic/. I will stop by there from time to time, to answer questions and help with any problems that may arise. Otherwise, have fun, and I look forward to seeing what can be done with this. -Ryan Feltrin -Acrid- *****************************************************************/ #include "g_local.h" #include "bot_procs.h" #include "m_player.h" void CopyToBodyQue (edict_t *ent); void bot_AnimateFrames(edict_t *self) { int minframe, maxframe; vec3_t themove; VectorSubtract(self->s.origin, self->animate_org, themove); VectorCopy(self->s.origin, self->animate_org); themove[2] = 0; if (self->s.modelindex != 255) { // gibbed, ready to spawn self->s.frame = 0; if (self->timestamp < (level.time - 2)) // wait a bit respawn_bot(self); return; } //NEW TESTING botfreeze note needed more than 1 frame to body didnt disappear.. if (self->s.frame >= FRAME_stand01 && self->frozen && self->health <=0) { self->client->anim_priority = ANIM_DEATH; if ((self->s.frame >= FRAME_stand01) && (self->s.frame < FRAME_stand03)) { self->s.frame++; } else // must be ready to spawn { CopyToBodyQue(self); respawn_bot(self); } return; } //NEW TESTING if (self->s.frame >= FRAME_crdeath1) { self->client->anim_priority = ANIM_DEATH; if (((self->s.frame >= FRAME_crdeath1) && (self->s.frame < FRAME_crdeath5)) || ((self->s.frame >= FRAME_death101) && (self->s.frame < FRAME_death106)) || ((self->s.frame >= FRAME_death201) && (self->s.frame < FRAME_death206)) || ((self->s.frame >= FRAME_death301) && (self->s.frame < FRAME_death308))) { self->s.frame++; } else // must be ready to spawn { CopyToBodyQue(self); respawn_bot(self); } return; } //NEW TESTING else if (self->frozen && self->health <= 0)//acrid 3/99 botfrozen death animation { self->s.frame = FRAME_stand01; } //NEW TESTING else if (self->health <= 0 && !self->frozen) // FIXME: this shouldn't happen, but it does { // pick a random death frame, and go with it float rnd; rnd = random() * 3; if (self->viewheight < 0) self->s.frame = FRAME_crdeath1; else if (rnd <= 1) self->s.frame = FRAME_death101; else if (rnd <= 2) self->s.frame = FRAME_death201; else self->s.frame = FRAME_death301; } else if (self->client->anim_priority == ANIM_PAIN) { self->s.frame++; if (self->s.frame > self->client->anim_end) { self->s.frame = FRAME_stand01; self->client->anim_priority = ANIM_BASIC; } } else if (!self->groundentity && (self->waterlevel || (self->last_inair > (level.time - 0.3)))) { // jumping self->client->anim_priority = ANIM_JUMP; minframe = FRAME_jump1; maxframe = FRAME_jump3; self->s.frame++; if (self->s.frame < minframe) self->s.frame = minframe; else if (self->s.frame > maxframe) self->s.frame = maxframe; } else if ((self->s.frame >= FRAME_jump3) && (self->s.frame < FRAME_jump6)) // jump landing { self->client->anim_priority = ANIM_JUMP; maxframe = FRAME_jump3; self->s.frame++; } else if ((self->s.frame <= FRAME_point12) && (self->s.frame >= FRAME_salute01)) { self->client->anim_priority = ANIM_WAVE; self->s.frame++; if (self->enemy || (self->s.frame > self->radius_dmg)) // abort if enemy found self->s.frame = FRAME_stand01; } else if ((self->s.frame >= FRAME_attack1) && (self->s.frame <= FRAME_attack3)) { self->client->anim_priority = ANIM_ATTACK; maxframe = FRAME_attack3; self->s.frame++; if (self->s.frame > maxframe) self->s.frame = FRAME_run1; // finshed firing, loop is created by each fire, in bot_Attack() } else if ((self->s.frame >= FRAME_crattak1) && (self->s.frame < FRAME_crattak3)) { self->client->anim_priority = ANIM_ATTACK; maxframe = FRAME_crattak3; self->s.frame++; if (self->s.frame > maxframe) self->s.frame = FRAME_run1; // finshed firing, loop is created by each fire, in bot_Attack() } else if (VectorLength(themove) > 4) // running { self->client->anim_priority = ANIM_BASIC; if (self->maxs[2] == 4) { minframe = FRAME_crwalk1; maxframe = FRAME_crwalk6; } else { minframe = FRAME_run1; maxframe = FRAME_run6; if (!self->waterlevel && self->client->pickup_msg_time < (level.time - 0.3)) { self->s.event = EV_FOOTSTEP; self->client->pickup_msg_time = level.time; } } self->s.frame++; if (self->s.frame < minframe) self->s.frame = minframe; else if (self->s.frame > maxframe) self->s.frame = minframe; } else // standing { self->client->anim_priority = ANIM_BASIC; if (self->maxs[2] == 4) { minframe = FRAME_crstnd01; maxframe = FRAME_crstnd19; } else { minframe = FRAME_stand01; maxframe = FRAME_stand40; } self->s.frame++; if (self->s.frame < minframe) self->s.frame = minframe; else if (self->s.frame > maxframe) self->s.frame = minframe; } } void botDebugPrint(char *msg, ...) { if (!bot_debug->value) return; else { char bigbuffer[0x10000]; int len; va_list argptr; va_start (argptr,msg); len = vsprintf (bigbuffer,msg,argptr); va_end (argptr); gi.dprintf(bigbuffer); } } void ReadBotChat(void) { FILE *f; int section_index, line_count, i; char filename[256]; char buffer; cvar_t *game_dir; game_dir = gi.cvar ("game", "", 0); #ifdef _WIN32 i = sprintf(filename, ".\\"); i += sprintf(filename + i, game_dir->string); i += sprintf(filename + i, "\\chat.txt"); #else strcpy(filename, "./"); strcat(filename, game_dir->string); strcat(filename, "/chat.txt"); #endif f = fopen (filename, "r"); if (!f) { gi.error("\nUnable to read chat.txt\nChat functions not available.\n\n"); return; } memset(bot_chat_text, 0, sizeof(bot_chat_text)); section_index = -1; while (!feof(f)) { fscanf(f, "%c", &buffer); if (buffer == '#') { // read to the end of the line while (!feof(f) && (buffer != '\n')) fscanf(f, "%c", &buffer); } else if (buffer == '-') { // increment section section_index++; line_count = -1; while (!feof(f) && (buffer != '\n')) fscanf(f, "%c", &buffer); } else if (((buffer >= 'a') && (buffer <= 'z')) || ((buffer >= 'A') && (buffer <= 'Z')) || (buffer == '%')) { // read this entire line i = 0; line_count++; // allocate memory for new string bot_chat_text[section_index][line_count] = gi.TagMalloc(256, TAG_GAME); memset(bot_chat_text[section_index][line_count], 0, 256); while (!feof(f) && (buffer != '\n')) { bot_chat_text[section_index][line_count][i++] = buffer; fscanf(f, "%c", &buffer); } if (i > 0) { bot_chat_text[section_index][line_count][i] = '\0'; } // update the count now bot_chat_count[section_index] = line_count; } } fclose(f); } //FIXME ACRID GOES WITH BOTS.CFG gitem_t *GetWeaponForNumber(int i) { switch (i) { case 2 : return item_shotgun; case 3 : return item_supershotgun; case 4 : return item_machinegun; case 5 : return item_chaingun; case 6 : return item_grenadelauncher; case 7 : return item_rocketlauncher; case 8 : return item_railgun; case 9 : return item_hyperblaster; case 0 : return item_bfg10k; case 10 : return item_sniperrifle;//Acrid test case 11 : return item_lightninggun; case 12 : return item_infecteddart; case 13 : return item_pulsecannon; case 14 : return item_telsacoil; case 15 : return item_flamethrower; case 16 : return item_pelletrocketlauncher; case 17 : return item_rocketnapalmlauncher; case 18 : return item_rocketclusterlauncher; case 19 : return item_needler; case 20 : return item_shc; case 21 : return item_handgrenades; case 22 : return item_poisondart;//armordart case 23 : return item_ak47; case 24 : return item_pistol; case 25 : return item_stingerrocketlauncher; case 26 : return item_knife; default: gi.dprintf("GetWeaponForNumber: Unknown item number: %d\n",i); return item_shotgun; } } //ACRID I THINK THIS IS READING NON TEAM LISTING FOR TEAMS DATA,SKINS, SCORE bot_info_t *GenerateBotData(bot_team_t *bot_team, char *botname) { int i, gen; bot_info_t *botdata; botdata = gi.TagMalloc (sizeof(bot_info_t), TAG_GAME); botdata->ingame_count = 0; // name botdata->name = gi.TagMalloc (128, TAG_GAME); strcpy(botdata->name, botname); // skin botdata->skin = gi.TagMalloc (128, TAG_GAME); strcpy(botdata->skin, bot_team->default_skin); i = gen = 0; while (botname[i]) { gen += botname[i] + i; i++; } // stats botdata->bot_stats.accuracy = (float) (1 + ((int)(gen*3.234) % 5)); botdata->bot_stats.aggr = (float) (1 + ((int)(gen*5.132) % 5)); botdata->bot_stats.combat = (float) (1 + ((int)(gen*4.476) % 5)); botdata->bot_stats.fav_weapon = GetWeaponForNumber(3 + ((int)(gen*2.356) % 7)); botdata->bot_stats.quad_freak = (((int)(gen*1.453) % 3) < 2); botdata->bot_stats.camper = (((int)(gen*2.376) % 3) == 2); botdata->bot_stats.avg_ping = ((int)(gen*0.678) % 4) * 100; return botdata; } // Advances the pointer to the FILE to the next non-space, non-tab character char next_nonspace(FILE **f) { char ch=9; while (!feof(*f) && ((ch==9) || (ch==' '))) { fscanf(*f, "%c", &ch); // fscanf will skip spaces, but not TABS } return ch; }; bot_team_t *ReadTeamData(FILE **f) { char strbuf[256], ch; int i, numbots; bot_team_t *bot_team; bot_team = gi.TagMalloc (sizeof(bot_team_t), TAG_GAME); // name i=0; fscanf(*f, "%c", &strbuf[i]); while ((strbuf[i] != '\"') && (i < 255)) { i++; fscanf(*f, "%c", &strbuf[i]); } strbuf[i] = '\0'; // strip the trailing " bot_team->teamname = gi.TagMalloc (strlen(strbuf)+1, TAG_GAME); strcpy(bot_team->teamname, strbuf); // abbrev i=0; next_nonspace(f); fscanf(*f, "%c", &strbuf[i]); while ((strbuf[i] != '\"') && (i < 255)) { i++; fscanf(*f, "%c", &strbuf[i]); } strbuf[i] = '\0'; // strip the trailing " bot_team->abbrev = gi.TagMalloc (strlen(strbuf)+1, TAG_GAME); strcpy(bot_team->abbrev, strbuf); // default model/skin i=0; next_nonspace(f); fscanf(*f, "%c", &strbuf[i]); while ((strbuf[i] != '\"') && (i < 255)) { i++; fscanf(*f, "%c", &strbuf[i]); } strbuf[i] = '\0'; // strip the trailing " bot_team->default_skin = G_CopyString(strbuf); bot_team->default_skin = gi.TagMalloc (strlen(strbuf)+1, TAG_GAME); strcpy(bot_team->default_skin, strbuf); // start of the bots ch = 0; while ((ch != '[') && !feof(*f)) fscanf(*f, "%c", &ch); if (feof(*f)) { gi.error("Bot team \"%s\"doesn't have bot list\n", bot_team->teamname); return NULL; } fscanf(*f, "%c", &ch); // skip the opening " numbots = 0; while ((numbots < MAX_PLAYERS_PER_TEAM) && (ch != ']')) { // read the name i=0; fscanf(*f, "%c", &strbuf[i]); while ((strbuf[i] != '\"') && (i < 255)) { i++; fscanf(*f, "%c", &strbuf[i]); } strbuf[i] = '\0'; // strip the trailing " if (!(bot_team->bots[numbots] = GetBotData(strbuf))) { bot_team->bots[numbots] = GenerateBotData(bot_team, strbuf); // add the bot to the teambot_list if (!teambot_list) { teambot_list = bot_team->bots[numbots]; teambot_list->next = NULL; } else // add to the start of the list { bot_team->bots[numbots]->next = teambot_list; teambot_list = bot_team->bots[numbots]; } } numbots++; if (next_nonspace(f) == ']') break; // end of group } // clear any remaing bot slots while (numbots < MAX_PLAYERS_PER_TEAM) bot_team->bots[numbots++] = NULL; bot_team->score = 0; bot_team->ingame = 0; bot_team->num_players = 0; return bot_team; }; //FIXME ACRID // reads a line of bot data from bots.cfg bot_info_t *ReadBotData(FILE **f) { char strbuf[256]; char buffer; int i; bot_info_t *botdata; botdata = gi.TagMalloc (sizeof(bot_info_t), TAG_GAME); botdata->ingame_count = 0; // name i=0; fscanf(*f, "%c", &strbuf[i]); while ((strbuf[i] != '\"') && (i < 255)) { i++; fscanf(*f, "%c", &strbuf[i]); if (strbuf[i] == '\n') return NULL; } strbuf[i] = '\0'; // strip the trailing " botdata->name = gi.TagMalloc (128, TAG_GAME); strcpy(botdata->name, strbuf); while (!feof(*f) && (buffer != '"')) { fscanf(*f, "%c", &buffer); if (buffer == '\n') return NULL; } // skin i=0; fscanf(*f, "%c", &strbuf[i]); if (strbuf[i] == '\n') return NULL; while ((strbuf[i] != '\"') && (i < 255)) { i++; fscanf(*f, "%c", &strbuf[i]); if (strbuf[i] == '\n') return NULL; } strbuf[i] = '\0'; // strip the trailing " botdata->wfclass = gi.TagMalloc (128, TAG_GAME);//Acrid strcpy(botdata->wfclass, strbuf);//Acrid fscanf(*f, "%c", &buffer); if (buffer == '\n') return NULL; // stats fscanf(*f, "%i", &i); botdata->bot_stats.accuracy = (float) i; fscanf(*f, "%i", &i); botdata->bot_stats.aggr = (float) i; fscanf(*f, "%i", &i); botdata->bot_stats.combat = (float) i; fscanf(*f, "%i", &i); if (i == 1) // overwrite blaster with RL i = 7; botdata->bot_stats.fav_weapon = GetWeaponForNumber(i); fscanf(*f, "%i", &(botdata->bot_stats.quad_freak)); fscanf(*f, "%i", &(botdata->bot_stats.camper)); fscanf(*f, "%i", &(botdata->bot_stats.avg_ping)); return botdata; } void ReadViewWeaponModel(FILE **f) { char buffer; int i=0; while (!feof(*f) && (buffer != '\n')) { fscanf(*f, "%c", &buffer); if ((buffer != '"') && (buffer != '\n') && (buffer != '\r')) view_weapon_models[num_view_weapons][i++] = buffer; } if (i>0) { view_weapon_models[num_view_weapons][i] = 0; num_view_weapons++; } } qboolean read_bot_cfg = false; // Reads data from bots.cfg (called from worldspawn) void ReadBotConfig() { FILE *f; int i, mode=0; // mode 0 for reading bots, 1 for teams char filename[256]; char buffer; bot_info_t *botdata=NULL, *last_botdata=NULL; cvar_t *game_dir; if (read_bot_cfg) // must have already read in the config return; game_dir = gi.cvar ("game", "", 0); #ifdef _WIN32 i = sprintf(filename, ".\\"); i += sprintf(filename + i, game_dir->string); i += sprintf(filename + i, "\\bots.cfg"); #else strcpy(filename, "./"); strcat(filename, game_dir->string); strcat(filename, "/bots.cfg"); #endif f = fopen (filename, "r"); if (!f) { gi.error("Unable to read bots.cfg. Cannot continue.\n"); return; } // initialise the teams for (i=0; iingame_count = 0; botinfo_list->name = "Eraser"; botinfo_list->wfclass = "1";//was skin botinfo_list->bot_stats.accuracy = 5; botinfo_list->bot_stats.aggr = 0; botinfo_list->bot_stats.combat = 5; botinfo_list->bot_stats.fav_weapon = GetWeaponForNumber(2); botinfo_list->bot_stats.quad_freak = 0; botinfo_list->bot_stats.camper = 0; botinfo_list->bot_stats.avg_ping = 50; // done. botdata = botinfo_list; total_bots = 1; total_teams = 0; teambot_list = NULL; num_view_weapons = 0; memset(view_weapon_models, 0, sizeof(view_weapon_models)); while (!feof(f)) { fscanf(f, "%c", &buffer); if (feof(f)) break; if (buffer == '#') // commented line { while (!feof(f) && (buffer != '\n')) fscanf(f, "%c", &buffer); } else if (buffer == '[') // mode specifier (bots/teams) { fscanf(f, "%c", &buffer); if (buffer == 'b') mode = 0; else if (buffer == 't') { // if (!teamplay->value /*|| ctf->value*/) // break; mode = 1; } else if (buffer == 'v') { // if (!view_weapons->value) break; mode = 2; } fscanf(f, "\n"); } else if (buffer == '"') // start of some data { if (mode == 0) { last_botdata = botdata; if (!(botdata = ReadBotData(&f))) { gi.error("\nError in BOTS.CFG: Invalid BOT (#%i)\nEither re-install Eraser, or check your bots.cfg file for errors\n\n", total_bots); break; } #ifdef _WIN32 if (!_stricmp(botdata->name, "Eraser")) #else if (!strcasecmp(botdata->name, "Eraser")) #endif { // ignore this bot gi.TagFree(botdata); botdata = last_botdata; } else { total_bots++; if (last_botdata) last_botdata->next = botdata; else // first bot botinfo_list = botdata; botdata->next = NULL; } } else if (mode == 1) // teamplay data { if (!(bot_teams[total_teams] = ReadTeamData(&f))) { gi.error("\nError in BOTS.CFG: Invalid TEAM (#%i)\nEither re-install Eraser, or check your bots.cfg file for errors\n\n", total_teams); break; } total_teams++; if (total_teams == MAX_TEAMS) { gi.dprintf("Warning: MAX_TEAMS reached, unable to process all teams\n"); break; } } else if (mode == 2) // view weapon models { ReadViewWeaponModel(&f); } } } gi.dprintf("%i bots read.\n", total_bots); if (teamplay->value) gi.dprintf("%i teams read.\n", total_teams); gi.dprintf("\n"); /* if (view_weapons->value && (num_view_weapons == 0)) { gi.dprintf("WARNING: view_weapons enabled, but no [view weapon] section in bots.cfg\n You should re-install Eraser to restore the [view weapons] section.\n\n"); }*/ fclose (f); ReadBotChat(); read_bot_cfg = true; // don't load again } qboolean ViewModelSupported(char *model) { int i; for (i=0; i < num_view_weapons; i++) { if (!strcmp(view_weapon_models[i], model)) return true; } return false; } bot_info_t *GetBotData(char *botname) { bot_info_t *trav, *fallback=NULL; qboolean done; if (!botinfo_list) { gi.dprintf("No bots available!\n"); return NULL; } if (!botname) { // pick a random bot int i, repeat_count=0; if ((total_bots-1) == bot_count) // already using all bots! return NULL; i = (int) ceil(random() * ((total_bots-1) - bot_count)); // prevent Eraser from joining unless he is specifically asked for trav = botinfo_list->next; while (trav && (i > 0)) { if (!trav->ingame_count) // not being used i--; if (i>0) trav = trav->next; } done = false; while (!done && trav) { done = true; if (!fallback) fallback = trav; if (trav->ingame_count) done = false; /*else if (view_weapons->value)//ACRID FIXME HAD TO CO THIS FOR VIEWWEAPONS { // if this model isn't supported by view weapons, look for another one char heldmodel[128]; int len; strcpy(heldmodel, trav->skin);//fixme? for(len = 0; heldmodel[len]; len++) { if(heldmodel[len] == '/') { heldmodel[len] = '\0'; break; } } if (!ViewModelSupported(heldmodel)) { // model not supported by view_weapons done = false; } }*/ if (!done) { if (!(trav = trav->next)) { if (repeat_count < 2) trav = botinfo_list->next; repeat_count++; } } } if (trav) { return trav; } else if (fallback) { return fallback; } else { gi.dprintf("GetBotData(): random selection didn't work\n"); return NULL; } } else // see if we can find the bot in the list { char name[128], checkname[128]; int i; trav = botinfo_list; // remove the team abbrev. strcpy(checkname, botname); i=0; while (checkname[i] && (checkname[i] != '[')) i++; if (checkname[i] == '[') checkname[i] = 0; // remove the team abbrev. strcpy(name, trav->name); i=0; while (i<128 && name[i] && (name[i] != '[')) i++; if (name[i] == '[') name[i] = 0; #ifdef _WIN32 while (trav && _stricmp(checkname, name)) // not case-sensitive { trav = trav->next; #else while (trav && strcasecmp(checkname, name)) // not case-sensitive { trav = trav->next; #endif if (trav) { // remove the team abbrev. strcpy(name, trav->name); i=0; while (i<128 && name[i] && (name[i] != '[')) i++; if (name[i] == '[') name[i] = 0; } } if (!trav) { // search through the teamplay bots trav = teambot_list; if (trav) { // remove the team abbrev. strcpy(name, trav->name); i=0; while (i<128 && name[i] && (name[i] != '[')) i++; if (name[i] == '[') name[i] = 0; } #ifdef _WIN32 while (trav && _stricmp(checkname, name)) // not case-sensitive { trav = trav->next; #else while (trav && strcasecmp(checkname, name)) // not case-sensitive { trav = trav->next; #endif if (trav) { // remove the team abbrev. strcpy(name, trav->name); i=0; while (i<128 && name[i] && (name[i] != '[')) i++; if (name[i] == '[') name[i] = 0; } } } return trav; } } void FindVisibleItemsFromNode(edict_t *node) { int list; int pi=0, besti=-1; float bestdist=99999, thisdist; vec3_t org; edict_t *trav; for (list=0; list<4; list++) { if (list==0) trav = weapons_head; else if (list==1) trav = health_head; else if (list==2) trav = ammo_head; else trav = bonus_head; while (trav) {//lavacode // make sure it's not in a dangerous position VectorSubtract(trav->s.origin, tv(0,0,8), org); if (!(gi.pointcontents(org) & (CONTENTS_LAVA | CONTENTS_SLIME))) { if ((thisdist = entdist(trav, node)) > 256) goto next_ent; // find the end of the item's visible node list pi = 0; while ((trav->paths[pi] > -1) && (pi < MAX_PATHS)) pi++; if (pi == MAX_PATHS) goto next_ent; if (visible_box(node, trav) && CanReach(node, trav)) { trav->paths[pi] = node->trail_index; // see if this node is closer than the previous closest node if (!trav->movetarget || (thisdist < entdist(trav, trav->movetarget))) { trav->movetarget = node; } } } next_ent: trav = trav->node_target; } } } // skill_level ranges from 0 -> 3 void AdjustRatingsToSkill(edict_t *self) { self->bot_stats->accuracy = self->botdata->bot_stats.accuracy + (float)(self->skill_level - 1) * 2.5; if (self->bot_stats->accuracy > 5) self->bot_stats->accuracy = 5; else if (self->bot_stats->accuracy < 1) self->bot_stats->accuracy = 1; self->bot_stats->combat = self->botdata->bot_stats.combat + (float)(self->skill_level - 1) * 2.5; if (self->bot_stats->combat > 5) self->bot_stats->combat = 5; else if (self->bot_stats->combat < 1) self->bot_stats->combat = 1; self->bot_stats->aggr = self->botdata->bot_stats.aggr - (float)(self->skill_level - 1) * 2; // a good player, will make sure they stock up on armour/items before attacking if (self->bot_stats->aggr > 5) self->bot_stats->aggr = 5; else if (self->bot_stats->aggr < 1) self->bot_stats->aggr = 1; } //SHOWPATH PROBLE? void target_laser_think (edict_t *self); // draws a translucent line from spos to epos edict_t *DrawLine(edict_t *owner, vec3_t spos, vec3_t epos) { edict_t *beam; beam = G_Spawn(); beam->owner = owner; beam->spawnflags = 1 | 4; beam->classname = "path_beam"; beam->movetype = MOVETYPE_NONE; beam->solid = SOLID_NOT; beam->s.renderfx |= RF_BEAM|RF_TRANSLUCENT; beam->s.modelindex = 1; //gi.modelindex ("models/objects/gibs/skull/tris.md2"); // must be non-zero // set the beam diameter beam->s.frame = 4; // set the color (green) beam->s.skinnum = 0xd0d1d2d3; if (!beam->owner) beam->owner = beam; VectorSet (beam->mins, -8, -8, -8); VectorSet (beam->maxs, 8, 8, 8); beam->spawnflags |= 0x80000001; beam->svflags &= ~SVF_NOCLIENT; beam->flags |= FL_TEAMSLAVE; VectorCopy(spos, beam->s.origin); VectorCopy(epos, beam->s.old_origin); VectorSubtract(epos, spos, beam->movedir); // VectorNormalize2(beam->movedir, beam->movedir); beam->dmg = 0; beam->enemy = NULL; beam->think = target_laser_think; beam->nextthink = level.time + FRAMETIME; beam->think(beam); gi.linkentity (beam); return beam; } // Do team checking, which is different for CTF and non-CTF modes qboolean botOnSameTeam(edict_t *p1, edict_t *p2) { if (!ctf->value) return (p1->client->team == p2->client->team); else return (p1->client->resp.ctf_team == p2->client->resp.ctf_team); } /* ============ TeamGroup Issues a team grouping message, and alerts all bots accordingly ============ */ void TeamGroup(edict_t *ent) { int i, chat_line, chat_type; edict_t *closest, *trav; float best_dist; if (!ent->client->team && !ctf->value) return; if (ent->last_pain == level.time) chat_type = CHAT_TEAMPLAY_HELP; else chat_type = CHAT_TEAMPLAY_GROUP; chat_line = (int) (random() * (float) bot_chat_count[chat_type]); // find closest item closest = NULL; best_dist = 512; trav = weapons_head; while (trav) { if (trav->item && (entdist(trav, ent) < best_dist)) { closest = trav; } trav = trav->node_target; } if (!closest || (best_dist > 256)) { trav = bonus_head; while (trav) { if (trav->item && (trav->item->tag != ARMOR_SHARD) && (entdist(trav, ent) < best_dist)) { closest = trav; } trav = trav->node_target; } } if (!closest) // abort if no item is close by return; for (i=0; itarget_ent) || (random() < 0.4)))) { if (!players[i]->bot_client) { safe_cprintf(players[i], PRINT_CHAT, "%s: ", ent->client->pers.netname); safe_cprintf(players[i], PRINT_CHAT, bot_chat_text[chat_type][chat_line], closest->item->pickup_name); safe_cprintf(players[i], PRINT_CHAT, "\n"); } else if (!players[i]->target_ent) { // set bot to go straight to this player players[i]->target_ent = ent; } } } ent->group_pausetime = level.time + 10 + 5 * random(); if (ent->client->team) ent->client->team->last_grouping = level.time + 5 + (random() * 10); } // tells all following bots to disperse void TeamDisperse(edict_t *self) { int i, count=0; for (i=0; itarget_ent == self) { players[i]->target_ent = NULL; count++; } } if (count) { safe_cprintf(self, PRINT_CHAT, "%s: ", self->client->pers.netname); safe_cprintf(self, PRINT_CHAT, "all units disperse!\n"); } } void BotGreeting(edict_t *chat) { int i; if (!bot_chat->value) return; i = (int) (random() * (float) bot_chat_count[CHAT_GREETINGS]); my_bprintf(PRINT_CHAT, "%s: ", chat->owner->client->pers.netname); my_bprintf(PRINT_CHAT, bot_chat_text[CHAT_GREETINGS][i]); my_bprintf(PRINT_CHAT, "\n"); G_FreeEdict(chat); } void BotComeback(edict_t *self) { int i; if (!bot_chat->value) return; i = (int) (random() * (float) bot_chat_count[CHAT_COMEBACKS]); my_bprintf(PRINT_CHAT, "%s: ", self->owner->client->pers.netname); my_bprintf(PRINT_CHAT, bot_chat_text[CHAT_COMEBACKS][i], self->enemy->client->pers.netname); my_bprintf(PRINT_CHAT, "\n"); G_FreeEdict(self); } void BotInsultStart(edict_t *self) { // insult? if (fabs(self->owner->client->resp.score - self->enemy->client->resp.score) < 5) { // general insult if (last_bot_chat[CHAT_INSULTS_GENERAL] < (level.time - 5)) BotInsult(self->owner, self->enemy, CHAT_INSULTS_GENERAL); } else { if (self->owner->client->resp.score > self->enemy->client->resp.score) { // kickin ass if (last_bot_chat[CHAT_INSULTS_KICKASS] < (level.time - 5)) BotInsult(self->owner, self->enemy, CHAT_INSULTS_KICKASS); } else { if (last_bot_chat[CHAT_INSULTS_LOSING] < (level.time - 5)) BotInsult(self->owner, self->enemy, CHAT_INSULTS_LOSING); } } G_FreeEdict(self); } void BotInsult(edict_t *self, edict_t *enemy, int chat_type) { int i; if (!bot_chat->value) return; i = (int) (random() * (float) bot_chat_count[chat_type]); my_bprintf(PRINT_CHAT, "%s: ", self->client->pers.netname); my_bprintf(PRINT_CHAT, bot_chat_text[chat_type][i], enemy->client->pers.netname); my_bprintf(PRINT_CHAT, "\n"); last_bot_chat[chat_type] = level.time; if (enemy->bot_client && (random() < 0.3) && (last_bot_chat[CHAT_COMEBACKS] < (level.time - 3))) { edict_t *comeback; comeback = G_Spawn(); comeback->think = BotComeback; comeback->nextthink = level.time + 2 + random(); comeback->enemy = self; comeback->owner = enemy; last_bot_chat[CHAT_COMEBACKS] = level.time + 5; } } qboolean SameTeam(edict_t *plyr1, edict_t *plyr2) { // returns true if the 2 players are on the same team if (ctf->value) { return (plyr1->client->resp.ctf_team == plyr2->client->resp.ctf_team); } else { // neutral players attack anyone if (!plyr1->client->team || !plyr2->client->team) return false; return (plyr1->client->team == plyr2->client->team); } } float HomeFlagDist(edict_t *self) { edict_t *flag; if (self->client->resp.ctf_team == CTF_TEAM1) flag = flag1_ent; else flag = flag2_ent; return entdist(self, flag); } qboolean CarryingFlag(edict_t *ent) { // return true if ent is carrying the enemy flag /* if (!ctf->value) return false; if (!ent->client) return false; if (ent->client->resp.ctf_team == CTF_TEAM1) return ent->client->pers.inventory[ITEM_INDEX(flag2_item)]; else return ent->client->pers.inventory[ITEM_INDEX(flag1_item)]; */ return (ent->s.effects & (EF_FLAG1|EF_FLAG2)); }