コード例 #1
0
ファイル: ai_navigation.cpp プロジェクト: Clever-Boy/qfusion
/*
* 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;
			}
		}
	}
}
コード例 #2
0
ファイル: ai_navigation.cpp プロジェクト: Clever-Boy/qfusion
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
}
コード例 #3
0
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
}
コード例 #4
0
ファイル: ai_navigation.cpp プロジェクト: Clever-Boy/qfusion
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;
}
コード例 #5
0
ファイル: ai_main.cpp プロジェクト: MGXRace/racesow
//==========================================
// 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;
	}
}
コード例 #6
0
ファイル: ai_main.cpp プロジェクト: MGXRace/racesow
//==========================================
// 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
}
コード例 #7
0
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
}
コード例 #8
0
//==========================================
// 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;
}