/* * AI_TouchedEntity * Some AI has touched some entity. Some entities are declared to never be reached until touched. * See if it's one of them and declare it reached */ void AI_TouchedEntity( edict_t *self, edict_t *ent ) { int i; nav_ents_t *goalEnt; // right now we only support this on a few trigger entities (jumpads, teleporters) if( ent->r.solid != SOLID_TRIGGER && ent->item == NULL ) return; // clear short range goal, pick a new goal ASAP if( ent == self->movetarget ) { self->movetarget = NULL; self->ai->shortRangeGoalTimeout = level.time; } if( self->ai->goalEnt && ent == self->ai->goalEnt->ent ) { if( nav.debugMode && bot_showlrgoal->integer > 1 ) G_PrintChasersf( self, "REACHED entity %s\n", ent->classname ? ent->classname : "no classname" ); AI_ClearGoal( self ); return; } if( self->ai->next_node != NODE_INVALID && ( nodes[self->ai->next_node].flags & (NODEFLAGS_REACHATTOUCH|NODEFLAGS_ENTITYREACH) ) ) { for( i = 0; i < nav.num_navigableEnts; i++ ) { if( nav.navigableEnts[i].node == self->ai->next_node && nav.navigableEnts[i].ent == ent ) { if( nav.debugMode && bot_showlrgoal->integer > 1 ) G_PrintChasersf( self, "REACHED touch node %i with entity %s\n", self->ai->next_node, ent->classname ? ent->classname : "no classname" ); AI_NodeReached( self ); return; } } FOREACH_GOALENT( goalEnt ) { i = goalEnt->id; if( goalEnt->node == self->ai->next_node && goalEnt->ent == ent ) { if( nav.debugMode && bot_showlrgoal->integer > 1 ) G_PrintChasersf( self, "REACHED touch node %i with entity %s\n", self->ai->next_node, ent->classname ? ent->classname : "no classname" ); AI_NodeReached( self ); return; } } } }
void AI_SetGoal( edict_t *self, int goal_node ) { int node; self->ai->goal_node = goal_node; node = AI_FindClosestReachableNode( self->s.origin, self, NODE_DENSITY * 3, NODE_ALL ); if( node == NODE_INVALID ) { AI_ClearGoal( self ); return; } // ASTAR if( !AStar_GetPath( node, goal_node, self->ai->status.moveTypesMask, &self->ai->path ) ) { AI_ClearGoal( self ); return; } self->ai->current_node = self->ai->path.nodes[self->ai->path.numNodes]; if( nav.debugMode && bot_showlrgoal->integer > 1 ) G_PrintChasersf( self, "%s: GOAL: new START NODE selected %d goal %d\n", self->ai->pers.netname, node, self->ai->goal_node ); self->ai->next_node = self->ai->current_node; // make sure we get to the nearest node first self->ai->node_timeout = 0; self->ai->longRangeGoalTimeout = 0; self->ai->tries = 0; // Reset the count of how many times we tried this goal }
static bool BOT_DMclass_FindRocket( edict_t *self, vec3_t away_from_rocket ) { #define AI_ROCKET_DETECT_RADIUS 1000 #define AI_ROCKET_DANGER_RADIUS 200 int i, numtargets; int targets[MAX_EDICTS]; edict_t *target; float min_roxx_time = 1.0f; bool any_rocket = false; numtargets = GClip_FindRadius( self->s.origin, AI_ROCKET_DETECT_RADIUS, targets, MAX_EDICTS ); for( i = 0; i < numtargets; i++ ) { target = game.edicts + targets[i]; // Missile detection code if( target->r.svflags & SVF_PROJECTILE && target->s.type != ET_PLASMA ) // (plasmas come in bunchs so are too complex for the bot to dodge) { if( target->r.owner && target->r.owner != self ) { vec3_t end; trace_t trace; VectorMA( target->s.origin, 2, target->velocity, end ); G_Trace( &trace, target->s.origin, target->r.mins, target->r.maxs, end, target, MASK_SOLID ); if( trace.fraction < min_roxx_time ) { vec_t l; any_rocket = true; min_roxx_time = trace.fraction; VectorSubtract( trace.endpos, self->s.origin, end ); // ok... end is where the impact will be. // trace.fraction is the time. if( ( l = VectorLengthFast( end ) ) < AI_ROCKET_DANGER_RADIUS ) { RotatePointAroundVector( away_from_rocket, &axis_identity[AXIS_UP], end, -self->s.angles[YAW] ); VectorNormalize( away_from_rocket ); if( fabs( away_from_rocket[0] ) < 0.3 ) away_from_rocket[0] = 0; if( fabs( away_from_rocket[1] ) < 0.3 ) away_from_rocket[1] = 0; away_from_rocket[2] = 0; away_from_rocket[0] *= -1.0f; away_from_rocket[1] *= -1.0f; if( nav.debugMode && bot_showcombat->integer > 2 ) G_PrintChasersf( self, "%s: ^1projectile dodge: ^2%f, %f d=%f^7\n", self->ai->pers.netname, away_from_rocket[0], away_from_rocket[1], l ); } } } } } return any_rocket; #undef AI_ROCKET_DETECT_RADIUS #undef AI_ROCKET_DANGER_RADIUS }
bool AI_NewNextNode( edict_t *self ) { // reset timeout self->ai->node_timeout = 0; if( self->ai->next_node == self->ai->goal_node ) { if( nav.debugMode && bot_showlrgoal->integer > 1 ) G_PrintChasersf( self, "%s: GOAL REACHED!\n", self->ai->pers.netname ); //if botroam, setup a timeout for it /* if( nodes[self->ai->goal_node].flags & NODEFLAGS_BOTROAM ) { int i; for( i = 0; i < nav.num_broams; i++ ) //find the broam { if( nav.broams[i].node != self->ai->goal_node ) continue; //if(AIDevel.debugChased && bot_showlrgoal->integer) // G_PrintMsg (AIDevel.chaseguy, "%s: BotRoam Time Out set up for node %i\n", self->ai->pers.netname, nav.broams[i].node); //Com_Printf( "%s: BotRoam Time Out set up for node %i\n", self->ai->pers.netname, nav.broams[i].node); self->ai->status.broam_timeouts[i] = level.time + 15000; break; } } */ // don't let it wait too long to weight the inventory again AI_ClearGoal( self ); return false; // force checking for a new long range goal } // we did not reach our goal yet. just setup next node... self->ai->current_node = self->ai->next_node; if( self->ai->path.numNodes ) self->ai->path.numNodes--; self->ai->next_node = self->ai->path.nodes[self->ai->path.numNodes]; return true; }
//========================================== // AI_PickShortRangeGoal // Pick best goal based on importance and range. This function // overrides the long range goal selection for items that // are very close to the bot and are reachable. //========================================== static void AI_PickShortRangeGoal( edict_t *self ) { edict_t *bestGoal = NULL; float bestWeight = 0; nav_ents_t *goalEnt; const gsitem_t *item; bool canPickupItems; int i; if( !self->r.client || G_ISGHOSTING( self ) ) return; if( self->ai->state_combat_timeout > level.time ) { self->ai->shortRangeGoalTimeout = self->ai->state_combat_timeout; return; } if( self->ai->shortRangeGoalTimeout > level.time ) return; canPickupItems = (self->r.client->ps.pmove.stats[PM_STAT_FEATURES] & PMFEAT_ITEMPICK) != 0 ? true : false; self->ai->shortRangeGoalTimeout = level.time + AI_SHORT_RANGE_GOAL_DELAY; self->movetarget = NULL; FOREACH_GOALENT( goalEnt ) { float dist; i = goalEnt->id; if( !goalEnt->ent->r.inuse || goalEnt->ent->r.solid == SOLID_NOT ) continue; if( goalEnt->ent->r.client ) continue; if( self->ai->status.entityWeights[i] <= 0.0f ) continue; item = goalEnt->ent->item; if( canPickupItems && item ) { if( !G_Gametype_CanPickUpItem( item ) || !( item->flags & ITFLAG_PICKABLE ) ) { continue; } } dist = DistanceFast( self->s.origin, goalEnt->ent->s.origin ); if( goalEnt == self->ai->goalEnt ) { if( dist > AI_GOAL_SR_LR_RADIUS ) continue; } else { if( dist > AI_GOAL_SR_RADIUS ) continue; } clamp_low( dist, 0.01f ); if( AI_ShortRangeReachable( self, goalEnt->ent->s.origin ) ) { float weight; bool in_front = G_InFront( self, goalEnt->ent ); // Long range goal gets top priority if( in_front && goalEnt == self->ai->goalEnt ) { bestGoal = goalEnt->ent; break; } // get the one with the best weight weight = self->ai->status.entityWeights[i] / dist * (in_front ? 1.0f : 0.5f); if( weight > bestWeight ) { bestWeight = weight; bestGoal = goalEnt->ent; } } } if( bestGoal ) { self->movetarget = bestGoal; if( nav.debugMode && bot_showsrgoal->integer ) G_PrintChasersf( self, "%i %s: selected a %s for SR goal.\n", level.framenum, self->ai->pers.netname, self->movetarget->classname ); } else { // got nothing else to do so keep scanning self->ai->shortRangeGoalTimeout = level.time + AI_SHORT_RANGE_GOAL_DELAY_IDLE; } }
//========================================== // AI_PickLongRangeGoal // // Evaluate the best long range goal and send the bot on // its way. This is a good time waster, so use it sparingly. // Do not call it for every think cycle. // // jal: I don't think there is any problem by calling it, // now that we have stored the costs at the nav.costs table (I don't do it anyway) //========================================== void AI_PickLongRangeGoal( edict_t *self ) { #define WEIGHT_MAXDISTANCE_FACTOR 20000.0f #define COST_INFLUENCE 0.5f int i; float weight, bestWeight = 0.0; int current_node; float cost; float dist; nav_ents_t *goalEnt, *bestGoalEnt = NULL; AI_ClearGoal( self ); if( G_ISGHOSTING( self ) ) return; if( self->ai->longRangeGoalTimeout > level.time ) return; if( !self->r.client->ps.pmove.stats[PM_STAT_MAXSPEED] ) { return; } self->ai->longRangeGoalTimeout = level.time + AI_LONG_RANGE_GOAL_DELAY + brandom( 0, 1000 ); // look for a target current_node = AI_FindClosestReachableNode( self->s.origin, self, ( ( 1 + self->ai->nearest_node_tries ) * NODE_DENSITY ), NODE_ALL ); self->ai->current_node = current_node; if( current_node == NODE_INVALID ) { if( nav.debugMode && bot_showlrgoal->integer ) G_PrintChasersf( self, "%s: LRGOAL: Closest node not found. Tries:%i\n", self->ai->pers.netname, self->ai->nearest_node_tries ); self->ai->nearest_node_tries++; // extend search radius with each try return; } self->ai->nearest_node_tries = 0; // Run the list of potential goal entities FOREACH_GOALENT( goalEnt ) { i = goalEnt->id; if( !goalEnt->ent ) continue; if( !goalEnt->ent->r.inuse ) { goalEnt->node = NODE_INVALID; continue; } if( goalEnt->ent->r.client ) { if( G_ISGHOSTING( goalEnt->ent ) || goalEnt->ent->flags & (FL_NOTARGET|FL_BUSY) ) goalEnt->node = NODE_INVALID; else goalEnt->node = AI_FindClosestReachableNode( goalEnt->ent->s.origin, goalEnt->ent, NODE_DENSITY, NODE_ALL ); } if( goalEnt->ent->item ) { if( !G_Gametype_CanPickUpItem( goalEnt->ent->item ) ) continue; } if( goalEnt->node == NODE_INVALID ) continue; weight = self->ai->status.entityWeights[i]; if( weight <= 0.0f ) continue; // don't try to find cost for too far away objects dist = DistanceFast( self->s.origin, goalEnt->ent->s.origin ); if( dist > WEIGHT_MAXDISTANCE_FACTOR * weight/* || dist < AI_GOAL_SR_RADIUS*/ ) continue; cost = AI_FindCost( current_node, goalEnt->node, self->ai->status.moveTypesMask ); if( cost == NODE_INVALID ) continue; cost -= brandom( 0, 2000 ); // allow random variations clamp_low( cost, 1 ); weight = ( 1000 * weight ) / ( cost * COST_INFLUENCE ); // Check against cost of getting there if( weight > bestWeight ) { bestWeight = weight; bestGoalEnt = goalEnt; } } if( bestGoalEnt ) { self->ai->goalEnt = bestGoalEnt; AI_SetGoal( self, bestGoalEnt->node ); if( self->ai->goalEnt != NULL && nav.debugMode && bot_showlrgoal->integer ) G_PrintChasersf( self, "%s: selected a %s at node %d for LR goal. (weight %f)\n", self->ai->pers.netname, self->ai->goalEnt->ent->classname, self->ai->goalEnt->node, bestWeight ); return; } if( nav.debugMode && bot_showlrgoal->integer ) G_PrintChasersf( self, "%s: did not find a LR goal.\n", self->ai->pers.netname ); #undef WEIGHT_MAXDISTANCE_FACTOR #undef COST_INFLUENCE }
void BOT_DMclass_Move( edict_t *self, usercmd_t *ucmd ) { #define BOT_FORWARD_EPSILON 0.5f int i; unsigned int linkType; bool printLink = false; bool nodeReached = false; bool specialMovement = false; vec3_t v1, v2; vec3_t lookdir, pathdir; float lookDot; if( self->ai->next_node == NODE_INVALID || self->ai->goal_node == NODE_INVALID ) { BOT_DMclass_MoveWander( self, ucmd ); return; } linkType = AI_CurrentLinkType( self ); specialMovement = ( self->ai->path.numNodes >= MIN_BUNNY_NODES ) ? true : false; if( AI_GetNodeFlags( self->ai->next_node ) & (NODEFLAGS_REACHATTOUCH|NODEFLAGS_ENTITYREACH) ) specialMovement = false; if( linkType & (LINK_JUMP|LINK_JUMPPAD|LINK_CROUCH|LINK_FALL|LINK_WATER|LINK_LADDER|LINK_ROCKETJUMP) ) specialMovement = false; if( self->ai->pers.skillLevel < 0.33f ) specialMovement = false; if( specialMovement == false || self->groundentity ) self->ai->is_bunnyhop = false; VectorSubtract( nodes[self->ai->next_node].origin, self->s.origin, self->ai->move_vector ); // 2D, normalized versions of look and path directions pathdir[0] = self->ai->move_vector[0]; pathdir[1] = self->ai->move_vector[1]; pathdir[2] = 0.0f; VectorNormalize( pathdir ); AngleVectors( self->s.angles, lookdir, NULL, NULL ); lookdir[2] = 0.0f; VectorNormalize( lookdir ); lookDot = DotProduct( lookdir, pathdir ); // Ladder movement if( self->is_ladder ) { ucmd->forwardmove = 0; ucmd->upmove = 1; ucmd->sidemove = 0; if( nav.debugMode && printLink ) G_PrintChasersf( self, "LINK_LADDER\n" ); nodeReached = AI_NodeReached_Generic( self ); } else if( linkType & LINK_JUMPPAD ) { VectorCopy( self->s.origin, v1 ); VectorCopy( nodes[self->ai->next_node].origin, v2 ); v1[2] = v2[2] = 0; if( DistanceFast( v1, v2 ) > 32 && lookDot > BOT_FORWARD_EPSILON ) { ucmd->forwardmove = 1; // push towards destination ucmd->buttons |= BUTTON_WALK; } nodeReached = self->groundentity != NULL && AI_NodeReached_Generic( self ); } // Platform riding - No move, riding elevator else if( linkType & LINK_PLATFORM ) { VectorCopy( self->s.origin, v1 ); VectorCopy( nodes[self->ai->next_node].origin, v2 ); v1[2] = v2[2] = 0; if( DistanceFast( v1, v2 ) > 32 && lookDot > BOT_FORWARD_EPSILON ) ucmd->forwardmove = 1; // walk to center ucmd->buttons |= BUTTON_WALK; ucmd->upmove = 0; ucmd->sidemove = 0; if( nav.debugMode && printLink ) G_PrintChasersf( self, "LINK_PLATFORM (riding)\n" ); self->ai->move_vector[2] = 0; // put view horizontal nodeReached = AI_NodeReached_PlatformEnd( self ); } // entering platform else if( AI_GetNodeFlags( self->ai->next_node ) & NODEFLAGS_PLATFORM ) { ucmd->forwardmove = 1; ucmd->upmove = 0; ucmd->sidemove = 0; if( lookDot <= BOT_FORWARD_EPSILON ) ucmd->buttons |= BUTTON_WALK; if( nav.debugMode && printLink ) G_PrintChasersf( self, "NODEFLAGS_PLATFORM (moving to plat)\n" ); // is lift down? for( i = 0; i < nav.num_navigableEnts; i++ ) { if( nav.navigableEnts[i].node == self->ai->next_node ) { //testing line //vec3_t tPoint; //int j; //for(j=0; j<3; j++)//center of the ent // tPoint[j] = nav.ents[i].ent->s.origin[j] + 0.5*(nav.ents[i].ent->r.mins[j] + nav.ents[i].ent->r.maxs[j]); //tPoint[2] = nav.ents[i].ent->s.origin[2] + nav.ents[i].ent->r.maxs[2]; //tPoint[2] += 8; //AITools_DrawLine( self->s.origin, tPoint ); //if not reachable, wait for it (only height matters) if( ( nav.navigableEnts[i].ent->s.origin[2] + nav.navigableEnts[i].ent->r.maxs[2] ) > ( self->s.origin[2] + self->r.mins[2] + AI_JUMPABLE_HEIGHT ) && nav.navigableEnts[i].ent->moveinfo.state != STATE_BOTTOM ) { self->ai->blocked_timeout = level.time + 10000; ucmd->forwardmove = 0; } } } nodeReached = AI_NodeReached_PlatformStart( self ); } // Falling off ledge or jumping else if( !self->groundentity && !self->is_step && !self->is_swim && !self->ai->is_bunnyhop ) { ucmd->upmove = 0; ucmd->sidemove = 0; ucmd->forwardmove = 0; if( lookDot > BOT_FORWARD_EPSILON ) { ucmd->forwardmove = 1; // add fake strafe accel if( !(linkType & LINK_FALL) || linkType & (LINK_JUMP|LINK_ROCKETJUMP) ) { if( linkType & LINK_JUMP ) { if( AI_AttemptWalljump( self ) ) { ucmd->buttons |= BUTTON_SPECIAL; } if( VectorLengthFast( tv( self->velocity[0], self->velocity[1], 0 ) ) < 600 ) VectorMA( self->velocity, 6.0f, lookdir, self->velocity ); } else { if( VectorLengthFast( tv( self->velocity[0], self->velocity[1], 0 ) ) < 450 ) VectorMA( self->velocity, 1.0f, lookdir, self->velocity ); } } } else if( lookDot < -BOT_FORWARD_EPSILON ) ucmd->forwardmove = -1; if( nav.debugMode && printLink ) G_PrintChasersf( self, "FLY MOVE\n" ); nodeReached = AI_NodeReached_Generic( self ); } else // standard movement { ucmd->forwardmove = 1; ucmd->upmove = 0; ucmd->sidemove = 0; // starting a jump if( ( linkType & LINK_JUMP ) ) { if( self->groundentity ) { trace_t trace; vec3_t v1, v2; if( nav.debugMode && printLink ) G_PrintChasersf( self, "LINK_JUMP\n" ); //check floor in front, if there's none... Jump! VectorCopy( self->s.origin, v1 ); VectorNormalize2( self->ai->move_vector, v2 ); VectorMA( v1, 18, v2, v1 ); v1[2] += self->r.mins[2]; VectorCopy( v1, v2 ); v2[2] -= AI_JUMPABLE_HEIGHT; G_Trace( &trace, v1, vec3_origin, vec3_origin, v2, self, MASK_AISOLID ); if( !trace.startsolid && trace.fraction == 1.0 ) { //jump! // prevent double jumping on crates VectorCopy( self->s.origin, v1 ); v1[2] += self->r.mins[2]; G_Trace( &trace, v1, tv( -12, -12, -8 ), tv( 12, 12, 0 ), v1, self, MASK_AISOLID ); if( trace.startsolid ) ucmd->upmove = 1; } } nodeReached = AI_NodeReached_Generic( self ); } // starting a rocket jump else if( ( linkType & LINK_ROCKETJUMP ) ) { if( nav.debugMode && printLink ) G_PrintChasersf( self, "LINK_ROCKETJUMP\n" ); if( !self->ai->rj_triggered && self->groundentity && ( self->s.weapon == WEAP_ROCKETLAUNCHER ) ) { self->s.angles[PITCH] = 170; ucmd->upmove = 1; ucmd->buttons |= BUTTON_ATTACK; self->ai->rj_triggered = true; } nodeReached = AI_NodeReached_Generic( self ); } else { // Move To Short Range goal (not following paths) // plats, grapple, etc have higher priority than SR Goals, cause the bot will // drop from them and have to repeat the process from the beginning if( AI_MoveToShortRangeGoalEntity( self, ucmd ) ) { nodeReached = AI_NodeReached_Generic( self ); } else if( specialMovement && !self->is_swim ) // bunny-hopping movement here { BOT_DMclass_SpecialMove( self, lookdir, pathdir, ucmd ); nodeReached = AI_NodeReached_Special( self ); } else { nodeReached = AI_NodeReached_Generic( self ); } } // if static assume blocked and try to get free if( VectorLengthFast( self->velocity ) < 37 && ( ucmd->forwardmove || ucmd->sidemove || ucmd->upmove ) ) { if( random() > 0.1 && AI_SpecialMove( self, ucmd ) ) // jumps, crouches, turns... return; self->s.angles[YAW] += brandom( -90, 90 ); } } // swimming if( self->is_swim ) { if( !( G_PointContents( nodes[self->ai->next_node].origin ) & MASK_WATER ) ) // Exit water ucmd->upmove = 1; } AI_ChangeAngle( self ); if( nodeReached ) AI_NodeReached( self ); #undef BOT_FORWARD_EPSILON }
//========================================== // BOT_DMclass_FireWeapon // Fire if needed //========================================== static bool BOT_DMclass_FireWeapon( edict_t *self, usercmd_t *ucmd ) { #define WFAC_GENERIC_PROJECTILE 300.0 #define WFAC_GENERIC_INSTANT 150.0 float firedelay; vec3_t target; int weapon, i; float wfac; vec3_t fire_origin; trace_t trace; bool continuous_fire = false; firedef_t *firedef = GS_FiredefForPlayerState( &self->r.client->ps, self->r.client->ps.stats[STAT_WEAPON] ); if( !self->enemy ) return false; weapon = self->s.weapon; if( weapon < 0 || weapon >= WEAP_TOTAL ) weapon = 0; if( !firedef ) return false; // Aim to center of the box for( i = 0; i < 3; i++ ) target[i] = self->enemy->s.origin[i] + ( 0.5f * ( self->enemy->r.maxs[i] + self->enemy->r.mins[i] ) ); fire_origin[0] = self->s.origin[0]; fire_origin[1] = self->s.origin[1]; fire_origin[2] = self->s.origin[2] + self->viewheight; if( self->s.weapon == WEAP_LASERGUN || self->s.weapon == WEAP_PLASMAGUN ) continuous_fire = true; if( !continuous_fire && !BOT_DMclass_CheckShot( self, target ) ) return false; // find out our weapon AIM style if( AIWeapons[weapon].aimType == AI_AIMSTYLE_PREDICTION_EXPLOSIVE ) { // in the lowest skill level, don't predict projectiles if( self->ai->pers.skillLevel >= 0.33f ) BOT_DMclass_PredictProjectileShot( self, fire_origin, firedef->speed, target, self->enemy->velocity ); wfac = WFAC_GENERIC_PROJECTILE * 1.3; // aim to the feet when enemy isn't higher if( fire_origin[2] > ( target[2] + ( self->enemy->r.mins[2] * 0.8 ) ) ) { vec3_t checktarget; VectorSet( checktarget, self->enemy->s.origin[0], self->enemy->s.origin[1], self->enemy->s.origin[2] + self->enemy->r.mins[2] + 4 ); G_Trace( &trace, fire_origin, vec3_origin, vec3_origin, checktarget, self, MASK_SHOT ); if( trace.fraction == 1.0f || ( trace.ent > 0 && game.edicts[trace.ent].takedamage ) ) VectorCopy( checktarget, target ); } else if( !AI_IsStep( self->enemy ) ) wfac *= 2.5; // more imprecise for air rockets } else if( AIWeapons[weapon].aimType == AI_AIMSTYLE_PREDICTION ) { if( self->s.weapon == WEAP_PLASMAGUN ) wfac = WFAC_GENERIC_PROJECTILE * 0.5; else wfac = WFAC_GENERIC_PROJECTILE; // in the lowest skill level, don't predict projectiles if( self->ai->pers.skillLevel >= 0.33f ) BOT_DMclass_PredictProjectileShot( self, fire_origin, firedef->speed, target, self->enemy->velocity ); } else if( AIWeapons[weapon].aimType == AI_AIMSTYLE_DROP ) { //jalToDo wfac = WFAC_GENERIC_PROJECTILE; // in the lowest skill level, don't predict projectiles if( self->ai->pers.skillLevel >= 0.33f ) BOT_DMclass_PredictProjectileShot( self, fire_origin, firedef->speed, target, self->enemy->velocity ); } else // AI_AIMSTYLE_INSTANTHIT { if( self->s.weapon == WEAP_ELECTROBOLT ) wfac = WFAC_GENERIC_INSTANT; else if( self->s.weapon == WEAP_LASERGUN ) wfac = WFAC_GENERIC_INSTANT * 1.5; else wfac = WFAC_GENERIC_INSTANT; } wfac = 25 + wfac * ( 1.0f - self->ai->pers.skillLevel ); // look to target VectorSubtract( target, fire_origin, self->ai->move_vector ); if( self->r.client->ps.weaponState == WEAPON_STATE_READY || self->r.client->ps.weaponState == WEAPON_STATE_REFIRE || self->r.client->ps.weaponState == WEAPON_STATE_REFIRESTRONG ) { // in continuous fire weapons don't add delays if( self->s.weapon == WEAP_LASERGUN || self->s.weapon == WEAP_PLASMAGUN ) firedelay = 1.0f; else firedelay = ( 1.0f - self->ai->pers.skillLevel ) - ( random()-0.25f ); if( firedelay > 0.0f ) { if( G_InFront( self, self->enemy ) ) { ucmd->buttons |= BUTTON_ATTACK; // could fire, but wants to? } // mess up angles only in the attacking frames if( self->r.client->ps.weaponState == WEAPON_STATE_READY || self->r.client->ps.weaponState == WEAPON_STATE_REFIRE || self->r.client->ps.weaponState == WEAPON_STATE_REFIRESTRONG ) { if( (self->s.weapon == WEAP_LASERGUN) || (self->s.weapon == WEAP_PLASMAGUN) ) { target[0] += sinf( (float)level.time/100.0) * wfac; target[1] += cosf( (float)level.time/100.0) * wfac; } else { target[0] += ( random()-0.5f ) * wfac; target[1] += ( random()-0.5f ) * wfac; } } } } //update angles VectorSubtract( target, fire_origin, self->ai->move_vector ); AI_ChangeAngle( self ); if( nav.debugMode && bot_showcombat->integer ) G_PrintChasersf( self, "%s: attacking %s\n", self->ai->pers.netname, self->enemy->r.client ? self->enemy->r.client->netname : self->classname ); return true; }