Author: beffy (c++programmer)

So you got bots in your game running around randomly and maybe to specific locations? Cool! (If not, make sure you've read my previous tutorial on adding bots to a game ;-) ...) But wouldn't it be cooler to make them hunt you? Move along waypoints? Or search the next nearest player and hunt him? Well, you are pretty close... read on!

First of all, I took the test mission and added some waypoints in the world editor creator (F11 - F4) and put them in a seperate SimGroup to access them from script later on:
   new SimGroup(BotMarkers) {
      new WayPoint() {
         position = "84.7609 -329.467 181.154";
         rotation = "1 0 0 0";
         scale = "1 1 1";
         dataBlock = "WayPointMarker";
         name = "1";
         team = "0";
      };
      new WayPoint() {
         position = "91.9828 -332.776 184.206";
         rotation = "1 0 0 0";
         scale = "1 1 1";
         dataBlock = "WayPointMarker";
         name = "2";
         team = "0";
      };
      new WayPoint() {
         position = "98.164 -330.142 187.618";
         rotation = "1 0 0 0";
         scale = "1 1 1";
         dataBlock = "WayPointMarker";
         name = "3";
         team = "0";
      };
      new WayPoint() {
         position = "119.626 -343.224 208.346";
         rotation = "1 0 0 0";
         scale = "1 1 1";
         dataBlock = "WayPointMarker";
         name = "4";
         team = "0";
      };
      new WayPoint() {
         position = "138.997 -327.711 225.599";
         rotation = "1 0 0 0";
         scale = "1 1 1";
         dataBlock = "WayPointMarker";
         name = "5";
         team = "0";
      };
   };

For the next part we need to add a little snipit to the engine code to clear the bots aim through script.
The function itself is already in aiPlayer.cc, it simply sets the aim location to the current destination point of the AI, like this:


/**
 * Clears the aim location and sets it to the bot's
 * current destination so he looks where he's going
 */
void AIPlayer::clearAim() {
   mAimLocation = Point3F( 0.0f, 0.0f, 0.0f );
   mAimToDestination = true;
}

It just wasn't exposed to the scripting engine, so I did it:


/**
 * Clears the point the AI is aiming at
 */
ConsoleMethod( AIConnection, clearAim, void, 2, 2, "ai.clearAim();" ) {
   AIPlayer *ai = static_cast<AIPlayer*>( object );
   ai->clearAim();
}


Then, in aiPlayer.cs, I got a handy little function following any waypoints in this SimGroup and/or chasing the player if finished:


function AIPlayer::handleWaypointsAndChase(%this)
{
   // look for next "available" human player... :-)
   %nextClient = ClientGroup.getObject(AIPlayer::getClosestHuman(%this));
   $daPlaya = %nextClient.player;
   if(!isObject($daPlaya) || !isObject(%this))
   {
      return;
   }
   	// get his position:
	%playPos = $daPlaya.getPosition();
	// but first look for some waypoints...
	// if there are some waypoints to follow
	if(isObject(BotMarkers))
	{
		%num = getNumberBotMarkers();
		// %this.countMyMoves tracks the number of waypoints already passed by this AI
		if(%this.countMyMoves $= "")
		{
			//echo number of waypoint once
			echo(%num @ " BotMarkers found!");
		}
		// output player posititon
		error("Player Position: " @ %playPos);
		// as long as this bot hasn't completed the waypoint path:
		if(%this.countMyMoves < BotMarkers.getCount())
		{
			%dest = BotMarkers.getObject(%this.countMyMoves).position;
			echo("Next destination: " @ %dest);
			echo("The distance is: " @ getMarkerDistance(%this.countMyMoves, %this));
			echo("myMoves along the waypoint path:" SPC %this.countMyMoves);
			%this.setMoveDestination(%dest);
			%this.move();
			%this.countMyMoves++;
		}
		// okay, finished path, time for hunting the player...
		else
		{
			AIPlayer::shootHimIfYouSeeHim(%this, %playPos);
		}
	}
	// else: No waypoints, so chase the player!
	else
	{
	  AIPlayer::shootHimIfYouSeeHim(%this, %playPos);
	}
}


This next function bascially tests if the player's position is in view and if it is, the AI aims and shoots at him (if a weapon and some ammo are available -
NOTE: hasInventory() is defined in inventory.cs. I'm using Spock's Inventory Manager, though - I will show you two versions of the function, hold on...).
function AIPlayer::shootHimIfYouSeeHim(%this, %playPos)
{
   %this.setTargetObject($daPlaya);
   %iSeeHim = aiPlayer::isObjectInView(%this, $daPlaya);
   error(%this.player SPC "isObjectInView:" SPC %iSeeHim);
   if(%iSeeHim)
   {
      error(%this.player @ ": I see him!!!");
      %this.setAimLocation($daPlaya.getWorldBoxCenter());
      // do we have a weapon?
      error(%this.player SPC "has Weapon:" SPC %this.player.hasInventory("Crossbow"));
      error(%this.player SPC "has Ammo:" SPC %this.player.hasInventory("CrossbowAmmo"));
      if ( (%this.player.hasInventory("Crossbow") && %this.player.hasInventory("CrossbowAmmo"))
            || (%this.player.hasInventory("Rifle") && %this.player.hasInventory("RifleAmmo")) )
      {
		// shoooooot
		%this.setTrigger(0,true);
		// UPDATE 02/10/10: the new AI player class in the HEAD uses
		// setImageTrigger(0,true); 
		// to make the bot shoot... thx to Sabrecyd for investigating :)
		// haven't tried it yet, though...
      }
      else
      {
         error(%this.player SPC "has no weapon/ammo!");
      }
      %this.setMoveDestination(%playPos);
      %this.move();
   }
   // no player in sight?
   else
   {
	// clear aim
	%this.clearAim();
	// check for next nearest player
	%nextClient = ClientGroup.getObject(AIPlayer::getClosestHuman(%this));
	$daPlaya = %nextClient.player;
	if(!isObject($daPlaya))
	{
		return;
	}
	%playPos = $daPlaya.getPosition();
	%this.setMoveDestination(%playPos);
	%this.setAimLocation($daPlaya.getWorldBoxCenter());
	%this.move();
  }
}


so here are the functions I added in inventory.cs to check for available weapons, the first one is for the "traditional" inventory system, the second one is for Spock's inventory manager:
// "traditional"
function ShapeBase::hasInventory(%this, %data) {
    return %this.inv[%data];
}
// "Spock's way" ;-)
function ShapeBase::hasInventory(%this, %data) {
	return %this.getInvItem(%data, "amount");
}


The next function checks if the target is in view. First it sets the sight range of the AI, you might want to adjust this...
// this one's provided by Ryan Mette
function aiPlayer::isObjectInView(%this, %object)
{
   %player = %this.player;
   // default sight range of AI:
   %this.sightRange = 70.0;
   if (!(isObject(%player) && isObject(%object))) return;

   %objPos = %object.getWorldBoxCenter();
   %eyePoint = %player.getWorldBoxCenter();
   %distance = VectorDist(%objPos, %eyePoint);
   
   error("DISTANCE:" SPC %distance);

   if (%distance <= %this.sightRange)
   {
      // if the object is within 1.5 meters sometimes it can
      // fall out of the field of view due to the eye height
      if (%distance > 2)
      {
      	%eyeTransform = %player.getEyeTransform();
      	%eyePoint = firstWord(%eyeTransform)
      	   SPC getWord(%eyeTransform, 1)
      	   SPC getWord(%eyeTransform, 2);
      }
      %eyeVector = VectorNormalize(%player.getEyeVector());
      
    //make sure we're not looking through walls...
    %mask = $TypeMasks::TerrainObjectType |
           $TypeMasks::InteriorObjectType |
           $TypeMasks::StaticShapeObjectType;
    %losResult = containerRayCast(%objPos, %eyePoint, %mask);
    %losObject = GetWord(%losResult, 0);
    if (!isObject(%losObject))
    {
         //create the vector from this client to the client
         %objVector = VectorNormalize(VectorSub(%objPos, %eyePoint));

         // dot product to determine field of view
         %dot = VectorDot(%objVector, %eyeVector);

         // within field of view
   	    return (%dot > 0.6);
    }
   }
   return false;
}


The next piece of code searches for the closest human player. It simply iterates through the ClientGroup and checks the distance to the available clients.
// provided by Jared Hoberock
// I changed it to use ClientGroup instead of its own array like Jared did...
function AIPlayer::getClosestHuman(%this) 
{ 
	%botpos = %this.getLocation(); // get bot position 
	%count = ClientGroup.getCount(); // number of player 
	%tempDist2 = 32000; // set this pretty high 
	for(%i = 0; %i < %count; %i++) // step through 
	{ 
		%client = ClientGroup.getObject(%i); 
		if(isObject(%client.player)) // only if client.player is an object 
		{ 
			if(%client.isAIControlled())
				continue; // we dont want to find other bots, do we...
			%playPos = %client.player.getPosition(); // player position 
			%tempDist = VectorDist(%playPos, %botPos); // distance player - bot 
			if(%tempDist2 > %tempDist) // if Dist2 greater Dist we take the smaller 
			{ 
				%tempDist2 = %tempDist; // save shortest Dist 
				%index = %i; // set %index 
			} 
		} 
	} 
	return %index; 
} 


Now some little helper functions: the first returns the number of available bot waypoints, the second returns the distance to the next waypoint.
function getNumberBotMarkers()
{
	%count = BotMarkers.getCount();
	return %count;
}
function getMarkerDistance(%markernumber, %bot)
{
	%botPosition = %bot.getLocation();
	%markerObject = BotMarkers.getObject(%markernumber);
	%dist = vectorDist( %botPosition, %markerObject.position );
	return %dist;
}


Yep, that should be it... If I missed something, please let me know ;-)
Have fun!