Game Development Community

Getting the bots to fire or jump

by Dennis Mathews · in Torque Game Engine · 03/15/2002 (7:53 am) · 24 replies

I have been having trouble getting the AIPlayers to do anthing but move. I was wondering if someone could show me the way to get them to do some basic things like jump or shoot.
Page «Previous 1 2
#1
03/15/2002 (11:12 pm)
Posted: Mar 15, 2002 23:26 GMT

In aiPlayer.cc there is a member array mTriggerCount[x] that maintains the trigger state for the aiPlayer. Making the bot fire is actually just a matter of setting the trigger in the AIPlayer::getMoveList function.

Here is a snippet from that function, I belive that in the latest torque version there is a block regarding triggers at the bottom of the function which you need to remove.

--------------------------------------------------------------------------------
// Copy over the trigger status
for(int i = 0; i < MaxTriggerKeys; i++)
mMove.trigger[i] = mTriggers[i];
--------------------------------------------------------------------------------


Then you may need a setTrigger and getTrigger function in aiPlayer to control that member array. 0 and 1 are fire triggers.

BTW - Jump is trigger 2 so setting mTriggers[2] = true will jump (repeatedly).

Hope that helps,
Ryan Mette
21-6 Productions
#2
03/16/2002 (1:36 am)
In script, you could set these triggers like that (I've made a quick test in aiPlayer.cs, as soon as the bots arrive at any location, they begin to fire and jump - so I've put this code in AIPLayer::onReachDestination(), which doesn't make too much sense, though... ;-)
%playPos = $daPlaya.getPosition();
            %this.setTargetObject($daPlaya);
            %this.setAimLocation(%playPos);
            echo("AimLocation:" SPC %this.getAimLocation());
            // shoooooot
            %this.setTrigger(0,true);
            %this.setMoveDestination(%playPos);
            %this.move();
            // juuuump
            %this.setTrigger(2,true);
$daPlaya is a global var I've set somewhere else to track the current client player ($daPlaya = %client.player)...

Hope this helps to get started...
#3
03/16/2002 (2:40 am)
that didnt do anything for me,,,cuz i have this in my monster.cc (exact copy of the aiplayer.cc).
//ace commented out
// Copy over the trigger status
for( int i = 0; i < MaxTriggerKeys; i++ ) {
mMove.trigger[i] = mTriggers[i];
// mTriggers[i] = false;
mTriggers[0] = true;
mTriggers[1] = true;
mTriggers[2] = true;
}
}

which works well when you push the bind to make them run except they jump with the antigravity belt (dont need no jets hehe), i put your script in my monster.cs like so:

$daPlaya = %client.player
function Monster::onReachDestination() {
echo( "onReachDestination" );
%playPos = $daPlaya.getPosition();
%this.setTargetObject($daPlaya);
%this.setAimLocation(%playPos);
echo("AimLocation:" SPC %this.getAimLocation());
// shoooooot
%this.setTrigger(0,true);
%this.setMoveDestination(%playPos);
%this.move();
// juuuump
%this.setTrigger(2,true);

}
#4
03/16/2002 (2:48 am)
ok i for got the ; on the first part :) [edit ok it was my imagination/]
#5
03/16/2002 (2:53 am)
Mhm, %client.player will not be defined in aiPlayer.cs, so I've set $daPlaya = %client.player in commands.cs (where I "spawn" my bots), like this:
function serverCmdMoveBotsToPlayer(%client)
{
   if (isObject(%client.player))
   {
         $daPlaya = %client.player;
         ...
and then you can access it from any other script.

Furthermore, my AIPlayer::onReachDestination has a %this
handle and therefore reads like this:
function AIPlayer::onReachDestination(%this) {
    echo( "onReachDestination" );
    // the first time the bot reaches any destination, he is programmed to
    // follow the waypoint path, if any, and hunt the player after finishing it:
    handleWaypointsAndChase(%this);
}

Then I handle some waypoint and "player hunting" stuff in
this function (I doesn't really work well, the bots don't hit me if I don't want to... ;-) - the "hunting" code would need much more work, but I just wanted to get the basic idea....
//-----------------------------------------------------------------------------
// AI Helpers
//-----------------------------------------------------------------------------
function handleWaypointsAndChase(%this)
{
    %playPos = $daPlaya.getPosition();
    if(isObject(BotMarkers))
    {
        %num = getNumberBotMarkers();
        if(%this.countMyMoves $= "")
        {
            echo(%num @ " BotMarkers found!");
        }
        echo("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
       {
            if(%this.countMyMoves == %num)
            {
                echo("Last Waypoint reached! Now hunting player :-)");
                echo("My Target:" SPC %this.getTargetObject());
            }
            %this.setTargetObject($daPlaya);
            %this.setAimLocation(%playPos);
            echo("AimLocation:" SPC %this.getAimLocation());
            // shoooooot
            %this.setTrigger(0,true);
            %this.setMoveDestination(%playPos);
            %this.move();
            // juuuump
            %this.setTrigger(2,true);
       }
    }
    else
    {
        // No waypoints, so chase the player!
        %this.setTargetObject($daPlaya);
        %this.setAimLocation($daPlaya.getPosition());
        %this.setTrigger(0,true);
        %this.setMoveDestination(%playPos);
        %this.move();
    }
}

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;
}

That should also work in your monster.cs ... let me know!
#6
03/16/2002 (3:42 am)
oh wow thats a definate improvement i had to remove the jumpcode from the monsters

my worst enemy is the scripts i d just asoon remove them all (but it will be good for people to mod with so i wont).

hey now it needs a radius for a player being in range and the trigger code need s a visual so it only fires when the player is in sight,,, or more ammo.hehe good job man i been busy with other stuff but this is inspiring :)
thanks

[edit ,,btw i have all of this in the monster.cs and it works,,,,do the monsters/bots suposed to follow you constantly even after they respawn? i like it:)/]

[edit2...i forgot my manners...thanks/]
#7
03/16/2002 (6:51 am)
This function may be useful to you, I use this function when aquiring a target and when determining if it should fire at that target.
function aiPlayer::isObjectInView(%this, %object)
{
   %player = %this.player;
   if (!(isObject(%player) && isObject(%object))) return;

   %objPos = %object.getWorldBoxCenter();
   %eyePoint = %player.getWorldBoxCenter();
   %distance = VectorDist(%objPos, %eyePoint);

   if (%distance <= %this.sightRange)
   {
      // if the object is within 2 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;
}
BTW - The aiPlayer namespace is tied to the AI GameConnection created when you use the aiAddPlayer() function. (contained in aiPlayer.cc) Almost all of my AI script functions are part of this namespace, for instance aiPlayer::setMode(), aiPlayer::interceptTarget(), aiPlayer::attackTarget() and so on. Now, if you pass a classType into that aiAddPlayer function, a new namespace for that class will be created. This way you can override the aiPlayer:: functions with let's say Bug::attackTarget(). This allows you to assign unique behaviors based on that AI class. Hope that makes sense ;) If not, let me know, happy to help.

Ryan Mette
21-6 Productions
#8
03/16/2002 (7:06 am)
hey awesome,this is turnning into somthing good:)

i have eliminated the need for the bind of moving the monsters/botstoPlayer

$monsterCounter = 0;
function serverCmdAddMonster(%client) {
$monsters[$monsterCounter] = aiAddMonster("Monster" @ $monsterCounter);
MissionCleanup.add($monsters[$monsterCounter]);
$monsterCounter++;
//}
//function serverCmdMoveMonstersToPlayer(%client)
//{

if (isObject(%client.player))
{


for(%i = 0; %i < $monsterCounter; %i++)
{
if(isObject($monsters[%i])){

$monsters[%i].setMoveDestination( %client.player.getPosition() );
$monsters[%i].move();
$daPlaya = %client.player;
}
}
}
}
//}


thanks
#9
03/17/2002 (8:57 am)
Hey Ryan, cool function!
So this is my waypoint/hunting test so far:
//-----------------------------------------------------------------------------
// AI Helpers
//-----------------------------------------------------------------------------
function AIPlayer::handleWaypointsAndChase(%this)
{
   %playPos = $daPlaya.getPosition();
   // if there are some waypoints to follow
   if(isObject(BotMarkers))
   {
      %num = getNumberBotMarkers();
      if(%this.countMyMoves $= "")
      {
      echo(%num @ " BotMarkers found!");
      }
      // 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
      {
         %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?
            if ( (%this.player.hasInventory("Crossbow") && %this.player.hasInventory("CrossbowAmmo"))
                  || (%this.player.hasInventory("Rifle") && %this.player.hasInventory("RifleAmmo")) )
            {
               // shoooooot
               %this.setTrigger(0,true);
            }
            else
            {
               error(%this.player SPC "has no weapon/ammo!");
            }
         }
         else
         {
            %this.clearAim();
            // juuuump
            //%this.setTrigger(2,true);
         }
         %this.setMoveDestination(%playPos);
         %this.move();
      }
   }
   // else: No waypoints, so chase the player!
   else
   {
      %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??
         if ( (%this.player.hasInventory("Crossbow") && %this.player.hasInventory("CrossbowAmmo"))
               || (%this.player.hasInventory("Rifle") && %this.player.hasInventory("RifleAmmo")) )
         {
            // shoooooot
            %this.setTrigger(0,true);
         }
         else
         {
            error(%this.player SPC "has no weapon/ammo!");
         }
      }
      else
      {
         %this.clearAim();
      }
      %this.setMoveDestination(%playPos);
      %this.move();
   }
}

function aiPlayer::isObjectInView(%this, %object)
{
   %player = %this.player;
   %this.sightRange = 50.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 > 1.5)
      {
      	%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;
}

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;
}
(in aiPlayer.cs)
I still call it from
function AIPlayer::onReachDestination(%this) {
    echo( "onReachDestination" );
    // the first time the bot reaches any destination, he is programmed to
    // follow the waypoint path, if any, and hunt the player after finishing it:
    aiPlayer::handleWaypointsAndChase(%this);
}
I'm sure it could be done more elegantly, efficiently and exact, but at least it's enough to get nailed by the bots... ;-)

Furhtermore, I've added one little function in inventory.cs:
function ShapeBase::hasInventory(%this, %data)
{
    return %this.inv[%data];
}
to check for weapons/ammo of the bots...

And, as mentioned earlier, I've set the global variable "$daPlaya" in commands.cs (see above post)...
should be enough to get it working - though a very basic, rude and pure "hunting" is possible with this, it's time to dig a bit more into the engine code to work on some "real", but still very basic AI stuff now(scanning for health packs or weapons, choosing appropriate waypoints to get to desired locations, avoiding at least static obstacles, flee from player if out of ammo, etc. ) ...
Until then: Happy hunting, my little bots ... ;)
#10
03/17/2002 (9:13 am)
By the way, is there a way to cycle through all the inventory (without giving a "name" to search for, e.g. "Crossbow") and to see if the player has ANY weapon?
You get the invetory "class" with this:
if(%this.inv[%i].className $= "Weapon")
but I couldn't find any way to cycle/iterate through all the inventory like this:
for(%i=0;%this.inv[%i]!$="";%i++)
   {
      if(%this.inv[%i].className $= "Weapon")
      {
         echo("It's a weapon!");
      }
   }
This doesn't work because the array index is the weapon/datablock name string, not a number...
Do the TGE script arrays have a "size" field?
How could I iterate through this array without giving a name to search for?
#11
03/17/2002 (10:01 pm)
This is really interesting, but I was just wondering what your code for clearAim() was?
#12
03/17/2002 (10:08 pm)
Yes, sorry, forgot about it...
the function itself is already in "aiPlayer.cc", it simply sets the point the AI is aiming at 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();
}
#13
03/17/2002 (10:18 pm)
Also, I can get my bots to have a rifle, but for some reason, I can't seem to give them any ammo.

This is the code I use in game.cs:

function GameConnection::createPlayer(%this)
{
...
%weapon = new Item() {
dataBlock = Rifle;
};

%ammo = new Item() {
dataBlock = RifleAmmo;
};

MissionCleanup.add(%weapon);
MissionCleanup.add(%ammo);
%player.pickup(%weapon, 1);
%player.pickup(%ammo, 1);
...
}

Thanks,
D
#14
03/17/2002 (10:43 pm)
That's strange, it works for me...!
I've just pasted the posted scriptblock at the end of my "GameConnection::createPlayer" function and
now the bots start with a rifle and some ammo and shoot at me ...
#15
03/17/2002 (10:51 pm)
Well, it might have something to do with the inventory system I'm using. Well, that's for another day. I'll look into it later.

Thanks
#16
03/20/2002 (6:47 am)
I'm just wondering if you've expanded this to take multiple players into account? Like in a multiplayer game?
#17
03/20/2002 (8:35 am)
Nope, not really... I never thought about it, though...
I'm working on a single player game (mainly) and I don't really need the bots at all... I'm just trying to get an overview of the engine and the scripting and I try to get some ideas on how to get some AI stuff done, e.g. pathfinding ... so I just played around with those bots for fun... :-)
Anybody else got bots in a MP situation?
#18
03/20/2002 (2:10 pm)
Okay, well I actually got it to work in a multiplayer scenario. Basically I overhauled game.cs and made a few changes in aiPlayer.cs (from the above posts). If anybody really wants to find out how I did it, I'll post it here. Oh, and my bots are cannibals, they don't shoot you, they just run up to you and if they are within 2 meters of you when they hit the onReachDestination fuction, they will hurt you.
#19
03/20/2002 (2:32 pm)
Yeah, why not post it... I would be interested in how you've done it!
#20
03/20/2002 (3:27 pm)
Okay, here goes:

In aiPlayer.cs:

function AIPlayer::onReachDestination(%this) {
   ...
   aiPlayer::senseAndHurtPlayer(%this);
   aiPlayer::aiChase(%this);
   ...
}
function AIPlayer::senseAndHurtPlayer(%this) {
   if($humanInGame == 1) {
       %index = aiPlayer::getClosestHuman(%this);
       %getMe = $human[%index].player;
       %playPos = %getMe.getPosition();
       %botPos = %this.getLocation();
       %dist = VectorDist(%playPos, %botPos);
       if (%dist <= 2) {
          //echo("Damage Me");
          alxPlay(CrunchSound);
          %getMe.applyDamage(1);
          //echo($human.getDamageLevel());
          if (%getMe.getDamageLevel() >= 99) {
             %getMe.kill();
          } // End if
       } // End if
   } // End if
}
function AIPlayer::getClosestHuman(%this) {
   %botPos = %this.getLocation();
   for(%i = 0; %i < $numCurrentHumanPlayers; %i++) {
      %playPos = $human[%i].player.getPosition();
      %tempDist = VectorDist(%playPos, %botPos);
      if(%i == 0) {
         %dist = %tempDist;
         %index = %i;
      } // End if
      else {
         if(%dist > %tempDist) {
            %dist = %tempDist;
            %index = %i;
         } // End if
      } // End else
   } // End for
   //echo(%index SPC $humansKilled);
   return %index;
}
function AIPlayer::aiChase(%this)
{
    if($humanInGame == 1) {
       %index = aiPlayer::getClosestHuman(%this);
       %getMe = $human[%index].player;
       %playPos = %getMe.getPosition();
       %x = getWord(%playPos, 0);
       %y = getWord(%playPos, 1);
       %z = getWord(%playPos, 2);

       %rand = getRandom(4);
       if(%rand = 0) %x--;
       else if(%rand = 1) %x++;
       else if(%rand = 2) %y--;
       else %y++;

       %playPos = %x SPC %y SPC %z;

       %this.setTargetObject(%getMe);

       %iSeeHim = aiPlayer::isObjectInView(%this, %getMe);
       //echo(%this.player SPC "isObjectInView:" SPC %iSeeHim);

       if(%iSeeHim) { // Move to player
          //echo(%this.player @ ": I see him!!!");
          %this.setAimLocation(%getMe.getWorldBoxCenter());
          %this.setMoveDestination(%playPos);
       } // End if
       else { // Move Randomly
          %this.clearAim();
          %dest = getRandom(512)-256 SPC getRandom(512)-256 SPC getRandom(512)-256;
          %this.setMoveDestination(%dest);
       } // End else
   } // End if
   else {
       %this.clearAim();
       %dest = getRandom(512)-256 SPC getRandom(512)-256 SPC getRandom(512)-256;
       %this.setMoveDestination(%dest);
   } // End else
   
   %this.move();
   
} // End aiChase
function aiPlayer::isObjectInView(%this, %object)
{
   %player = %this.player;
   %this.sightRange = 75.0;
   if (!(isObject(%player) && isObject(%object))) return false;

   %objPos = %object.getWorldBoxCenter();
   %eyePoint = %player.getWorldBoxCenter();
   %distance = VectorDist(%objPos, %eyePoint);

   //echo("DISTANCE:" SPC %distance);

   if (%distance <= %this.sightRange) {
      return true;
   } // End if
   return false;
}
The global variable $numCurrentHumanPlayers and the global array $human are defined in game.cs. ($human is comparable to $daPlaya in the above posts)

In game.cs:
Define these variables at the top:
$humanInGame = 0;
$justDied = 0;
$index = 0;
And then the functions:
function GameConnection::onClientEnterGame(%this)
{
   //echo("onClientEnterGame");
   %humanOrAI = %this.isAIControlled();
   //echo(%humanOrAI);
   if( %humanOrAI == 0 ) {
      $human[$numCurrentHumanPlayers] = %this;          echo("Human entered at" SPC $numCurrentHumanPlayers);
      $numCurrentHumanPlayers++; 
   } // End if
   ...
}
This next one hasn't actually been tested yet, but it *should* work.
function GameConnection::onClientLeaveGame(%this)
{
   ...      
   for(%i=0;%i<$numCurrentHumanPlayers;%i++) {
      if($human[%i] == %this) {
         echo("Found guy who left at" SPC %i);
         break;
      } // End if
   } // End for

   $numCurrentHumanPlayers--;

   for(%j=%i;%j<$numCurrentHumanPlayers;%j++) {
      $human[%j] = $human[%j+1];
   } // End for
}
function GameConnection::onDeath(%this, %sourceObject, %sourceClient, %damageType, %damLoc)
{
   $humanInGame = 0;

   for(%i=0;%i<$numCurrentHumanPlayers;%i++) {
      if($human[%i] == %this) {
         echo("Found dead guy at" SPC %i);
         $index = %i;
         break;
      } // End if
   } // End for

   ...

   // Doll out points and display an appropriate message
   if (%damageType $= "Suicide" || %sourceClient == %this) {
      %this.incScore(-1);
      messageAll('MsgClientKilled','%1 takes his own life!',%this.name);
   }
   else {
      if(%sourceClient != 0) {
         %sourceClient.incScore(1);
         messageAll('MsgClientKilled','%1 gets nailed by %2!',%this.name,%sourceClient.name);
      } // End if
      else {
         %this.incScore(-1);
         messageAll('MsgClientKilled','%1 has been devoured by wild boars!',%this.name);
      } // End else
   }
   
   $justDied = 1;
}
function GameConnection::createPlayer(%this)
{
   ...   
   %humanOrAI = %this.isAIControlled();
   
   if (%humanOrAI == 0) {

      if($justDied == 1) {
        $human[$index] = %this;
        echo("Respawn human at" SPC $index);
      } // End if
      $justDied = 0;

      $humanInGame = 1;
   }
}
It wasn't a problem getting the bots to run after the closest player and "eating" him. However, after a human player is killed, that would throw the array of human players all to hell, so you have to respawn the player that was just killed in the same array position where he was killed at. If you don't, then the bots will try to get the position of a human that is not in the game, and it throws an error. Also, that $humanInGame flag makes it so the bots won't chase the players if one of the players is lying dead on the ground. If they did, it throws the same error as above, because a new player hasn't been added yet. The addition to the onClientLeaveGame function should fix the array so that only players in the game are checked. Hopefully I'll get a chance to check that, as soon as my roommates get home...

Yeah, this is probably confusing, but it works for me. :) D
Page «Previous 1 2