/* Copyright (C) 1998 Steve Yeager This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /////////////////////////////////////////////////////////////////////// // // ACE - Quake II Bot Base Code // // Version 1.0 // // This file is Copyright(c), Steve Yeager 1998, 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 ACE, you MUST // include the following text (minus the BEGIN and END lines) in the // documentation for your modification. // // --- BEGIN --- // // The ACE Bot is a product of Steve Yeager, and is available from // the ACE Bot homepage, at http://www.axionfx.com/ace. // // This program is a modification of the ACE Bot, and is therefore // in NO WAY supported by Steve Yeager. // // --- END --- // // I, Steve Yeager, hold no responsibility for any harm caused by the // use of this source code, especially to small children and animals. // It is provided as-is with no implied warranty or support. // // I also wish to thank and acknowledge the great work of others // that has helped me to develop this code. // // John Cricket - For ideas and swapping code. // Ryan Feltrin - For ideas and swapping code. // SABIN - For showing how to do true client based movement. // BotEpidemic - For keeping us up to date. // Telefragged.com - For giving ACE a home. // Microsoft - For giving us such a wonderful crash free OS. // id - Need I say more. // // And to all the other testers, pathers, and players and people // who I can't remember who the heck they were, but helped out. // /////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////// // acebot_movement.c - This file contains all of the // movement routines for the ACE bot // /////////////////////////////////////////////////////////////////////// #include "g_local.h" #include "acebot.h" // Platform states #define STATE_TOP 0 #define STATE_BOTTOM 1 #define STATE_UP 2 #define STATE_DOWN 3 /////////////////////////////////////////////////////////////////////// // Checks if bot can move (really just checking the ground) // Also, this is not a real accurate check, but does a // pretty good job and looks for lava/slime. /////////////////////////////////////////////////////////////////////// qboolean ACEMV_CanMove(edict_t *self, int direction) { vec3_t forward, right; vec3_t offset,start,end; vec3_t angles; trace_t tr; // Now check to see if move will move us off an edge VectorCopy(self->s.angles,angles); if(direction == MOVE_LEFT) angles[1] += 90; else if(direction == MOVE_RIGHT) angles[1] -= 90; else if(direction == MOVE_BACK) angles[1] -=180; // Set up the vectors AngleVectors (angles, forward, right, NULL); VectorSet(offset, 36, 0, 24); G_ProjectSource (self->s.origin, offset, forward, right, start); VectorSet(offset, 36, 0, -400); G_ProjectSource (self->s.origin, offset, forward, right, end); tr = gi.trace(start, NULL, NULL, end, self, MASK_OPAQUE); if(tr.fraction > 0.3 && tr.fraction != 1 || tr.contents & (CONTENTS_LAVA|CONTENTS_SLIME)) { if(debug_mode) debug_printf("%s: move blocked\n",self->client->pers.netname); return false; } return true; // yup, can move } /////////////////////////////////////////////////////////////////////// // Handle special cases of crouch/jump // // If the move is resolved here, this function returns // true. /////////////////////////////////////////////////////////////////////// qboolean ACEMV_SpecialMove(edict_t *self, usercmd_t *ucmd) { vec3_t dir,forward,right,start,end,offset; vec3_t top; trace_t tr; // Get current direction VectorCopy(self->client->ps.viewangles,dir); dir[YAW] = self->s.angles[YAW]; AngleVectors (dir, forward, right, NULL); VectorSet(offset, 18, 0, 0); G_ProjectSource (self->s.origin, offset, forward, right, start); offset[0] += 18; G_ProjectSource (self->s.origin, offset, forward, right, end); // trace it start[2] += 18; // so they are not jumping all the time end[2] += 18; tr = gi.trace (start, self->mins, self->maxs, end, self, MASK_MONSTERSOLID); if(tr.allsolid) { // Check for crouching start[2] -= 14; end[2] -= 14; // Set up for crouching check VectorCopy(self->maxs,top); top[2] = 0.0; // crouching height tr = gi.trace (start, self->mins, top, end, self, MASK_PLAYERSOLID); // Crouch if(!tr.allsolid) { ucmd->forwardmove = 400; ucmd->upmove = -400; return true; } // Check for jump start[2] += 32; end[2] += 32; tr = gi.trace (start, self->mins, self->maxs, end, self, MASK_MONSTERSOLID); if(!tr.allsolid) { ucmd->forwardmove = 400; ucmd->upmove = 400; return true; } } return false; // We did not resolve a move here } /////////////////////////////////////////////////////////////////////// // Checks for obstructions in front of bot // // This is a function I created origianlly for ACE that // tries to help steer the bot around obstructions. // // If the move is resolved here, this function returns true. /////////////////////////////////////////////////////////////////////// qboolean ACEMV_CheckEyes(edict_t *self, usercmd_t *ucmd) { vec3_t forward, right; vec3_t leftstart, rightstart,focalpoint; vec3_t upstart,upend; vec3_t dir,offset; trace_t traceRight,traceLeft,traceUp, traceFront; // for eyesight // Get current angle and set up "eyes" VectorCopy(self->s.angles,dir); AngleVectors (dir, forward, right, NULL); // Let them move to targets by walls if(!self->movetarget) VectorSet(offset,200,0,4); // focalpoint else VectorSet(offset,36,0,4); // focalpoint G_ProjectSource (self->s.origin, offset, forward, right, focalpoint); // Check from self to focalpoint // Ladder code VectorSet(offset,36,0,0); // set as high as possible G_ProjectSource (self->s.origin, offset, forward, right, upend); traceFront = gi.trace(self->s.origin, self->mins, self->maxs, upend, self, MASK_OPAQUE); if(traceFront.contents & 0x8000000) // using detail brush here cuz sometimes it does not pick up ladders...?? { ucmd->upmove = 400; ucmd->forwardmove = 400; return true; } // If this check fails we need to continue on with more detailed checks if(traceFront.fraction == 1) { ucmd->forwardmove = 400; return true; } VectorSet(offset, 0, 18, 4); G_ProjectSource (self->s.origin, offset, forward, right, leftstart); offset[1] -= 36; // want to make sure this is correct //VectorSet(offset, 0, -18, 4); G_ProjectSource (self->s.origin, offset, forward, right, rightstart); traceRight = gi.trace(rightstart, NULL, NULL, focalpoint, self, MASK_OPAQUE); traceLeft = gi.trace(leftstart, NULL, NULL, focalpoint, self, MASK_OPAQUE); // Wall checking code, this will degenerate progressivly so the least cost // check will be done first. // If open space move ok if(traceRight.fraction != 1 || traceLeft.fraction != 1 || strcmp(traceLeft.ent->classname,"func_door")!=0) { // Special uppoint logic to check for slopes/stairs/jumping etc. VectorSet(offset, 0, 18, 24); G_ProjectSource (self->s.origin, offset, forward, right, upstart); VectorSet(offset,0,0,200); // scan for height above head G_ProjectSource (self->s.origin, offset, forward, right, upend); traceUp = gi.trace(upstart, NULL, NULL, upend, self, MASK_OPAQUE); VectorSet(offset,200,0,200*traceUp.fraction-5); // set as high as possible G_ProjectSource (self->s.origin, offset, forward, right, upend); traceUp = gi.trace(upstart, NULL, NULL, upend, self, MASK_OPAQUE); // If the upper trace is not open, we need to turn. if(traceUp.fraction != 1) { if(traceRight.fraction > traceLeft.fraction) self->s.angles[YAW] += (1.0 - traceLeft.fraction) * 45.0; else self->s.angles[YAW] += -(1.0 - traceRight.fraction) * 45.0; ucmd->forwardmove = 400; return true; } } return false; } /////////////////////////////////////////////////////////////////////// // Make the change in angles a little more gradual, not so snappy // Subtle, but noticeable. // // Modified from the original id ChangeYaw code... /////////////////////////////////////////////////////////////////////// void ACEMV_ChangeBotAngle (edict_t *ent) { float ideal_yaw; float ideal_pitch; float current_yaw; float current_pitch; float move; float speed; vec3_t ideal_angle; // Normalize the move angle first VectorNormalize(ent->move_vector); current_yaw = anglemod(ent->s.angles[YAW]); current_pitch = anglemod(ent->s.angles[PITCH]); vectoangles (ent->move_vector, ideal_angle); ideal_yaw = anglemod(ideal_angle[YAW]); ideal_pitch = anglemod(ideal_angle[PITCH]); // Yaw if (current_yaw != ideal_yaw) { move = ideal_yaw - current_yaw; speed = ent->yaw_speed; if (ideal_yaw > current_yaw) { if (move >= 180) move = move - 360; } else { if (move <= -180) move = move + 360; } if (move > 0) { if (move > speed) move = speed; } else { if (move < -speed) move = -speed; } ent->s.angles[YAW] = anglemod (current_yaw + move); } // Pitch if (current_pitch != ideal_pitch) { move = ideal_pitch - current_pitch; speed = ent->yaw_speed; if (ideal_pitch > current_pitch) { if (move >= 180) move = move - 360; } else { if (move <= -180) move = move + 360; } if (move > 0) { if (move > speed) move = speed; } else { if (move < -speed) move = -speed; } ent->s.angles[PITCH] = anglemod (current_pitch + move); } } /////////////////////////////////////////////////////////////////////// // Set bot to move to it's movetarget. (following node path) /////////////////////////////////////////////////////////////////////// void ACEMV_MoveToGoal(edict_t *self, usercmd_t *ucmd) { // If a rocket or grenade is around deal with it // Simple, but effective (could be rewritten to be more accurate) if(strcmp(self->movetarget->classname,"rocket")==0 || strcmp(self->movetarget->classname,"grenade")==0 || strcmp(self->movetarget->classname,"homing rocket")==0) { VectorSubtract (self->movetarget->s.origin, self->s.origin, self->move_vector); ACEMV_ChangeBotAngle(self); if(debug_mode) debug_printf("%s: Oh crap a rocket!\n",self->client->pers.netname); // strafe left/right if(rand()%1 && ACEMV_CanMove(self, MOVE_LEFT)) ucmd->sidemove = -400; else if(ACEMV_CanMove(self, MOVE_RIGHT)) ucmd->sidemove = 400; return; } else { // Set bot's movement direction VectorSubtract (self->movetarget->s.origin, self->s.origin, self->move_vector); ACEMV_ChangeBotAngle(self); ucmd->forwardmove = 400; return; } } /////////////////////////////////////////////////////////////////////// // Main movement code. (following node path) /////////////////////////////////////////////////////////////////////// void ACEMV_Move(edict_t *self, usercmd_t *ucmd) { vec3_t dist; int current_node_type=-1; int next_node_type=-1; int i; // Get current and next node back from nav code. if(!ACEND_FollowPath(self)) { self->state = STATE_WANDER; self->wander_timeout = level.time + 1.0; return; } current_node_type = nodes[self->current_node].type; next_node_type = nodes[self->next_node].type; /////////////////////////// // Move To Goal /////////////////////////// if (self->movetarget) ACEMV_MoveToGoal(self,ucmd); //////////////////////////////////////////////////////// // Grapple /////////////////////////////////////////////////////// if(next_node_type == NODE_GRAPPLE) { ACEMV_ChangeBotAngle(self); ACEIT_ChangeWeapon(self,FindItem("grapple")); ucmd->buttons = BUTTON_ATTACK; return; } // Reset the grapple if hangin on a graple node if(current_node_type == NODE_GRAPPLE) { CTFPlayerResetGrapple(self); return; } //////////////////////////////////////////////////////// // Platforms /////////////////////////////////////////////////////// if(current_node_type != NODE_PLATFORM && next_node_type == NODE_PLATFORM) { // check to see if lift is down? for(i=0;inext_node) if(item_table[i].ent && // Knightmare- fix crash item_table[i].ent->moveinfo.state != STATE_BOTTOM) // crashes here return; // Wait for elevator } if(current_node_type == NODE_PLATFORM && next_node_type == NODE_PLATFORM) { // Move to the center self->move_vector[2] = 0; // kill z movement if(VectorLength(self->move_vector) > 10) ucmd->forwardmove = 200; // walk to center ACEMV_ChangeBotAngle(self); return; // No move, riding elevator } //////////////////////////////////////////////////////// // Jumpto Nodes /////////////////////////////////////////////////////// if(next_node_type == NODE_JUMP || (current_node_type == NODE_JUMP && next_node_type != NODE_ITEM && nodes[self->next_node].origin[2] > self->s.origin[2])) { // Set up a jump move ucmd->forwardmove = 400; ucmd->upmove = 400; ACEMV_ChangeBotAngle(self); VectorCopy(self->move_vector,dist); VectorScale(dist,440,self->velocity); return; } //////////////////////////////////////////////////////// // Ladder Nodes /////////////////////////////////////////////////////// if(next_node_type == NODE_LADDER && nodes[self->next_node].origin[2] > self->s.origin[2]) { // Otherwise move as fast as we can ucmd->forwardmove = 400; self->velocity[2] = 320; ACEMV_ChangeBotAngle(self); return; } // If getting off the ladder if(current_node_type == NODE_LADDER && next_node_type != NODE_LADDER && nodes[self->next_node].origin[2] > self->s.origin[2]) { ucmd->forwardmove = 400; ucmd->upmove = 200; self->velocity[2] = 200; ACEMV_ChangeBotAngle(self); return; } //////////////////////////////////////////////////////// // Water Nodes /////////////////////////////////////////////////////// if(current_node_type == NODE_WATER) { // We need to be pointed up/down ACEMV_ChangeBotAngle(self); // If the next node is not in the water, then move up to get out. if(next_node_type != NODE_WATER && !(gi.pointcontents(nodes[self->next_node].origin) & MASK_WATER)) // Exit water ucmd->upmove = 400; ucmd->forwardmove = 300; return; } // Falling off ledge? if(!self->groundentity) { ACEMV_ChangeBotAngle(self); self->velocity[0] = self->move_vector[0] * 360; self->velocity[1] = self->move_vector[1] * 360; return; } // Check to see if stuck, and if so try to free us // Also handles crouching if(VectorLength(self->velocity) < 37) { // Keep a random factor just in case.... if(random() > 0.1 && ACEMV_SpecialMove(self, ucmd)) return; self->s.angles[YAW] += random() * 180 - 90; ucmd->forwardmove = 400; return; } // Otherwise move as fast as we can ucmd->forwardmove = 400; ACEMV_ChangeBotAngle(self); } /////////////////////////////////////////////////////////////////////// // Wandering code (based on old ACE movement code) /////////////////////////////////////////////////////////////////////// void ACEMV_Wander(edict_t *self, usercmd_t *ucmd) { vec3_t temp; // Do not move if(self->next_move_time > level.time) return; // Special check for elevators, stand still until the ride comes to a complete stop. if(self->groundentity != NULL && self->groundentity->use == Use_Plat) if(self->groundentity->moveinfo.state == STATE_UP || self->groundentity->moveinfo.state == STATE_DOWN) // only move when platform not { self->velocity[0] = 0; self->velocity[1] = 0; self->velocity[2] = 0; self->next_move_time = level.time + 0.5; return; } // Is there a target to move to if (self->movetarget) ACEMV_MoveToGoal(self,ucmd); //////////////////////////////// // Swimming? //////////////////////////////// VectorCopy(self->s.origin,temp); temp[2]+=24; if(gi.pointcontents (temp) & MASK_WATER) { // If drowning and no node, move up if(self->client->next_drown_time > 0) { ucmd->upmove = 1; self->s.angles[PITCH] = -45; } else ucmd->upmove = 15; ucmd->forwardmove = 300; } else self->client->next_drown_time = 0; // probably shound not be messing with this, but //////////////////////////////// // Lava? //////////////////////////////// temp[2]-=48; if(gi.pointcontents(temp) & (CONTENTS_LAVA|CONTENTS_SLIME)) { // safe_bprintf(PRINT_MEDIUM,"lava jump\n"); self->s.angles[YAW] += random() * 360 - 180; ucmd->forwardmove = 400; ucmd->upmove = 400; return; } if(ACEMV_CheckEyes(self,ucmd)) return; // Check for special movement if we have a normal move (have to test) if(VectorLength(self->velocity) < 37) { if(random() > 0.1 && ACEMV_SpecialMove(self,ucmd)) return; self->s.angles[YAW] += random() * 180 - 90; if(!M_CheckBottom(self) || !self->groundentity) // if there is ground continue otherwise wait for next move ucmd->forwardmove = 400; return; } ucmd->forwardmove = 400; } /////////////////////////////////////////////////////////////////////// // Attack movement routine // // NOTE: Very simple for now, just a basic move about avoidance. // Change this routine for more advanced attack movement. /////////////////////////////////////////////////////////////////////// void ACEMV_Attack (edict_t *self, usercmd_t *ucmd) { float c; vec3_t target; vec3_t angles; // Randomly choose a movement direction c = random(); if(c < 0.2 && ACEMV_CanMove(self,MOVE_LEFT)) ucmd->sidemove -= 400; else if(c < 0.4 && ACEMV_CanMove(self,MOVE_RIGHT)) ucmd->sidemove += 400; if(c < 0.6 && ACEMV_CanMove(self,MOVE_FORWARD)) ucmd->forwardmove += 400; else if(c < 0.8 && ACEMV_CanMove(self,MOVE_FORWARD)) ucmd->forwardmove -= 400; if(c < 0.95) ucmd->upmove -= 200; else ucmd->upmove += 200; // Set the attack ucmd->buttons = BUTTON_ATTACK; // Aim VectorCopy(self->enemy->s.origin,target); // modify attack angles based on accuracy (mess this up to make the bot's aim not so deadly) // target[0] += (random()-0.5) * 20; // target[1] += (random()-0.5) * 20; // Set direction VectorSubtract (target, self->s.origin, self->move_vector); vectoangles (self->move_vector, angles); VectorCopy(angles,self->s.angles); // if(debug_mode) // debug_printf("%s attacking %s\n",self->client->pers.netname,self->enemy->client->pers.netname); }