/***************************************************************** 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- *****************************************************************/ // bot_ai.c #include "g_local.h" #include "m_player.h" #include "g_items.h" #include "p_trail.h" #include "bot_procs.h" mmove_t bot_move_attack; float last_roam_time; void CTFDropFlagTouch(edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf); void CTFFlagThink(edict_t *ent); void CTFGrappleFire (edict_t *ent, vec3_t g_offset, int damage, int effect); /* ======================= bot_roam This is the main searching routine, whichs looks around for enemies or items to head for. set "no_paths" to disable all path checking (faster) ======================= */ void bot_roam (edict_t *self, int no_paths) { edict_t *search, *closest, /**enemy,*/ *closest_nonvisible=NULL, *save_goal=NULL, *save_movetarget=NULL; float closest_dist, this_dist, nonvis_dist=999999; int player_index = 0; float save_suicide_time; int i; int n, carrying_flag; edict_t *flag, *enemy_flag; int flaggoal=false; // vec3_t vec; //testing // edict_t *blip; // float dist; // vec3_t v; // trace_t tr; // blip = NULL; //testing if (no_paths) { if (self->last_nopaths_roam > (level.time - (0.05*num_players + 0.1))) return; self->last_nopaths_roam = level.time; } else { if (self->last_roam_time > (level.time - (0.05*num_players*3 + 1))) return; } save_suicide_time = self->bored_suicide_time; self->bored_suicide_time = -1; // restored only if not found anything // look for a visible enemy closest = closest_nonvisible = NULL; closest_dist = 800; search = players[player_index]; // enable the following line to disable bots attacking others //if (false) if (no_paths && (!ctf->value || !self->enemy || !CarryingFlag(self->enemy))) { // don't attack someone else if current enemy has our flag! carrying_flag = false; while (player_index < num_players) { if ( !(search->flags & FL_NOTARGET) && (search != self) && (self->enemy != search) && (!search->disguised)&& (search->health > 0) && (search->bot_client || (search->light_level > 5)) && search->solid && !SameTeam(self, search) /*|| SameTeam(self, search) && (search->disease)&& (self->client->player_class == 2)&& (self->client->pers.inventory[ITEM_INDEX(item_shells)] != 0))*///Acrid Nurse // we can see this person, is it worth attacking them? && (!(search->client->invincible_framenum > level.framenum)) && ( ((self->client->quad_framenum > level.framenum) || self->client->invincible_framenum > level.framenum) || (ctf->value && (carrying_flag = CarryingFlag(search))) || !self->enemy || (self->enemy->health > search->health) || (search->client->pers.weapon == item_blaster))) // if Quad, or enemy is weaker than self { if (((carrying_flag) || ((this_dist = entdist(self, search)) < closest_dist)) && visible(self, search) && CanSee(self, search)) { // go for em!//fixme testing botDebugPrint("Attacking %s\n",search->classname);// search->client->pers.netname); self->enemy = search; closest_dist = this_dist; //I think don't go for any other goal but enemy if ( (carrying_flag) || (self->movetarget && ((entdist(self, self->movetarget) > 256) && ((search->health < 15) || (search->client->pers.weapon == item_blaster)) && ((self->bot_fire != botBlaster) && (self->bot_fire != botShotgun))) || ((self->bot_stats->aggr/5)*0.2 > random()))) { botDebugPrint(" - ABORTING MOVETARGET !!"); self->movetarget = NULL; if (carrying_flag) // go for flag carrier! break; } botDebugPrint("\n"); } else if (this_dist < nonvis_dist) { closest_nonvisible = search; nonvis_dist = this_dist; } } search = players[++player_index]; } } /////////////////////////////////////testing /* if(!self->enemy) { //Reduce range while (blip = findradius (blip, self->s.origin, 550)) { if (blip->solid == SOLID_NOT) continue; //don't see observers if (blip == self) continue; if (blip->wf_team == self->wf_team) continue; if (!blip->takedamage) continue; if (blip->disguised) continue; /* tr = gi.trace (self->s.origin, NULL, NULL, blip->s.origin, self, MASK_SOLID); if (tr.fraction != 1.0) continue; VectorSubtract (self->s.origin, blip->s.origin, v); dist = VectorLength(v); if (!visible(self, blip) && dist > 300) continue;*/ /* botDebugPrint("Attacking %s\n",blip->classname); self->enemy = blip; closest_dist = this_dist; self->movetarget = NULL; if (this_dist < nonvis_dist) { closest_nonvisible = blip; nonvis_dist = this_dist; } } }*/ /////////////////////////////////////////////////testing // only go beyond this point a few times per server frame if (roam_calls_this_frame > 10) return; if (no_paths) { roam_calls_this_frame++; } else // checking paths { self->last_roam_time = level.time; roam_calls_this_frame += 3; } if (ctf->value && CarryingFlag(self)) { // don't go for items if carrying flag, at enemy base, and there is an enemy around if (CarryingFlag(self) && (((int)wfflags->value & WF_ZOID_FLAGCAP) == 0)//$ && !(flagreturn1_ent == NULL || flagreturn2_ent == NULL))//$ {//$ botDebugPrint("zoid1\n"); if (self->client->resp.ctf_team == CTF_TEAM1)//$ {//$ flag = flagreturn1_ent;//$ enemy_flag = flag2_ent;//$ }//$ else//$ {//$ flag = flagreturn2_ent;//$ enemy_flag = flag1_ent;//$ }//$ }//$ else//$ {//$ botDebugPrint("normal1\n"); if (self->client->resp.ctf_team == CTF_TEAM1) { flag = flag1_ent; enemy_flag = flag2_ent; } else { flag = flag2_ent; enemy_flag = flag1_ent; } }//$ // we don't really need anything, and enemy is close if (self->enemy && (entdist(self, self->enemy) < 384) && (self->health > 25))//acrid fixme && (self->bot_fire != botBlaster)) return; // don't check paths if we have flag, unless at base if ((entdist(self, flag) > 800) || (flag->solid)) flaggoal = true; } ////////////// look for an item to get//////////////FIXME LOOK HERE ACRID if ( (!self->movetarget || (self->movetarget->routes) || (no_paths && (entdist(self->movetarget, self) > 512))) // if going for enemy flag, don't look for a new target && !(self->movetarget && self->movetarget->solid && self->movetarget->item && (self->movetarget->item->pickup == CTFPickup_Flag) && (self->movetarget->count != self->client->resp.ctf_team) && (entdist(self->movetarget, self) < 600)) && ((self->bot_fire == botBlaster) || (((0.3 * self->bot_stats->aggr) / 5.0) < random()))) // if really aggressive, don't always look for items { closest_dist = 999999; //aco // botDebugPrint("%s looking for an item.. ", self->client->pers.netname); // if (no_paths) // botDebugPrint("(no paths)\n"); // else // botDebugPrint("(checking paths)\n"); self->save_movetarget = self->movetarget; // used in RoamFindBestWeapon() self->save_goalentity = self->goalentity; save_goal = self->goalentity; save_movetarget = self->movetarget; // look for health this_dist = RoamFindBestItem(self, health_head, !no_paths); if (this_dist > -1) { closest_dist = this_dist; // go for the item if (self->health < 10) { goto gotgoal; } // we kinda need health else if (self->health < 50) { this_dist = this_dist / 3; } save_goal = self->goalentity; save_movetarget = self->movetarget; if (no_paths && (this_dist < 128)) { // this will do goto gotgoal; } } // look for weapons//look here might be able to use this for depots/healing stations this_dist = RoamFindBestItem(self, weapons_head, !no_paths); if ((this_dist > -1) && (this_dist < closest_dist)) { closest_dist = this_dist; save_goal = self->goalentity; save_movetarget = self->movetarget; if (no_paths && (this_dist < 128)) { // this will do goto gotgoal; } } else // restore goals { self->goalentity = save_goal; self->movetarget = save_movetarget; } // look for bonuses this_dist = RoamFindBestItem(self, bonus_head, !no_paths); if ((this_dist > -1) && (this_dist < closest_dist))/* && //acrid (self->goalentity->wf_team == self->wf_team) &&(self->goalentity != flag))*///acrid added fixme fixme fixme for armor { // go for this item instead closest_dist = this_dist; save_goal = self->goalentity; save_movetarget = self->movetarget; if (no_paths && (this_dist < 128)) { // this will do goto gotgoal; }//REF goal->item->pickup == CTFPickup_Flag }//whole acrid section//FIXME LOOK HERE ACRID /* else if ((this_dist > -1) && (this_dist < closest_dist) && (self->goalentity == flag))//acrid added fixme { // go for this item instead closest_dist = this_dist; save_goal = self->goalentity; save_movetarget = self->movetarget; botDebugPrint(" flagger\n"); if (no_paths && (this_dist < 128)) { // this will do goto gotgoal; } }*/ else // restore goals { self->goalentity = save_goal; self->movetarget = save_movetarget; } this_dist = RoamFindBestItem(self, ammo_head, !no_paths); if (this_dist > closest_dist) // revert back to ammo targets { self->goalentity = save_goal; self->movetarget = save_movetarget; } else { closest_dist = this_dist; } gotgoal: if (self->movetarget) { if (closest_dist < 999999) { if (flaggoal) { if (entdist(self->movetarget, self) > 128) { // restore goals self->movetarget = self->save_movetarget; self->goalentity = self->save_goalentity; } } else { // botDebugPrint("%s going for %s ", self->client->pers.netname, self->movetarget->classname); // botDebugPrint("(%i)\n", (int)closest_dist); } } } } ////////////////////////////////////////////////////////////////////////// if (no_paths) // don't go passed here return; if (self->movetarget || self->enemy) {//botDebugPrint("RETURN if self-enemy\n"); return; } if (ClosestNodeToEnt(self, false, true) == -1) { // can't get to a node goto roam_no_node; } else if (self->target_ent) { // no need to roam, just go for the target_ent return; } // walk around aimlessly, if possible // pick a random node and go for it, hopefully once we get there, it'll lead us to sometihng else for (n=0; n<5; n++) // try 10 different nodes { i = (int) ((trail_head-1) * random()) + 1; if (!trail[i]->timestamp) continue; if (entdist(self, trail[i]) < 800) continue; if (PathToEnt(self, trail[i], false, false) == -1) continue; if ((PathToEnt_Node->enemy == self) && (PathToEnt_Node->ignore_time > level.time)) continue; botDebugPrint("Roaming towards node #%i\n", i); // found a distant node self->movetarget = trail[i]; self->goalentity = PathToEnt_Node; return; } roam_no_node: self->last_roam_time = level.time + 2; // don't roam again for a few seconds self->movetogoal_time = level.time + 2; botRoamFindBestDirection(self); // nothing to do, if we've been lost for long enough, suicide // self->enemy = self->goalentity = self->movetarget = NULL; self->bored_suicide_time = save_suicide_time; } /* ============= bot_run This is the core thinking routine ============= */ void G_SetClientEffects (edict_t *ent); void bot_run (edict_t *self) { float dist; vec3_t vec; edict_t *old_goal; botDebugPrint("frozen #%i\n",self->frozen); botDebugPrint("frozen time #%i\n",self->frozentime); if (self->frozen)//botfreeze return; if (self->health <= 0) { self->s.modelindex2 = 0; self->s.modelindex3 = 0; return; } if (self->waterlevel > 2) self->flags |= FL_SWIM; // so walkmove() works else self->flags &= ~FL_SWIM; // fix: somehow non-client's are becoming enemies, which causes crashes if (self->enemy && !self->enemy->client && ((strcmp(self->enemy->classname, "SentryGun") != 0) || (strcmp(self->enemy->classname, "turret") != 0))) self->enemy = NULL;//Acrid CTFEffects(self); CTFApplyRegeneration(self); /* if ((self->client->resp.ctf_flagsince > 0) && (self->client->resp.ctf_flagsince > (level.time - 10))) dist = 0; */ // do Quad Damage AI if ((self->client->quad_framenum > level.framenum) || self->client->invincible_framenum > level.framenum) { bot_roam(self, true); // keep scanning for visible enemies, even though we may be chasing down a current enemy } else { //Acrid removed old Co code bot_roam(self, true); //Acrid removed old Co code } G_SetClientEffects(self); // support infinite AMMO if ((int)dmflags->value & DF_INFINITE_AMMO) { if (self->client->ammo_index) self->client->pers.inventory[self->client->ammo_index] = 999; } // check for taunting if ((self->s.frame <= FRAME_point12) && (self->s.frame >= FRAME_salute01)) { if ((skill->value >= 3) && self->enemy && gi.inPVS(self->s.origin, self->enemy->s.origin)) // abort taunt { self->s.frame = FRAME_run1; } else { bot_ChangeYaw(self); bot_ChangeYaw(self); return; } } // this is the default moving distance per frame dist = (float) BOT_RUN_SPEED * bot_frametime; // ducking if (self->goalentity && (self->goalentity->maxs[2] < 32) && (self->goalentity->routes)) { if (self->maxs[2] != self->goalentity->maxs[2]) { if (self->maxs[2] < 32) { // try to stand if (CanStand(self)) self->maxs[2] = self->goalentity->maxs[2]; } else // duck { self->maxs[2] = self->goalentity->maxs[2]; } } } // move slower if ducking if (self->maxs[2] == 4) { dist *= 0.5; self->viewheight = -2; } else { self->viewheight = 22; /* if ((self->waterlevel > 2) || (!self->groundentity && (self->waterlevel > 0))) { dist *= 0.75; } */ } ////////////////////////////////////////////////////////////////////////// // GRAPPLE if (self->client->ctf_grapple) { vec3_t oldorg; // so we don't fire immediately after switching from grapple self->last_fire = level.time + BOT_CHANGEWEAPON_DELAY; VectorCopy(self->s.origin, oldorg); CTFGrapplePull(self->client->ctf_grapple); // set visible model vwep with 3.20 code ShowGun(self); // it could have been killed in CTFGrapplePull() if (self->client->ctf_grapple) { // face in direction of grapple if (self->groundentity) { VectorSubtract( self->client->ctf_grapple->s.origin, self->s.origin, vec); VectorNormalize(vec); self->s.angles[YAW] = vectoyaw(vec); } if (self->client->ctf_grapplestate > CTF_GRAPPLE_STATE_FLY) { // gravity will be applied in gi.Pmove() self->velocity[2] += sv_gravity->value*FRAMETIME; VectorCopy(self->velocity, self->jump_velocity); // so we move really fast, and don't do any course adjustments self->groundentity = NULL; if (CanJump(self)) { self->s.origin[2] += 1; gi.linkentity(self); } bot_move(self, dist); if (self->client->ctf_grapple) { static vec3_t goal_dir, grapple_dir, diff_dir; static edict_t *nextgoal; qboolean abort_grapple=false; // if grapple is not in direction of goal, abort if (self->goalentity && (self->groundentity || self->waterlevel > 1) && self->last_movegoal && (self->goalentity->node_type != NODE_LANDING)) { VectorSubtract(self->client->ctf_grapple->s.origin, self->s.origin, grapple_dir); VectorNormalize(grapple_dir); VectorSubtract(self->goalentity->s.origin, self->s.origin, goal_dir); VectorNormalize(goal_dir); VectorSubtract(grapple_dir, goal_dir, diff_dir); if (VectorLength(diff_dir) > 0.25) { // see if the next goal is ahead abort_grapple = true; if (PathToEnt(self->goalentity, self->last_movegoal, false, false) > -1) { VectorSubtract(PathToEnt_Node->s.origin, self->s.origin, goal_dir); VectorNormalize(goal_dir); VectorSubtract(grapple_dir, goal_dir, diff_dir); if (VectorLength(diff_dir) < 0.25) { self->goalentity = PathToEnt_Node; abort_grapple = false; } } } } if ( (abort_grapple) || ((fabs(self->client->ctf_grapple->s.origin[2] - (self->s.origin[2])) < 40) && (entdist(self->client->ctf_grapple, self) < (44*(((!self->goalentity) || (self->goalentity->node_type != NODE_LANDING)) + 1)))) || (self->client->ctf_grapplestart < (level.time - 3))) { // give up, or drop to floor CTFResetGrapple(self->client->ctf_grapple); // set visible model vwep with 3.20 code ShowGun(self); // update view model if (self->goalentity && (PathToEnt(self->goalentity, self->last_movegoal, false, false) > -1)) { VectorSubtract(PathToEnt_Node->s.origin, self->s.origin, self->velocity); VectorNormalize(self->velocity); VectorScale(self->velocity, 240, self->velocity); self->goalentity = PathToEnt_Node; } else { AngleVectors(self->s.angles, vec, NULL, NULL); VectorScale(vec, 80, self->velocity); VectorCopy(self->velocity, self->jump_velocity); } self->goalentity = NULL; } } return; } else // only allow the bot to move while firing grapple under certain conditions { if ( (self->waterlevel > 2) || (self->goalentity && (fabs(self->s.origin[2] - self->client->ctf_grapple->s.origin[2]) < 128))) { bot_move(self, dist); return; } if (self->goalentity && self->goalentity->s.origin[2] > self->s.origin[2]) return; } } } // END GRAPPLE // are these still required?? if (self->enemy == world) self->enemy = NULL; if (self->enemy == self) self->enemy = NULL; // teamplay, prevent attacking someone on our team if (self->enemy && self->enemy->client && SameTeam(self, self->enemy) && ((strcmp(self->enemy->classname, "SentryGun") != 0) || (strcmp(self->enemy->classname, "turret") != 0))) self->enemy = NULL; // ACRID NURSE HEALING /*if((self->client->player_class == 2) && (self->client->pers.inventory[ITEM_INDEX(FindItem("Shells"))] > 0))*/ // this stuff checks our current movetarget, to see if we should still be going for it if (self->movetarget && (self->movetarget->item)) { // check that the movetarget is still reachable if ( (self->movetarget->solid != SOLID_TRIGGER) // can't get it anymore // if this item is REALLY desirable, hover around it, waiting for it to respawn && ( (self->movetarget_want < WANT_SHITYEAH) || ( (self->movetarget->nextthink > (level.time + 4)) && ( (self->movetarget->item->pickup != CTFPickup_Flag) || !CarryingFlag(self)))))//ACRID LOOK HERE FOR FLAG GUARDING { self->last_roam_time = 0; self->movetarget = NULL; } else if (self->movetarget->item->pickup == Pickup_Health) { // check that we still need this health if (!(self->movetarget->style & HEALTH_IGNORE_MAX)) if (self->health >= self->max_health) self->movetarget = NULL; }//flagref else if (self->movetarget->item->pickup == CTFPickup_Flag) { if (self->movetarget == self->target_ent) { if (entdist(self->movetarget, self) < 384) self->movetarget = NULL; } else if (self->movetarget->solid != SOLID_TRIGGER) { if (entdist(self->movetarget, self) < 256) self->movetarget = NULL; } } } // check for dead enemy if (self->enemy && (self->enemy->health <= 0) && (self->groundentity) && self->enemy->client) { // enemy is dead, taunt if no enemies around self->goalentity = NULL; // check for the enemy dropping a weapon RoamFindBestItem(self, weapons_head, false); if ((self->bot_fire != botBlaster) && (entdist(self, self->enemy) < 384) && (random() < 0.5)) { vec3_t vec; int in_range=false; VectorSubtract(self->enemy->s.origin, self->s.origin, vec); if (!in_range) { // taunt float rnd; edict_t *chat; chat = G_Spawn(); chat->owner = self; chat->enemy = self->enemy; chat->think = BotInsultStart; chat->nextthink = level.time + 1.5 + random(); // face towards player self->ideal_yaw = vectoyaw(vec); rnd = random() * 3; if (rnd < 1) { self->s.frame = FRAME_taunt01; self->radius_dmg = FRAME_taunt17; } else if (rnd < 2) { self->s.frame = FRAME_salute01; self->radius_dmg = FRAME_salute11; } else { self->s.frame = FRAME_point01; self->radius_dmg = FRAME_point12; } return; } } self->enemy = NULL; if (!self->movetarget) { self->last_roam_time = 0; // force check for new enemy self->last_nopaths_roam = 0; self->search_time = 0; bot_roam(self, false); } } //////////////////////////////////check for dead end////////////////////// if (!self->enemy && !self->movetarget && !self->target_ent) self->goalentity = NULL; old_goal = self->goalentity; // if roaming aimlessly, and we have found an enemy, aborting roaming if (self->movetarget && self->movetarget->routes && self->enemy) // we have an enemy! { if (PathToEnt(self, self->enemy, false, false) > -1) // make sure we can reach it still { self->goalentity = PathToEnt_Node; self->movetarget = NULL; } else // no path, so abort enemy { self->enemy = NULL; } } //fixme acrid remove bot blaster dumb lines called alot might be a prob if ((!self->movetarget || self->movetarget->routes) && (!self->enemy || (self->bot_fire == botBlaster))) { edict_t *oldgoalentity = self->goalentity; bot_roam(self, false); //aco if (self->target_ent) self->goalentity = oldgoalentity; } if (!self->enemy && !self->movetarget && !self->target_ent) { botRoamFindBestDirection(self); bot_move(self, dist); } else // we have a goal, so go for it, and attack if necessary { // set ideal yaw if ((self->last_ladder_touch > (level.time - 0.2)) && self->goalentity) {botDebugPrint("else 1\n"); VectorSubtract(self->goalentity->s.origin, self->s.origin, vec); self->ideal_yaw = vectoyaw(vec); } else if (!self->groundentity && self->goalentity && ((self->goalentity->s.origin[2] - 80) > self->s.origin[2])) {botDebugPrint("else 2\n"); VectorSubtract(self->goalentity->s.origin, self->s.origin, vec); self->ideal_yaw = vectoyaw(vec); } else if (self->goalentity && self->goalentity->goalentity && self->waterlevel && (self->waterlevel < 3) && (!self->enemy || (entdist(self, self->enemy) > 256))) {botDebugPrint("else 3\n"); VectorSubtract(self->goalentity->goalentity->s.origin, self->goalentity->s.origin, vec); self->ideal_yaw = vectoyaw(vec); } else if (self->enemy && ((entdist(self, self->enemy) < 600) || (gi.inPVS(self->s.origin, self->enemy->s.origin)))) { VectorSubtract(self->enemy->s.origin, self->s.origin, vec); self->ideal_yaw = vectoyaw(vec); } else if (self->target_ent && (self->s.frame <= FRAME_stand40) && (entdist(self->target_ent, self) < 600)) {//self standing still acrid VectorSubtract(self->s.origin, self->target_ent->s.origin, vec); self->ideal_yaw = vectoyaw(vec); }//called alot must be >2 else if (self->goalentity && (entdist(self, self->goalentity) > 2)) { VectorSubtract(self->goalentity->s.origin, self->s.origin, vec); self->ideal_yaw = vectoyaw(vec); } bot_ChangeYaw(self); bot_MoveAI(self, dist); bot_SuicideIfStuck(self); if (self->enemy) { bot_Attack(self); } else { if ((self->health > 0) && (self->s.frame >= FRAME_attack1) && (self->s.frame <= FRAME_attack8)) { // get out of attack frames self->s.frame = FRAME_run1; } } } if (self->goalentity && (self->goalentity->node_type == NODE_PLAT)) { // check for platform pausing, while going upwards if (self->groundentity && (self->groundentity->use == Use_Plat) && (self->groundentity->moveinfo.state == STATE_UP)) { // wait for plat to rise/fall self->bot_plat_pausetime = level.time + 0.3; goto noabortcheck; } } // keep track of each time our goalentity changes if (self->goalentity != self->giveup_lastgoal) { self->giveup_lastgoal = self->goalentity; self->last_reached_trail = level.time; } if (!self->client->ctf_grapple && self->goalentity /*&& self->goalentity->routes*/ && ((self->last_movegoal != self->enemy) || (self->last_enemy_sight < (level.time - 0.5)))) // if we've been going for this node for too long, give up { if ( ((self->last_reached_trail < (level.time - 2)) && (entdist(self, self->goalentity) > 128)) || (self->last_reached_trail < (level.time - 4))) { // edict_t *goal; if (self->movetarget) { //acrid co botDebugPrint("Giving up search for %s\n", self->movetarget->classname); self->movetarget->ignore_time = level.time + 3; self->movetarget->enemy = self; self->movetarget = NULL; } if (self->enemy) { //acrid co botDebugPrint("Giving up search for %s\n", self->enemy->client->pers.netname); self->enemy->ignore_time = level.time + 1; self->enemy->enemy = self; self->enemy = NULL; } self->goalentity->ignore_time = level.time + 0.5; self->goalentity->enemy = self; self->goalentity = NULL; bot_roam(self, false); // go for the new target for at least a few seconds self->last_reached_trail = level.time; } } noabortcheck: if (self->movetarget && !self->movetarget->item && (self->last_roam_time < (level.time - 5))) { // look for new targets, since we're roaming bot_roam(self, false); } } void bot_ChangeYaw(edict_t *self) { // this used to do special handling M_ChangeYaw(self); }; void BotLadderEnd(edict_t *self) { // end of ladder, jump forward vec3_t dir; self->last_ladder_touch = -1; AngleVectors(self->s.angles, dir, NULL, NULL); VectorScale(dir, 240, self->velocity); self->velocity[2] = 300; VectorCopy(self->velocity, self->jump_velocity); }; qboolean botTouchingLadder(edict_t *self) { vec3_t org; VectorCopy(self->s.origin, org); org[2] += 48; if (gi.pointcontents(org) & CONTENTS_LADDER) return true; org[0] += 8; org[1] += 8; if (gi.pointcontents(org) & CONTENTS_LADDER) return true; org[0] += -16; if (gi.pointcontents(org) & CONTENTS_LADDER) return true; org[1] += -16; if (gi.pointcontents(org) & CONTENTS_LADDER) return true; org[0] += 16; if (gi.pointcontents(org) & CONTENTS_LADDER) return true; org[0] -= 8; org[1] += 8; org[2] -= 48; if (gi.pointcontents(org) & CONTENTS_LADDER) return true; return false; }; void bot_MoveAI(edict_t *self, int dist) { vec3_t dir, angle; edict_t *goal=NULL; int goal_in_range = false; float goal_dist; int done_move=false; //gi.dprintf("%s moving (%i)\n", self->map, (int) entdist(self, the_client)); // check platform riding if (self->groundentity && (self->goalentity) && (self->goalentity->node_type == NODE_PLAT)) { if (self->groundentity->use == Use_Plat) { if (self->groundentity->moveinfo.state == STATE_UP) { // make sure we keep waiting vec3_t center; float length; self->bot_plat_pausetime = level.time + 0.3; // walk towards the center VectorCopy(self->goalentity->s.origin, center); center[2] = self->s.origin[2]; VectorSubtract(center, self->s.origin, dir); if ((length = VectorLength(dir)) > 12) { VectorNormalize(dir); M_walkmove(self, vectoyaw(dir), 10); } else { VectorNormalize(dir); M_walkmove(self, vectoyaw(dir), length); } } else if (self->groundentity->moveinfo.state == STATE_BOTTOM) { // move towards the center of the platform qboolean activator_set=false; vec3_t center; if (!self->activator) // HACK: to walk in ideal_yaw direction, bot_move() likes to see a self->activator { self->activator = self->goalentity; activator_set = true; } // find center of plat, at current Z VectorSubtract(self->groundentity->maxs, self->groundentity->mins, dir); VectorMA(self->groundentity->mins, 0.5, dir, center); center[2] = self->s.origin[2]; VectorSubtract(center, self->s.origin, dir); vectoangles(dir, angle); self->ideal_yaw = angle[1]; bot_move(self, dist); if (activator_set) self->activator = NULL; } } // if it's not ready, wait here for a bit else if (self->goalentity->target_ent->moveinfo.state != STATE_BOTTOM) { self->bot_plat_pausetime = level.time + 0.3; if (entdist(self, self->goalentity) < 200) { if (self->last_goal && (self->last_goal != self->goalentity)) { // face towards old goal // if (entdist(self, self->last_goal) > 10) // { VectorSubtract(self->last_goal->s.origin, self->goalentity->s.origin, dir); VectorNormalize2(dir, dir); vectoangles(dir, angle); self->s.angles[1] = angle[1]; if (!M_walkmove(self, self->s.angles[1], 10)) if (!M_walkmove(self, self->s.angles[1]+45, 10)) if (!M_walkmove(self, self->s.angles[1]-45, 10)) if (!M_walkmove(self, self->s.angles[1]+100, 10)) M_walkmove(self, self->s.angles[1]-100, 10); // } } else { // move backwards M_walkmove(self, self->s.angles[1] + 180, 10); } } } if (self->bot_plat_pausetime > level.time) return; } else if (self->bot_plat_pausetime > -1) { // set new goalentity, since we're at the top of the platform /* if (self->movetarget) goal = self->movetarget; else if (self->enemy) goal = self->enemy; else goal = NULL; if (goal && (PathToEnt(self, goal, false, true) > -1)) { self->goalentity = PathToEnt_Node; } */ // self->goalentity = NULL; self->bot_plat_pausetime = -1; } // make sure we crouch when we have to if (self->goalentity && self->goalentity->routes && (self->goalentity->maxs[2] < 32)) { // ^^ don't set maxs to that of an item if (self->maxs[2] != self->goalentity->maxs[2]) { if (self->maxs[2] < 32) { // try to stand if (CanStand(self)) self->maxs[2] = self->goalentity->maxs[2]; } else // duck { self->maxs[2] = self->goalentity->maxs[2]; } } } else if (self->movetarget && (self->goalentity == self->movetarget) && self->movetarget->movetarget && (self->movetarget->movetarget->maxs[2] < 32)) { // duck self->maxs[2] = 4; } else if (self->maxs[2] < 32 && (self->crouch_attack_time < level.time)) { if (CanStand(self)) self->maxs[2] = 32; } if (!self->groundentity && !self->waterlevel) { // in-air, also check for ladder movement vec3_t dir; if ((self->goalentity) && ((self->goalentity->s.origin[2]+8) > self->s.origin[2]) && botTouchingLadder(self)) { AngleVectors(self->goalentity->s.angles, dir, NULL, NULL); VectorScale(dir, 48, self->velocity); self->velocity[2] = 310; self->last_ladder_touch = level.time; } else if (self->last_ladder_touch > (level.time - 0.3)) { BotLadderEnd(self); } bot_move(self, dist); return; } else if (self->last_ladder_touch > (level.time - 0.3)) { BotLadderEnd(self); bot_move(self, dist); return; } if (self->activator) { // going for a button if (self->activator_time < (level.time - 5)) { // abort it self->activator = self->goalentity = NULL; } /* else if ((self->activator->moveinfo.state == STATE_BOTTOM) && !CarryingFlag(self)) { float dist1; vec3_t postvec; VectorSubtract(self->activator->absmin, self->s.origin, dir); dist1 = VectorLength(dir); VectorNormalize2(dir, dir); self->ideal_yaw = vectoyaw(dir); bot_move(self, dist); // did we move towards the button? VectorSubtract(self->activator->absmin, self->s.origin, postvec); if (VectorLength(postvec) >= dist1) self->activator = NULL; return; } else { self->activator = self->goalentity = NULL; } */ // get to the button goal = self->activator; goto got_goal; } // We're kinda lost, so walk in the ideal_yaw direction, and randomly jump if stuck if (self->movetogoal_time > level.time) { vec3_t oldorg; VectorCopy(self->s.origin, oldorg); bot_move(self, dist); VectorSubtract(self->s.origin, oldorg, oldorg); // jump randomly, so as not to get hung up if (VectorLength(oldorg) < 3) { self->movetogoal_time = 0; /* if ((random() < 0.5) && (CanJump(self))) { botDebugPrint("MoveToGoal: random jump\n"); botRandomJump(self); } */ } return; } // HACK, make sure we head for home if we have the flag! if (ctf->value && CarryingFlag(self))// && // (!self->movetarget || !self->movetarget->item || (self->movetarget->item->pickup != CTFPickup_Flag))) { edict_t *flag, *enemy_flag, *plyr; int i=0, count=0, ideal; static float last_checkhelp=0; if (CarryingFlag(self) && (((int)wfflags->value & WF_ZOID_FLAGCAP) == 0)//$ && !(flagreturn1_ent == NULL || flagreturn2_ent == NULL))//$ {//$ botDebugPrint("zoid2\n"); if (self->client->resp.ctf_team == CTF_TEAM1)//$ {//$ flag = flagreturn1_ent;//$ enemy_flag = flag2_ent;//$ }//$ else//$ {//$ flag = flagreturn2_ent;//$ enemy_flag = flag1_ent;//$ }//$ }//$ else//$ {//$ botDebugPrint("normal2\n"); if (self->client->resp.ctf_team == CTF_TEAM1) { flag = flag1_ent; enemy_flag = flag2_ent; } else { flag = flag2_ent; enemy_flag = flag1_ent; } }//$ // look for some helpers // if (last_checkhelp < (level.time - 0.5)) { for (i=0; iclient->resp.ctf_team != self->client->resp.ctf_team) { if ( (plyr->enemy != self) // && (!plyr->target_ent || (plyr->target_ent->think != CTFFlagThink) || (entdist(plyr, plyr->target_ent) > 1000)) && (entdist(plyr, self) < 2000)) { // send this enemy to us plyr->enemy = self; } // continue; } else if ((plyr != self) && (plyr->target_ent == self)) count++; } ideal = ((int)ceil((1.0*(float)num_players)/4.0)); if (count < ideal) { for (i=0; (iclient->resp.ctf_team != self->client->resp.ctf_team) continue; if (plyr->target_ent == self) continue; if (entdist(plyr, self) > 700) continue; if (!gi.inPVS(plyr->s.origin, self->s.origin)) continue; plyr->target_ent = self; if (++count >= ideal) break; } } else if (count > ideal) // release a defender { for (i=0; (iclient->resp.ctf_team != self->client->resp.ctf_team) continue; if (plyr->target_ent != self) continue; plyr->target_ent = NULL; break; } } last_checkhelp = level.time + random()*0.5; } //flagref if ((flag->solid == SOLID_TRIGGER) || (entdist(self, flag) > 700) || CarryingFlag(self))// FLAG CAPPING FIX ACRID { if ( (!self->movetarget) || ( (self->movetarget != flag) && ( (entdist(self->movetarget, self) > 128) || (entdist(self, enemy_flag) < 1000)))) { self->movetarget = flag; } if (self->movetarget == flag) { if ((self->target_ent == flag) && (flag->solid == SOLID_TRIGGER)) self->target_ent = NULL; botDebugPrint("movetarg flag2\n"); goal = flag; goto got_goal; } } else if (self->movetarget && self->movetarget->item && (self->movetarget->item->pickup == CTFPickup_Flag)) // do something else? { self->movetarget = NULL; } self->target_ent = NULL; } // ------------------------------------------------------------ // OK, now choose which goal to head for (enemy or movetarget) // and go for it. goal = NULL; // always head for flag carrier //fixme acrid pain bots getting attacked by other players if (self->enemy && !CarryingFlag(self) && CarryingFlag(self->enemy)) { goal = self->enemy; self->last_seek_enemy = level.time; goto got_goal; } // start off with the movetarget, then overwrite if necessary if (self->movetarget) { goal = self->movetarget; if (goal->touch == CTFDropFlagTouch) goto got_goal; else if (((strcmp(goal->classname, "item_flagreturn_team1") == 0) ||//$ (strcmp(goal->classname, "item_flagreturn_team2") == 0)) &&//$ CarryingFlag(self) && !(flagreturn1_ent == NULL || flagreturn2_ent == NULL))//$ goto got_goal;//$ else if (goal->item && (goal->item->pickup == CTFPickup_Flag) && goal->solid && (goal->count != self->client->resp.ctf_team) && (entdist(goal, self) < 1000)) goto got_goal; } if (self->enemy && (!CarryingFlag(self) || !goal) && ((!ctf->value || !self->movetarget || !self->movetarget->item || (self->movetarget->item->pickup != CTFPickup_Flag)))) // attacking enemy, may be going for an item also, decide which to go for { if (! ( (goal) && ( (self->movetarget_want >= WANT_SHITYEAH) || (self->bot_fire == botBlaster) || (self->bot_fire == botShotgun) || ((goal_dist = entdist(self->movetarget, self)) < 512) || (self->enemy->health > self->health) || (goal_dist < entdist(self, self->enemy)) ) ) || ( ctf->value && (HomeFlagDist(self) < 1500)) // defend base! ) { // enemy is best goal goal = self->enemy; self->last_seek_enemy = level.time; } } // check for team goal if ( (self->target_ent && !CarryingFlag(self)) && (!self->enemy || !CarryingFlag(self->enemy)) && ( !goal || (self->target_ent->client) || ( (goal == self->movetarget) && ((entdist(goal, self->target_ent) / self->movetarget_want) > BOT_GUARDING_RANGE)))) { if (self->target_ent->client) { if (self->target_ent->health <= 0) { // leader is dead, resume normal roaming self->target_ent = NULL; } else if (!self->movetarget || (entdist(self->target_ent, self->movetarget) > 400)) // must follow leader! { if (self->movetarget) { self->movetarget->ignore_time = level.time + 2; self->movetarget = NULL; } goal = self->target_ent; } } else if (self->target_ent->item) // defending an item (CTF flag, etc) { if (self->bot_fire == botBlaster) { } if ((entdist(self, self->target_ent) > BOT_GUARDING_RANGE) || !gi.inPVS(self->s.origin, self->target_ent->s.origin)) { // go to our target_ent goal = self->target_ent; self->movetarget = NULL; } else // hang around for a bit { self->group_pausetime = level.time + 1; self->checkstuck_time = level.time; if (self->movetarget == self->target_ent) self->movetarget = NULL; } } } got_goal: // flagpath stuff if (self->flagpath_goal) {botDebugPrint("flaggoalpath\n"); if (CarryingFlag(self)) goal = self->flagpath_goal; else self->flagpath_goal = NULL; } // end: flagpath stuff self->last_movegoal = goal; // if enemy has moved out of view, look for a new goal if ( (goal) && (goal->client)//fixme? && (self->goalentity == goal) && !visible_box(self, goal)) { botDebugPrint("goal->client\n"); self->goalentity = NULL; } // ------------------------------------------------------------ ////////////////////////////////////////////////////////////////////////// // check for a node jump if (goal && self->goalentity && self->goalentity->routes && self->goalentity->goalentity) if (entdist(self->goalentity, self) < 32)//fix this? if (CanJump(self)) { if ((PathToEnt(self->goalentity, goal, false, false) > -1) && (PathToEnt_Node == self->goalentity->goalentity)) { // jumping from here, will get us closer to the goal vec3_t jump_vec, org; trace_t trace; int i; VectorCopy(self->goalentity->velocity, jump_vec); jump_vec[2] = 0; // grapple?//Acrid FIXME this is the prob if ((self->goalentity->node_type == NODE_NORMAL)//FIXME ACRID WAS NODE_GRAPPLE && (self->goalentity->s.origin[2] <= PathToEnt_Node->s.origin[2])) //&& ((self->client->player_special & SPECIAL_GRAPPLE) != 0))//future fix { // find the target point, and face towards it VectorScale(self->goalentity->velocity, 8000, org); VectorAdd(self->goalentity->s.origin, org, org); trace = gi.trace(self->goalentity->s.origin, NULL, NULL, org, NULL, MASK_SOLID); VectorSubtract(trace.endpos, self->s.origin, jump_vec); VectorNormalize(jump_vec); vectoangles(jump_vec, self->client->v_angle); CTFGrappleFire (self, vec3_origin, 10, 0); // set visible model vwep with 3.20 code ShowGun(self); self->goalentity = PathToEnt_Node; return; } if (self->movetarget && (entdist(self, self->movetarget) < 64)) { // movetarget is in range, so jump towards it VectorSubtract(self->movetarget->s.origin, self->goalentity->s.origin, dir); dir[2] = 0; VectorScale(dir, 2, dir); } else if (VectorLength(jump_vec) > 80) { // the velocity of the jump node is good enough VectorCopy(self->goalentity->velocity, dir); } else { // just jump in the direction we're going //Acrid removed old co code if ((self->goalentity->goalentity->s.origin[2] - 64) < self->goalentity->s.origin[2]) { if (self->goalentity->waterlevel && !self->goalentity->goalentity->waterlevel) { // coming out of water, jump in the direction the goal is facing AngleVectors(self->goalentity->s.angles, dir, NULL, NULL); VectorScale(dir, 240, dir); dir[2] = 310; goto gotvel; } else { VectorSubtract(self->goalentity->goalentity->s.origin, self->goalentity->s.origin, jump_vec); } } else // must be a ladder { AngleVectors(self->goalentity->s.angles, jump_vec, NULL, NULL); VectorScale(jump_vec, 10, jump_vec); } jump_vec[2] = 0; if (VectorLength(jump_vec) > 8) VectorCopy(jump_vec, dir); else { AngleVectors(self->s.angles, dir, NULL, NULL); } dir[2] = 0; VectorNormalize2(dir, dir); VectorScale(dir, 50, dir); // } } dir[2] = 0; if (VectorLength(dir) > 300) { VectorNormalize2(dir, dir); VectorScale(dir, 300, dir); } else if (VectorLength(dir) < 20) { VectorNormalize2(dir, dir); VectorScale(dir, 20, dir); } dir[2] = self->goalentity->velocity[2]; gotvel: if (dir[2] < 80) { // if directly ahead is blocked we should jump higher static vec3_t joffs = {0,0,8}; vec3_t vec, org; VectorAdd(self->s.origin, joffs, org); VectorCopy(dir, vec); vec[2] = 0; VectorNormalize2(vec, vec); VectorScale(vec, 32, vec); VectorAdd(org, vec, vec); trace = gi.trace(org, VEC_ORIGIN, VEC_ORIGIN, vec, self, MASK_SOLID); if (trace.fraction < 1) dir[2] = 310; else if ((self->goalentity->goalentity->s.origin[2] + 96) < self->goalentity->s.origin[2]) { VectorSubtract(self->goalentity->goalentity->s.origin, self->goalentity->s.origin, jump_vec); jump_vec[2] = 0; if (VectorLength(jump_vec) < 96) { VectorNormalize2(jump_vec, dir); VectorScale(dir, 150, dir); dir[2] = 160; } } } else { dir[2] += 80; } VectorCopy(dir, self->velocity); if (self->velocity[2] > 191) { self->velocity[2] = 300; } else // ladder? { if (botTouchingLadder(self) && (self->goalentity->goalentity->s.origin[2] > self->goalentity->s.origin[2])) { self->velocity[2] = 300; } else if (self->velocity[2] < 40) { self->velocity[2] = 40; } } VectorCopy(self->goalentity->s.origin, org); // org[2] += 1; trace = gi.trace(org, self->mins, self->maxs, org, self, MASK_PLAYERSOLID); if (trace.fraction == 1) VectorCopy(org, self->s.origin); self->groundentity = NULL; gi.linkentity(self); VectorCopy(self->velocity, self->jump_velocity); // make sure once we land, we go for the next node if (((i = PathToEnt(self->goalentity->goalentity, goal, false, false)) > -1) && (PathToEnt_Node->s.origin[2] > self->s.origin[2])) self->goalentity = PathToEnt_Node; else /// just head for the landing node self->goalentity = self->goalentity->goalentity; if (self->velocity[2] > 200) { gi.sound(self, CHAN_VOICE, gi.soundindex("*jump1.wav"), 1, 2, 0); } //botDebugPrint("Trail jump: %s\n", vtos(self->velocity)); return; } else // there is a better route than jumping { self->goalentity = PathToEnt_Node; } } // if we've found a valid goal, make sure we have a visible destination (goalentity) if (goal) { if (!self->enemy && (self->target_ent == self)) { if (self->group_pausetime < level.time) { // start this leader on a path of destruction self->target_ent = NULL; } else // hang around for a bit { return; } } //ref(self->client->pers.weapon != item_blaster) // Squadron following if (goal == self->target_ent) { //Acrid removed old co code here flag thinking if ((goal_dist = entdist(goal, self)) < (BOT_IDEAL_DIST_FROM_ENEMY / 2) && (self->client->pers.weapon != item_knife)) { // too close, move back vec3_t vec; //fixme acrid look here for attacking sentrys distance self->goalentity = NULL; botDebugPrint("ideal dis 1\n"); VectorSubtract(self->s.origin, goal->s.origin, vec); VectorNormalize2(vec, vec); self->ideal_yaw = vectoyaw(vec); bot_move(self, dist); } // if in proximity, then hang around for a bit longer if (goal_dist < (BOT_IDEAL_DIST_FROM_ENEMY * (1+((goal->think == CTFFlagThink)*2))) && (self->client->pers.weapon != item_knife)) { botDebugPrint("ideal dis 3\n"); if (goal->client && self->movetarget) self->movetarget = NULL; self->group_pausetime = level.time + 0.2; // don't check_stuck return; } // otherwise, move towards leader as you would any other item } if (!self->goalentity) { // find a path damnit! if (PathToEnt(self, goal, false, false) > -1) { self->goalentity = PathToEnt_Node; } else // no path, so stop looking for this "thing" { if (goal == self->movetarget) self->movetarget = NULL; else if (goal == self->enemy) self->enemy = NULL; else if (goal == self->target_ent) self->target_ent = NULL; } } else if ((self->goalentity == self->enemy) && ((goal_dist = entdist(self->enemy, self)) < BOT_IDEAL_DIST_FROM_ENEMY) && (self->client->pers.weapon != item_knife)) { // in range if (goal_dist < (BOT_IDEAL_DIST_FROM_ENEMY / 2)) { // too close, move back vec3_t vec; botDebugPrint("ideal dis 4\n"); self->goalentity = NULL; VectorSubtract(self->s.origin, self->enemy->s.origin, vec); VectorNormalize2(vec, vec); self->ideal_yaw = vectoyaw(vec); bot_move(self, dist); } return; } } else // no goal, so clear goalentity and get out of here! { self->goalentity = NULL; return; } ////////////////////////////////////////////////////////////////////////// // ********************************************************* // // This is the MAIN movement AI // // If we have a goalentity, walk towards it, when we reach it, // find the next goal for the next frame if ( (self->goalentity) // if we don't have a goalentity, then no point trying to go anywhere && ( ( (self->last_inair < level.time) // if just landed check for a new goalentity from landing position || (self->goalentity->s.origin[2] <= self->s.origin[2]) || (!visible_box(self, self->goalentity)) || ( (PathToEnt(self->goalentity, goal, false, false) > -1) && (visible_box(self, PathToEnt_Node)) && (self->goalentity = PathToEnt_Node))) // we need a new goalentity, since we've just landed || ( (PathToEnt(self, goal, false, false) > -1) && (self->goalentity = PathToEnt_Node)) || ((self->goalentity = NULL) && false))) // couldn't find a path, so clear goalentity { // going for an item, so follow the set path VectorSubtract(self->goalentity->s.origin, self->s.origin, dir); if (self->goalentity->node_type == NODE_PLAT) // could be riding the plat dir[2] = 0; vectoangles(dir, angle); if (VectorLength(dir) <= (dist)) { dist = VectorLength(dir); goal_in_range = true; } // do the actual movement bot_move(self, dist); if (self->goalentity) // goalentity could have been cleared during bot_move() { if (goal_in_range) { // see if we've reached the marker if (bot_ReachedTrail(self)) { //f botDebugPrint("reached goal (%i)\n", self->goalentity->trail_index); if ( !( (PathToEnt(self->goalentity, goal, false, false) > -1) && (self->goalentity = PathToEnt_Node))) { self->goalentity = NULL; //f botDebugPrint("Couldn't find next goal, roaming\n"); bot_roam(self, false); } else { //f botDebugPrint("heading for goal (%i)\n", self->goalentity->trail_index); // if goal is plat, and it's no ready, wait here for a bit if ( (self->goalentity->node_type == NODE_PLAT) && (self->goalentity->target_ent->moveinfo.state != STATE_BOTTOM)) { // stay here for a bit self->bot_plat_pausetime = level.time + 0.5; // move backwards M_walkmove(self, self->s.angles[1] + 180, 10); } // use grapple to get there faster?//FIXME ACRID else if ( (bot_tarzan->value)/*(grapple->value || ctf->value)*///acrid coed and added tarzan && (bot_tarzan->value || (goal->item && goal->item->pickup == CTFPickup_Flag) /*CarryingFlag(self)*/) && ((self->waterlevel > 2) || (self->last_enemy_sight < (level.time - 1)) || !self->enemy || (entdist(self, self->enemy) > 512)) && (PathToEnt(self->goalentity, goal, false, false) > -1)) { static vec3_t goaldir, nextgoaldir, diffdir; VectorSubtract(self->goalentity->s.origin, self->s.origin, goaldir); VectorSubtract(PathToEnt_Node->s.origin, self->s.origin, nextgoaldir); if (VectorLength(nextgoaldir) > 300) { VectorNormalize(goaldir); VectorNormalize(nextgoaldir); VectorSubtract(nextgoaldir, goaldir, diffdir); if (VectorLength(diffdir) < 0.4) { trace_t tr; VectorScale(nextgoaldir, 1024, diffdir); VectorAdd(self->s.origin, diffdir, diffdir); tr = gi.trace(self->s.origin, NULL, NULL, diffdir, self, MASK_PLAYERSOLID); if ((tr.fraction < 1) && (tr.fraction > 0.4) && (tr.ent == world)) { // fire away vectoangles(nextgoaldir, self->client->v_angle); CTFGrappleFire (self, vec3_origin, 10, 0); // set visible model vwep with 3.20 code ShowGun(self); } } } } } } } } else // goalentity has been whiped { return; } if (self->movetarget && !self->movetarget->item && (self->last_roam_time < (level.time - 3))) { // walking around aimlessly, check for new targets every 3 seconds self->movetarget = NULL; bot_roam(self, false); } } else { bot_move(self, dist); // just go anywhere } // ********************************************************* if ((self->flags & FL_SHOWPATH) && (goal)) { // show path to goal Debug_ShowPathToGoal(self, goal); } }; float bot_ReachedTrail(edict_t *self) { vec3_t vec; int retval = false; VectorSubtract(self->s.origin, self->goalentity->s.origin, vec); if (retval = (abs(vec[2]) < 16)) { vec[2] = 0; retval = (VectorLength(vec) < 12); } // check if this is a plat node, and the plat is not responding if (self->goalentity->node_type == NODE_PLAT) { if ( (!self->goalentity->target_ent) || ( (self->groundentity != self->goalentity->target_ent) // not standing on the platform && (self->goalentity->target_ent->moveinfo.state != STATE_BOTTOM))) // and it's not waiting { // give up searching for this ent edict_t *goal = NULL; botDebugPrint("Aborting plat node\n"); if (self->movetarget) goal = self->movetarget; else if (self->enemy) goal = self->enemy; // make sure nothing else searches for this ent for a while if (goal) { goal->ignore_time = level.time + 0.5; goal->enemy = self; } self->goalentity->ignore_time = level.time + 0.2; self->movetarget = self->enemy = NULL; bot_roam(self, false); return false; } else if ( (self->groundentity == self->goalentity->target_ent) && (self->goalentity->target_ent->moveinfo.state == STATE_TOP)) { // standing on the platform, and it's reached the top return true; } } if (retval || (self->client->reached_goal_time == level.time)) { // ignore the current node for a bit if ((self->goalentity->enemy != self) || (self->goalentity->ignore_time < (level.time - 3))) self->last_reached_trail = level.time; self->goalentity->enemy = self; self->goalentity->ignore_time = level.time + 0.2; // standing/crouching if (self->goalentity->routes) { if (self->maxs[2] != self->goalentity->maxs[2]) { if (self->maxs[2] < 32) { // try to stand if (CanStand(self)) self->maxs[2] = self->goalentity->maxs[2]; } else // duck { self->maxs[2] = self->goalentity->maxs[2]; } } } self->client->reached_goal_time = 0; retval = true; } return retval; } extern qboolean touched_player; // Simulated movement physics, adapted from player-like movement int bot_move(edict_t *self, float dist) { trace_t trace; vec3_t dir, vec1/*, vec2*/, dest, dropdest; //, save_vel, save2; usercmd_t ucmd; // fake ucmd for client emulation vec3_t angles, oldorg, move; int ret, got_dir=false; qboolean going_for_goalentity=false; edict_t *last_groundentity; float save_nocloser; int start_waterlevel; //botfreeze 3/99 if (level.time < self->frozentime) return false; // only restore last_move_nocloser if we don't move any closer save_nocloser = self->last_move_nocloser; self->last_move_nocloser = level.time; /* // make sure we crouch when we have to if (self->goalentity && !self->goalentity->item && (self->goalentity->maxs[2] < 32)) { // don't set maxs to that of an item self->maxs[2] = self->goalentity->maxs[2]; } else if (self->movetarget && (self->goalentity == self->movetarget) && self->movetarget->movetarget && (self->movetarget->movetarget->maxs[2] < 32)) { self->maxs[2] = self->movetarget->movetarget->maxs[2]; } */ // BEGIN, SABIN code: prepare the ucmd for client simulation, but we have to clear the structure manually, since we don't have actual client's do it for us // delta_angles must be manually cleared VectorClear (self->client->ps.pmove.delta_angles); // otherwise the actual facing direction may be modified prior to movement memset (&ucmd, 0, sizeof (ucmd)); // END, SABIN code ucmd.msec = 100; // bot's think every 100 ms //acrid fixme look here for speed if (dist < BOT_RUN_SPEED*FRAMETIME) ucmd.forwardmove = sv_maxvelocity->value * 0.5; // walking speed else { if ((self->waterlevel > 2) || (!self->groundentity && (self->waterlevel > 0))) ucmd.forwardmove = sv_maxvelocity->value * 0.6; // go slower if swimming else ucmd.forwardmove = sv_maxvelocity->value; } if (self->waterlevel && ((self->air_finished - 2.5) < level.time) && ( !self->movetarget || !self->movetarget->item || (entdist(self, self->movetarget) > (384 * ((self->movetarget->item->pickup == CTFPickup_Flag) + 1))))) // go for air! { // can we get to air? VectorCopy(self->s.origin, dest); dest[2] += 512; trace = gi.trace(self->s.origin, NULL, NULL, dest, self, MASK_PLAYERSOLID); VectorCopy(trace.endpos, dest); dest[2] -= 1; if (botTouchingLadder(self) || !(gi.pointcontents(dest) & MASK_WATER)) ucmd.upmove = sv_maxvelocity->value; } else if (!self->waterlevel && self->goalentity && (self->goalentity->node_type == NODE_LANDING) && (self->goalentity->s.origin[2] > self->s.origin[2]+64)) {//the jumping fixme ucmd.upmove = sv_maxvelocity->value; } else if (!self->groundentity && (!self->waterlevel))// && self->goalentity && ((self->goalentity->s.origin[2]+32) > self->s.origin[2])) { ucmd.upmove = sv_maxvelocity->value; } else if (!self->groundentity && !self->waterlevel && self->goalentity) { // ladder? vec3_t vec; VectorSubtract(self->s.origin, self->goalentity->s.origin, vec); vec[2] = 0; if ((self->goalentity->s.origin[2] > self->s.origin[2]) && (VectorLength(vec) < 128)) ucmd.upmove = sv_maxvelocity->value; } VectorCopy(self->s.origin, oldorg); VectorCopy(self->s.angles, angles); if (self->avoid_ent && !self->avoid_ent->inuse) self->avoid_ent = NULL; if (self->groundentity || self->waterlevel) { // find walking direction if (self->avoid_ent) { if (self->avoid_dir_time > level.time) { VectorCopy(self->avoid_dir, dir); got_dir = true; } // find a direction away from danger else if (!(ret = botJumpAvoidEnt(self, self->avoid_ent))) { // move away from danger VectorSubtract(self->s.origin, self->avoid_ent->s.origin, dir); self->avoid_dir_time = level.time + 0.5; got_dir = true; } else if (ret == 1) // successful jump from danger { if (self->groundentity) { VectorCopy(self->avoid_dir, dir); got_dir = true; } return true; } } if (!got_dir) { if (self->goalentity && !self->activator) { VectorSubtract(self->goalentity->s.origin, self->s.origin, dir); if (VectorLength(dir) < 32) { // move straight to the goalentity VectorCopy(dir, self->velocity); // convert to "per second" VectorScale(self->velocity, 10, self->velocity); } VectorNormalize2(dir, vec1); going_for_goalentity = true; } else { VectorClear(dest); dest[1] = self->ideal_yaw; AngleVectors(dest, dir, NULL, NULL); } } // dir[2] = 0; VectorNormalize2(dir, dir); vectoangles(dir, angles); } else if (!self->client->ctf_grapple) { if (self->velocity[2] > 310) self->velocity[2] = 310; // have we gone passed the goalentity? if so slow down velocity if (self->goalentity && (fabs(self->s.origin[2] - self->goalentity->s.origin[2]) > 16)) { VectorCopy(self->velocity, dir); dir[2] = 0; if (VectorLength(dir) > 8) { VectorNormalize(dir); VectorSubtract(self->goalentity->s.origin, self->s.origin, dest); dest[2]=0; VectorNormalize2(dest, vec1); VectorSubtract(dir, vec1, dir); if ((VectorLength(dest) < 64) && (VectorLength(dir) > 1.5)) { // slow down! self->velocity[0] = self->jump_velocity[0] = 0; self->velocity[1] = self->jump_velocity[1] = 0; } } } } // BEGIN, SABIN code: convert angles to 16-bit int's, and pass them into the ucmd for simulation ucmd.angles[PITCH] = ANGLE2SHORT(angles[PITCH]); ucmd.angles[YAW] = ANGLE2SHORT(angles[YAW]); ucmd.angles[ROLL] = ANGLE2SHORT(angles[ROLL]); // END, SABIN code last_groundentity = self->groundentity; start_waterlevel = self->waterlevel; BotMoveThink(self, &ucmd); VectorSubtract(self->s.origin, oldorg, move); if ((!start_waterlevel) && !self->groundentity && last_groundentity) { float drop_dist=400; VectorCopy(self->velocity, self->jump_velocity); // just to be safe // if our goal is close by, and is slightly above, jump//fixme??? if (self->goalentity /*&& (entdist(self, self->goalentity) < 256)*/ && (self->s.origin[2] < (self->goalentity->s.origin[2] /*- 48*/))) { if (self->s.origin[2] < (self->goalentity->s.origin[2]+32)) // only jump if goal is above us { // don't jump if ground is closeby trace_t tr; VectorCopy(self->s.origin, dropdest); dropdest[2] -= 20; tr = gi.trace(self->s.origin, self->mins, self->maxs, dropdest, self, MASK_PLAYERSOLID | CONTENTS_LAVA | CONTENTS_SLIME); //lavacode if ((tr.fraction == 1) || (tr.plane.normal[2] < 0.5) || (tr.contents && (CONTENTS_LAVA | CONTENTS_SLIME))) { // jump to be safe VectorSubtract(self->goalentity->s.origin, self->s.origin, self->velocity); VectorScale( self->velocity, 1.5, self->velocity); self->velocity[2] = 0; if (VectorLength(self->velocity) > 300) { VectorNormalize(self->velocity); VectorScale(self->velocity, 300, self->velocity); } self->velocity[2] = 310; VectorCopy(self->velocity, self->jump_velocity); gi.sound(self, CHAN_VOICE, gi.soundindex("*jump1.wav"), 1, 2, 0); } } } else { if (self->goalentity) { if (self->goalentity->s.origin[2]+64 < self->s.origin[2]) drop_dist= 200 + (-1 * (self->goalentity->s.origin[2] - self->s.origin[2])); else if (self->goalentity->s.origin[2] > self->s.origin[2]) drop_dist = 22; } //lavacode // if monster had the ground pulled out, go ahead and fall, but only if safe (No lava or slime) // trace downwards, to see if it's dangerous VectorSubtract(self->s.origin, tv(0,0,drop_dist), dropdest); VectorMA(dropdest, 0.3 * (drop_dist/400), self->velocity, dropdest); trace = gi.trace(self->s.origin, VEC_ORIGIN, VEC_ORIGIN, dropdest, self, MASK_SOLID | MASK_WATER); VectorCopy(trace.endpos, dest); dest[2] += 1; //lavacode if ( ( ( (trace.fraction == 1) || (self->goalentity && (self->goalentity->s.origin[2]-64 > trace.endpos[2]))) && (!self->enemy || (self->enemy->s.origin[2] < self->s.origin[2]))) // don't follow a player too far down || (trace.contents) & (CONTENTS_LAVA | CONTENTS_SLIME)) // falling into lava // || (self->goalentity && (trace.fraction > 0.4) && (fabs(self->s.origin[2] - self->goalentity->s.origin[2]) < 8)) ) // we shouldn't be falling here { // lava below, see if we can safely proceed VectorCopy(self->velocity, dropdest); dropdest[2] = -64; VectorAdd(self->s.origin, dropdest, dropdest); trace = gi.trace(self->s.origin, self->mins, self->maxs, dropdest, self, MASK_SOLID | CONTENTS_LAVA | CONTENTS_SLIME); if ( (drop_dist < 32) || (trace.fraction == 1) || (trace.contents & (CONTENTS_LAVA | CONTENTS_SLIME)) || (trace.plane.normal[2] < 0.5)) // too steep { // abort the current path, so we can look for an alternate path next frame if (self->goalentity) self->goalentity->ignore_time = level.time + 0.5; self->goalentity = NULL; self->groundentity = last_groundentity; VectorCopy(oldorg, self->s.origin); gi.linkentity(self); } } else if ((self->velocity[2] <= 0) && self->goalentity && (self->goalentity->s.origin[2] < self->s.origin[2])) { // keep on ground when going down stairs or ramp vec3_t dest; VectorCopy(self->s.origin, dest); dest[2] -= 32; trace = gi.trace(self->s.origin, self->mins, self->maxs, dest, self, MASK_PLAYERSOLID); // only really inair if way above ground if (trace.fraction < 1) { VectorCopy(trace.endpos, self->s.origin); self->groundentity = trace.ent; gi.linkentity(self); } } } } else { if (self->groundentity && !last_groundentity) { // just landed, let ClosestNodeToEnt check for a better node self->last_closest_time = 0; } else if (!self->groundentity && !last_groundentity && (self->velocity[2] > 0) /*&& (!self->goalentity || (self->goalentity->s.origin[2] > self->s.origin[2]))*/) { // prevent not jumping high enough when scraping a wall/ledge float f; f = move[2]; move[2] = 0; if (VectorLength(move) < 5) { self->velocity[2] += 13; } move[2] = f; } if (self->groundentity && (VectorLength(move) < dist*0.1)) { // try walkmove() if (!M_walkmove(self, angles[YAW], dist)) { // if (!M_walkmove(self, angles[YAW], dist*0.3)) // if (!M_walkmove(self, angles[YAW+100], dist*0.5)) // M_walkmove(self, angles[YAW-100], dist*0.5); } VectorSubtract(self->s.origin, oldorg, move); } if ((self->groundentity) && (VectorLength(move) < dist*0.1)) { if (self->goalentity /*&& self->groundentity*/) { // if ((random() < 0.7)) // && (self->s.origin[2] + 16 < self->goalentity->s.origin[2])) { vec3_t start, dir, org, mins; VectorCopy(self->s.origin, start); // start[2] -= 12; AngleVectors(angles, dir, NULL, NULL); VectorScale(dir, 24, dir); VectorAdd(start, dir, org); VectorCopy(self->mins, mins); mins[2] += 16; trace = gi.trace(start, mins, self->maxs, org, self, MASK_SOLID); if ((trace.fraction < 0.3) && (fabs(trace.plane.normal[2]) < 0.3)) { // set ideal velocity // VectorScale(trace.plane.normal, -40, dir); dir[2] = 310; // see if clear at head start[2] += 32; org[2] += 32; trace = gi.trace(start, VEC_ORIGIN, VEC_ORIGIN, org, self, MASK_SOLID); if (trace.fraction == 1) { if (CanJump(self)) { // safe to jump // self->s.origin[2] += 1; self->groundentity = NULL; gi.linkentity(self); VectorCopy(dir, self->velocity); VectorCopy(self->velocity, self->jump_velocity); gi.sound(self, CHAN_VOICE, gi.soundindex("*jump1.wav"), 1, 2, 0); } } else // blocked at head also, abort goal { self->goalentity = NULL; } } } } // if (touched_player) if (self->groundentity) { // stuck, try and strafe int dir=1; if ((((int)(level.time)) % 6) < 3) dir = -1; if (!M_walkmove(self, self->s.angles[YAW] - 110 * dir, BOT_STRAFE_SPEED * FRAMETIME * 0.5)) { // try the other way M_walkmove( self, self->s.angles[YAW] + 110 * dir, BOT_STRAFE_SPEED * FRAMETIME * 0.5); } self->client->slide_time = level.time + 0.5; } } else if (self->client->slide_time > level.time) { int dir=1; if ((((int)(level.time)) % 6) < 3) dir = -1; M_walkmove( self, self->s.angles[YAW] - 110 * dir, BOT_STRAFE_SPEED * FRAMETIME ); } if (self->groundentity && (VectorLength(move) < dist*0.1)) { // try walkmove() M_walkmove(self, angles[YAW] + 180 * (random()*2 - 1), dist*0.5); VectorSubtract(self->s.origin, oldorg, move); } } if (going_for_goalentity && self->goalentity /*&& !self->goalentity->item*/) { vec3_t goalvec, oldgoalvec; vec3_t u_goalvec, u_oldgoalvec; float vdist; VectorSubtract(self->s.origin, self->goalentity->s.origin, goalvec); VectorSubtract(oldorg, self->goalentity->s.origin, oldgoalvec); if ((vdist = VectorLength(goalvec)) < 40) { // see if we can move to the goal if (vdist > 12) { VectorNormalize2(goalvec, u_goalvec); VectorNormalize2(oldgoalvec, u_oldgoalvec); VectorSubtract(u_goalvec, u_oldgoalvec, dir); } if ((vdist <= 12) || (VectorLength(dir) > 0.4)) { // close enough, and we've passed the goal, so try to move to it // trace_t trace; self->client->reached_goal_time = level.time; // if this is a jump node, move to it if (self->goalentity->goalentity) { trace = gi.trace (self->goalentity->s.origin, self->goalentity->mins, self->goalentity->maxs, self->goalentity->s.origin, self, MASK_PLAYERSOLID); if (!trace.startsolid) { VectorCopy(self->goalentity->s.origin, self->s.origin); gi.linkentity(self); } } } } else if (self->groundentity || self->waterlevel) // make sure we keep heading towards the goal { if (VectorLength(goalvec) >= VectorLength(oldgoalvec)) { // we haven't moved any closer, something's wrong self->last_move_nocloser = save_nocloser; if (self->last_move_nocloser < (level.time - 0.3)) { botDebugPrint("%s didn't move closer to goalentity\n", self->client->pers.netname); self->goalentity->ignore_time = level.time + 1; self->goalentity->enemy = self; self->goalentity = NULL; self->last_move_nocloser = level.time; } } } } return true; } int bot_BetterTarget (edict_t *self, edict_t *other) { botDebugPrint(" class other %s\n",other->classname); botDebugPrint(" self team %d\n",self->wf_team); botDebugPrint(" other team %d\n",other->wf_team); //if attacker is not a sentry or turret grenade dont attack /* if (!other->client && ((strcmp(other->classname, "SentryGun") != 0) ||(strcmp(other->classname, "turret") != 0))) { botDebugPrint("false not sentry or turret\n",other->classname); return false; }*/ return false; // attack sentry if not same team if ((strcmp(other->classname, "SentryGun") == 0) && (other->wf_team != self->wf_team)) { botDebugPrint(" true\n",other->classname); return true; } // don't attack same team sentry if it accidently hits bot if ((strcmp(other->classname, "SentryGun") == 0) && (other->wf_team == self->wf_team)) { return false; } if ((strcmp(other->classname, "turret") == 0)&& (other->wf_team != self->wf_team)) return true; if ((strcmp(other->classname, "turret") == 0)&& (other->wf_team == self->wf_team)) return false; /*if((self->client->player_class == 2) && (self->client->pers.inventory[ITEM_INDEX(FindItem("Shells"))] > 0)) { if (SameTeam(other, self) && (other->disease)) return true; }*/ if (SameTeam(other, self)) return false; if (ctf->value) { if (self->enemy && CarryingFlag(self->enemy)) return false; if (CarryingFlag(other)) return true; } // FIXME: weigh up other's weapon against their health? if ((self->enemy) && (entdist(self, self->enemy) < 512)) return (other->health < self->enemy->health); else return (other->health > 0); } void bot_pain (edict_t *self, edict_t *other, float kick, int damage) { int r, l; // botDebugPrint(" class 2222 %s\n",other->classname); // botDebugPrint(" name 2222 %1\n",other->health); self->last_pain = level.time; //lavacode if ( (!ctf->value) && (self->client->team) && (other->client) // ignore if hurt by door or lava && (self->client->team->last_grouping < (level.time - 5)) && (last_bot_chat[CHAT_TEAMPLAY_HELP] < (level.time - 5)) && (self->client->team != other->client->team) && (!self->target_ent) && ((self->bot_fire == botBlaster) || (self->bot_fire == botShotgun)) && (self->health < other->health)) { // signal for help! botDebugPrint("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"); TeamGroup(self); } if (bot_BetterTarget(self,other)) { // get mad at this person self->enemy = other; } /* if ((self->health < 20) && (self->bot_stats->aggr < random() * 5)) { // health required! // self->movetarget = NULL; bot_roam(self, false); } */ // play an apropriate pain sound if (level.time > self->pain_debounce_time) { int i=0; r = 1 + (rand()&1); self->pain_debounce_time = level.time + 0.7; if (self->health < 25) l = 25; else if (self->health < 50) l = 50; else if (self->health < 75) l = 75; else l = 100; gi.sound (self, CHAN_VOICE, gi.soundindex(va("*pain%i_%i.wav", l, r)), 1, ATTN_NORM, 0); if(!self->frozen)//botfreeze if (self->client->anim_priority < ANIM_PAIN) { self->client->anim_priority = ANIM_PAIN; if (self->maxs[2] == 4) { self->s.frame = FRAME_crpain1; self->client->anim_end = FRAME_crpain4; } else { i = (i+1)%3; switch (i) { case 0: self->s.frame = FRAME_pain101; self->client->anim_end = FRAME_pain104; break; case 1: self->s.frame = FRAME_pain201; self->client->anim_end = FRAME_pain204; break; case 2: self->s.frame = FRAME_pain301; self->client->anim_end = FRAME_pain304; break; } } } } } //======================================================================================== // // Miscellaneous routines // // Checks if the entity can be moved upwards for jumping int CanJump(edict_t *ent) { trace_t trace; vec3_t dest; VectorCopy(ent->s.origin, dest); dest[2] += 1; trace = gi.trace(ent->s.origin, ent->mins, ent->maxs, dest, ent, MASK_PLAYERSOLID); return (!trace.startsolid && (trace.fraction == 1)); } // check that the targ is visible from self (restricted to FOV) int CanSee(edict_t *self, edict_t *targ) { vec3_t dir, forward, vec; VectorSubtract(targ->s.origin, self->s.origin, dir); if (VectorLength(dir) < 256) // close enough return true; // dir[2] = 0; VectorNormalize2(dir, dir); AngleVectors(self->s.angles, forward, NULL, NULL); VectorSubtract(forward, dir, vec); return (VectorLength(vec) < (1 + (self->bot_stats->combat / 5))); } // check that the path from self to targ is valid (doesn't walk on air) int CanReach(edict_t *self, edict_t *targ) { vec3_t dir, midpos, end_trace, start; vec3_t midpos2, mins; trace_t trace; float dist, progressive_dist; int inc=32; // special water case (since tracing for WATER doesn't work if ( (self->waterlevel && targ->waterlevel) || ( (targ->waterlevel && (!targ->groundentity || (targ->waterlevel > 2))) && ((self->s.origin[2]+16) > targ->s.origin[2]))) return true; VectorAdd(self->mins, tv(0,0,12), mins); VectorCopy(self->s.origin, start); VectorSubtract(targ->s.origin, self->s.origin, dir); VectorMA(start, 0.5, dir, midpos); VectorSubtract(midpos, tv(0,0,40), end_trace); // check that the midpos is onground (down 28 units) trace = gi.trace(midpos, mins, self->maxs, end_trace, self, MASK_SOLID); if (trace.fraction == 1) return false; if ((dist = VectorLength(dir)) < 32) return true; VectorNormalize2(dir, dir); if (!bot_calc_nodes->value || (self->bot_client)) // only do thorough checking when laying nodes { return true; // if (dist > 66) // dist = 66; } else { inc = 12; // do more thourough checking } for (progressive_dist = 32; progressive_dist < (dist - 16) ; progressive_dist += inc) { VectorMA(start, progressive_dist, dir, midpos2); VectorSubtract(midpos2, tv(0,0,28), end_trace); trace = gi.trace(midpos2, mins, self->maxs, end_trace, self, MASK_SOLID); if (trace.fraction == 1) return false; } return true; } int CanStand(edict_t *self) { static vec3_t maxs = {16, 16, 32}; trace_t trace; trace = gi.trace(self->s.origin, self->mins, maxs, self->s.origin, self, MASK_PLAYERSOLID); return (!trace.startsolid || ((trace.ent->svflags & SVF_MONSTER) && (trace.ent->health <= 0))); } //======================================================================================== void bot_SuicideIfStuck(edict_t *self) { if ((self->checkstuck_time < (level.time - 1)) && (self->group_pausetime < (level.time - 1)) && (self->bot_plat_pausetime < (level.time - 1))) { vec3_t move; VectorSubtract(self->s.origin, self->checkstuck_origin, move); if (VectorLength(move) < 4) { // bot is stuck if (self->checkstuck_time < (level.time - 5)) { // suicide after long enough botDebugPrint("Bot suicide (stuck)\n"); T_Damage(self, self, self, tv(0,0,0), self->s.origin, tv(0,0,0), self->health + 1, 0, DAMAGE_NO_PROTECTION, MOD_SUICIDE); } else { // jump randomly botDebugPrint("BotStuck: Random jump\n"); botRandomJump(self); } self->checkstuck_time = level.time; return; } else { VectorCopy(self->s.origin, self->checkstuck_origin); self->checkstuck_time = level.time; } } } void botButtonThink(edict_t *ent) { static vec3_t mins = {-16, -16, -8}; int i, count=0; // trace_t tr; edict_t *plyr; float dist; if (ent->skill_level > num_players) ent->skill_level = 0; for (; ent->skill_levelskill_level++) { i = ent->skill_level; plyr = players[i]; if (count++ > 6) break; if (!plyr->bot_client || plyr->activator) continue; if (plyr->activator_time > level.time) continue; if (entdist(ent, plyr) > 900) continue; count += 2; // so we don't do too many PathToEnt()'s per think if ((dist = PathToEnt(ent->owner, ent, false, false)) == -1) continue; if (dist > 1200) continue; // push me! plyr->activator = ent; plyr->goalentity = PathToEnt_Node; plyr->activator_time = level.time; plyr->last_reached_trail = level.time; ent->nextthink = level.time + 4; // wait a bit longer than usual break; } ent->nextthink = level.time + 0.2 + random()*0.3; // so multiple buttons don't all go at once }