Game Development Community

AI Weirdness

by Robert Fritzen · in Torque 3D Professional · 04/10/2012 (4:02 pm) · 20 replies

So, I'm having an issue with my Zombie AI. I'm using the recast resource to generate paths to the player but for some reason my zombies are acting very strangely.

Some of them don't move at all, even though I see the path in the editor and it points right at my player. Others will move in thus and such randomized directions and try to go outside of the map when their position is marked to the player as well.

Here is what I'm using, I adapted the resource for DM to hopefully obtain what I was looking for, but so far no success there.
function spawnZombie(%number, %position) {
   if(%number == 1) {
      %block = "ZombiePlayerData";
   }
   else if(%number == 6) {
      %block = "MegaZombiePlayerData";
   }
   else {
      %block = "Zombie0"@%number@"PlayerData";
   }
   
   if(%number < 1 || %number > 6) {
      %block = "ZombiePlayerData";
   }

   //create a new path for this Ai's personal use
   %path = createNewPath();

   // Create the demo player object
   %player = new AiPlayer() {
      dataBlock = %block;
      position = %position;
      path = %path;
   };
   MissionCleanup.add(%player);
   //%player.path = %path;

   %player.setTransform(%position);

   //this is an arrayObject in my gameClass
   game.zombieGroup.add(%player, %path);

   //are we stuck?
   %player.stuck = 0;

   //movement nodes
   %player.currentNode = 0;
   %player.targetNode = 0;

   //pause a moment to let behind the scene stuff work out
   %player.schedule(1000, "aiStartUp");

   return %player;
}

function AIPlayer::getNearestTarget(%this) {
   %targ = -1;
   %closest = 99999;
   for(%i = 0; %i < ClientGroup.getCount(); %i++) {
      %cl = ClientGroup.getObject(%i);
      if(isObject(%cl.player) && %cl.player.getState() !$= "dead") {
         %d = vectorDist(getWords(%this.getTransform(), 0, 2), getWords(%cl.player.getTransform(), 0, 2));
         if(%d < %closest) {
            %targ = %cl.player;
            %closest = %d;
         }
      }
   }
   return %targ;
}

function AIPlayer::aiStartUp(%this) {
   if(!isObject(%this)) {
      return;
   }

   if(%this.getState() $= "Dead") {
      return;
   }

   %path = %this.path;
   
   %this.setMoveSpeed(0.5);

   //get an initial path
   %start = %this.getPosition();

   %enemy = %this.getNearestTarget();
   //echo("Startup: ENEMY: "@%enemy);
   if(!isObject(%enemy)) {
      %end = "0 0 0";
   }
   else {
      %end = %enemy.getPosition();
   }

   //create the variables for the path
   %this.path.from = %start;//add variable
   %this.path.to = %end;//add variable

   %path.plan();
   %this.canWeHit();
   %this.followPath(%path, -1);
}

function AIPlayer::canWeHit(%this) {
   if(!isObject(%this) || %this.getState() $= "dead") {
      return;
   }
   %path = %this.path;
   %target = %this.getNearestTarget();
   if(isObject(%target)) {
      //give it one more distance than the actual datablock
      if(%this.playerLOS(%target, 3)) {
         //fire
         %this.aimAt(%target);
         Melee_Attack(%this);
      }
      %this.path.from = %this.getPosition();
      %this.path.to = %target.getPosition();
      %path.plan();

      %this.followPath(%path, -1);
   }
   %this.schedule(500, canWeHit);
}

function Player::getPosition(%this) {
   return getWords(%this.getTransform(), 0, 2);
}

function ZombiePlayerData::onMoveStuck(%this, %obj) {
       //echo( %obj @ " onMoveStuck" );

   if(%obj.stuck > 20) {
      %obj.stop();
      %obj.randomPath();
   }
   else {
      %obj.stuck++;
   }
}

function ZombiePlayerData::onEndOfPath(%this,%obj,%path) {
   %obj.aiDecide();
}

function ZombiePlayerData::onDamage(%this, %obj, %delta) {
   if(%obj.getstate() $= "Dead") {
      //take finger off trigger
      %obj.setImageTrigger(0, 0);
      //cleanup
      %path = %this.path;
      %index = game.zombieGroup.getIndexFromKey(%obj);
      if(%index != -1) {
         game.zombieGroup.erase(%index);
      }

      //delete the Ai's path
      if(isObject(%path)) {
         if(%path.getClassname() $="navPath") {
            %path.delete();
         }
      }
      //
      %src = %obj.lastDamageBy;
      echo("dead by: "@%src@" "@%src.client);
      if(%src.client !$= "") {
         commandToClient(%src.client, 'UpdateInformation', 'points', 10); //10 points for a zombie kill!
      }
      //
      game.zombieCount--;
      game.checkNextWave();
   }
}

function AIPlayer::aiDecide(%this) {
   if(!isObject(%this) || %this.getState() $= "dead") {
      return;
   }
   %path = %this.path;//temporary alias for our path
   %start = %this.getPosition();

   //find that player!
   %enemy = %this.getNearestTarget();
   if(isObject(%enemy)) {
      if(%enemy.getState() !$= "Dead") {
         %end = %enemy.getPosition();
         %path.from = %start;
         %path.to = %end;
         %path.plan();

         %this.followPath(%path, -1);//hunt!
         return;
      }
   }
   //randomPath
   %this.randomPath();
}

function AIPlayer::randomPath(%this) {
   if(!isObject(%this) || %this.getState() $= "dead") {
      return;
   }

   %this.stuck = 0;
   %this.currentNode = 0;
   %this.targetNode = 0;

   %start = %this.getPosition();

   //get a random goal, count available up
   %count = ZombieDropPoints.getCount();

   //get the max. index from the group.
   //Index starts at 0 and not 1.
   //so we need to take 1 off the count
   %max = %count - 1;

   //randomize!
   %index = getRandom(0, %max);

   //get the spawnSphere from the simgroup at the index
   %spawn = ZombieDropPoints.getObject(%index);
   %end = %spawn.getPosition();

   NavPath1.from = %start;
   NavPath1.to = %end;
   NavPath1.plan();

   %this.followPath(NavPath1, -1);
}

function AIPlayer::playerLOS(%this, %them, %dist) {
   if(!isObject(%this) || !isObject(%them)) {
      return false;
   }
   //from our eye to the player's eye
   %ai = %this.getEyePoint();
   %player = %them.getEyePoint();

   //things to get in the way
   %mask = $TypeMasks::TerrainObjectType | $TypeMasks::StaticObjectType;

   // see if anything gets hit
   %collision = containerRayCast(%ai, %player, %mask, %this);

   //nothing hit, this happens when EyeNode is outside of boundsBox
   //and thus raycast has no collision to record against
   //still means that they are viewable
   if(!%collision) {
      if(vectorDist(%ai, %player) <= %dist) {
         return true;
      }
      else {
         return false;
      }
   }

   %hit = firstWord(%collision);
   //we can see them
   if(%hit == %them) {
      if(vectorDist(%ai, %player) <= %dist) {
         return true;
      }
      else {
         return false;
      }
   }
   else {
      return false;
   }
}

//Navegation Interfacing
function createNewPath() {
   %path = new NavPath() {
      from = "0 1 1";
      to = "10 1 1";
      mesh = "Nav";
   };
   MissionCleanup.add(%path);
   return %path;
}


function ZombiePlayerData::onReachDestination(%this,%obj)
{
   //echo( %obj @ " onReachDestination" );

   // Moves to the next node on the path.
   // Override for all player.  Normally we'd override this for only
   // a specific player datablock or class of players.
   if (%obj.path !$= "")
   {
      if (%obj.currentNode == %obj.targetNode)
         %this.onEndOfPath(%obj,%obj.path);
      else
         %obj.moveToNextNode();
   }
}

function ZombiePlayerData::onTargetExitLOS(%this,%obj)
{
   //echo( %obj @ " onTargetExitLOS" );
}

function ZombiePlayerData::onTargetEnterLOS(%this,%obj)
{
   //echo( %obj @ " onTargetEnterLOS" );
}
function ZombiePlayerData::onEndSequence(%this,%obj,%slot)
{
   echo("Sequence Done!");
   %obj.stopThread(%slot);
   %obj.nextTask();
}

#1
04/11/2012 (8:48 am)
I'll have to try it myself to be certain, but maybe creating explicit overridden functions for your ZombiePlayerData would help. Sometimes TorqueScript inheritance can be finicky.
#2
04/17/2012 (3:05 pm)
I modified my AI code a little, and still nothing. Anything setting off a red flag on why they won't even budge.

I've also used -1 as the starting value in followPath with no luck either.

// Zombie Spawn Function
function spawnZombie(%number, %position) {
   if(%number == 1) {
      %block = "ZombiePlayerData";
   }
   else if(%number == 6) {
      %block = "MegaZombiePlayerData";
   }
   else {
      %block = "Zombie0"@%number@"PlayerData";
   }
   
   if(%number < 1 || %number > 6) {
      %block = "ZombiePlayerData";
   }
   %path = createNewPath();

   %player = new AiPlayer() {
      dataBlock = %block;
      position = %position;
      path = %path;
   };
   MissionCleanup.add(%player);

   %player.setTransform(%position);
   game.zombieGroup.add(%player, %path);

   %player.stuck = 0;

   %player.currentNode = 0;
   %player.targetNode = 0;

   %player.schedule(1000, "aiStartUp");

   return %player;
}

function AIPlayer::aiStartUp(%this) {
   if(!isObject(%this) || %this.getState() $= "Dead") {
      return;
   }
   // Initialize the zombie's parameters
   
   // Begin the combat routine
   %enemy = %this.getNearestPlayerTarget();
   if(%enemy == -1) {
      //%this.currentNode = 0;
      //%this.targetNode = 0;

      %this.path.to = vectorAdd(%this.getPosition(), getRandomPosition(5, 1));
      %this.path.from = %this.getPosition();
      %this.path.plan();
      %this.followPath(%this.path, 0);
   }
   else {
      //%this.currentNode = 0;
      //%this.targetNode = 0;
   
      %this.path.to = %enemy.getPosition();
      %this.path.from = %this.getPosition();
      %this.path.plan();
      %this.followPath(%this.path, 0);
   }
   
   %this.zombieHitDetect();
}

function AIPlayer::zombieHitDetect(%this) {
   if(!isObject(%this) || %this.getState() $= "Dead") {
      return;
   }
   //push the updated position
   %enemy = %this.getNearestPlayerTarget();
   
   echo("Zombie Hit Detect: "@%this@" -> "@%enemy@"");
   if(%enemy == -1) {
      //%this.currentNode = 0;
      //%this.targetNode = 0;

      %this.path.to = vectorAdd(%this.getPosition(), getRandomPosition(5, 1));
      %this.path.from = %this.getPosition();
      %this.path.plan();
      %this.followPath(%this.path, 0);
   }
   else {
      //%this.currentNode = 0;
      //%this.targetNode = 0;
   
      %this.path.to = %enemy.getPosition();
      %this.path.from = %this.getPosition();
      %this.path.plan();
      %this.followPath(%this.path, 0);
      // check for 3m distance
      if(%this.getTargetDistance(%enemy) <= 3) {
         //check for visual hit
         if(%this.playerLOS(%enemy)) {
            Melee_Attack(%this);
         }
      }
   }
   %this.schedule(500, zombieHitDetect);
}

function ZombiePlayerData::onReachDestination(%this,%obj) {
   if (%obj.path !$= "") {
      if (%obj.currentNode == %obj.targetNode)
         %this.onEndOfPath(%obj,%obj.path);
      else
         %obj.moveToNextNode();
   }
}

function ZombiePlayerData::onMoveStuck(%this,%obj) {
   //echo( %obj @ " onMoveStuck" );
   %obj.stuck++;
   if(%obj.stuck >= 20) {
      %obj.stop();
   
      %obj.stuck = 0;
      %obj.currentNode = 0;
      %obj.targetNode = 0;
      //plan a randomized path
      %obj.path.to = vectorAdd(%obj.getPosition(), getRandomPosition(5, 1));
      %obj.path.from = %obj.getPosition();
      %obj.path.plan();
      %obj.followPath(%obj.path, 0);
   }
}

function ZombiePlayerData::onTargetExitLOS(%this,%obj) {
   //echo( %obj @ " onTargetExitLOS" );
}

function ZombiePlayerData::onTargetEnterLOS(%this,%obj) {
   //echo( %obj @ " onTargetEnterLOS" );
}

function ZombiePlayerData::onEndOfPath(%this,%obj,%path) {
   %obj.nextTask();
}

function ZombiePlayerData::onEndSequence(%this,%obj,%slot) {
   //echo("Sequence Done!");
   %obj.stopThread(%slot);
   %obj.nextTask();
}

function ZombiePlayerData::onDamage(%this,%obj, %delta) {
   if(%obj.getstate() $="Dead") {
      Game.zombieCount--;
      Game.checkNextWave();
      
      commandToClient(%obj.lastDamagedBy.client, 'UpdateInformation', "Points", 10);
   }
}
#3
04/27/2012 (10:41 am)
Good news, I got them to move.

Bad news... Only a few of the zombies actually follow the player, while the remainder of them march off to "0 0 0". I'll check for anything that may look like a set off.

On the other hand, do you know how to address the zombie moonwalk issue by chance?
#4
04/27/2012 (11:44 am)
Try looking up setSequenceGroundSpeed() in tsShapeConstruct.cpp - it looks like you can set the ground transform scaling with this that is normally done using the older dts export method. If my guess is correct, this value replaces the ground transform that would have been generated by moving the character forward during the walk animation (instead of animating the walk in place as is more standard).
#5
04/27/2012 (12:04 pm)
Hmmmm I did Have the same problem of the zombies not moving and I believe it was an issue with the node number in setmovedestination where if I made it another number it didn't move so I set it to 1024 and they go for my player.

Also instead of using a whole bunch of data blocks for the zombies
take this one from your code

function ZombiePlayerData::onTargetEnterLOS(%this,%obj) {  
   //echo( %obj @ " onTargetEnterLOS" );  
}

you can do this as well which will effect all AI players

function Armor::onTargetEnterLOS(%this,%obj) {  
   //echo( %obj @ " onTargetEnterLOS" );  
}

Also think if I remember you needed to change %this.path.plan(); to %path.plan(); because it was acting weird when I did it your way.
#6
04/27/2012 (12:12 pm)
I'll give those a try and report back.
#7
04/27/2012 (2:00 pm)
Ok, I made some modifications, but unfortunately no luck. The modification of the path statements and the block adjustments actually put me back at square one, with no zombie movement.

Also, I read the scripts for pack resource, and I'm noticing they're using the .dts model file. Where are they obtaining this from? I only have the .DAE format. And the cached .dts files are only for the DAE load (I tried loading those up and got a non-moving static zombie).

EDIT:
Interesting discovery.. When I move my player to an unreachable area (not inside the mesh) the zombies magically detect me and all point to me and move to the furthest position they can reach, once I move back inside, they only look at me, but don't move... The path points right to my player, but they don't follow it.
#8
04/27/2012 (8:21 pm)
Seems to me its something with the pathing because it seems like its bugged. I'm at the same point you are and what it seems like is they are not going to 0,0,0 they are actually going to the path object its self or what ever its called not the box and line thing to show the bots movement but the actual NavPath object its self when it get close to it. I haven't tested where the zombies are not on the navmesh but to be honest It would turn out to be just like setmovedestination where It would have no idea where other trees,hills,building are because it doesn't have a guide to follow when pathing.

Its very strange if you want I can work on it with you can add my email at adammgardner@hotmail.com, Also the cached files are for when the model loads the first time that way it doesn't have to load the full model every time you start the game its just to speed up the game a bit.

Also on the models the models can be dts or DAE shouldn't really matter just depending on what the modeler wants to convert to.

I also noticed if the zombie runs to the "0,0,0" as said above that it has no navmesh guidance so it just runs into the wall.

If you have any more questions just ask.
#9
04/28/2012 (6:29 am)
You're using the followPath function from the Full template AI scripts, right? That should work with NavPaths. If a NavPath fails to plan, its location gets set to (0, 0, 0) and any followPath calls on it will send the character there. Seems odd that the path following works when you're outside the mesh, though.

My advice at this point would be to use some good old-fashioned print debugging (or using the trace() function). Start with a single zombie so you don't get messages from multiple characters, and just figure out all the function calls that are going on, and what's not correct. Probably a good idea to start with the followPath function if you're sure the NavPath is being created correctly.
#10
04/28/2012 (9:43 am)
My point is that with the old dts exporter a ground transform speed would be taken from the animation automatically if you moved the character in its animation cycle - see the Bravetree Girl Pack for an example. I don't know if this works with DAE files at all via the COLLADA importer, but I do know that our recent art packs were all animated using a more standard animation method where the model does not move from its home position during the animation loop.

As for the other, I'm with Daniel. It sounds like something got missed somewhere.
#11
04/28/2012 (3:33 pm)
But Daniel the problem when the AI goes to 0,0,0 is that the path isn't updated when it tries to move there so its like it just stop following the path unless the planning part doesn't get planned then I'm not sure why it wouldn't since its called right after you set the TO and FROM all the time.


Edit:

Did some test and I think the reason it goes to 0,0,0 is because it cant reach your location and stay on the nav mesh at the same time so the path can't be updated and it is forced to 0,0,0.

Will do more test later!
#12
04/30/2012 (12:28 pm)
Here's the current trace output, nothing's jumping off the page to me.

Entering SurvivalGame::spawnAZombie(4747)
   Entering pickPlayerSpawnPoint(ZombieDropPoints)
   Leaving pickPlayerSpawnPoint() - return 4730
   Entering spawnZombie(3, 52.4056 220.68 -56.2191 1 0 0 0)
      Entering armor::onAdd(309, 4837)
      Leaving armor::onAdd() - return 
      Entering AIPlayer::doStand(4837)
          AiStand
      Leaving AIPlayer::doStand() - return 
      Entering AIPlayer::createNewPath(4837)
      Leaving AIPlayer::createNewPath() - return 4838
   Leaving spawnZombie() - return 4837
   Entering GameCore::pickPointInSpawnSphere(4837, 4730)
   Leaving GameCore::pickPointInSpawnSphere() - return 53.2295 221.664 -56.2191 1 0 0 0
Leaving SurvivalGame::spawnAZombie() - return 
Entering AIPlayer::aiStartUp(4837)
   Entering AIPlayer::getNearestPlayerTarget(4837)
   Leaving AIPlayer::getNearestPlayerTarget() - return 4822
   Entering AIPlayer::followPath(4837, 4838, 1024)
      Entering AIPlayer::moveToNode(4837, 0)
      Leaving AIPlayer::moveToNode() - return 
   Leaving AIPlayer::followPath() - return 
   Entering AIPlayer::zombieHitDetect(4837)
      Entering AIPlayer::getNearestPlayerTarget(4837)
      Leaving AIPlayer::getNearestPlayerTarget() - return 4822
      Entering AIPlayer::followPath(4837, 4838, 1024)
         Entering AIPlayer::moveToNode(4837, 0)
         Leaving AIPlayer::moveToNode() - return 
      Leaving AIPlayer::followPath() - return 
      Entering AIPlayer::getTargetDistance(4837, 4822)
      Leaving AIPlayer::getTargetDistance() - return 156.891
   Leaving AIPlayer::zombieHitDetect() - return 
Leaving AIPlayer::aiStartUp() - return

EDIT:

Any way to get rid of this in the trace output? it makes it hard to spot things as it is spammed rediculously.
Entering GammaPostFX::preProcess(1443)
Leaving GammaPostFX::preProcess() - return 
Entering GammaPostFX::setShaderConsts(1443)
Leaving GammaPostFX::setShaderConsts() - return
#13
05/01/2012 (7:25 pm)
And this just got even more interesting... I decided to write a centralized move control function to make this debugging a little more smooth:

function AIPlayer::pathUpdate(%this, %tObj, %mustGo) {
   if(!isObject(%this) || %this.getState() $= "dead") {
      return;
   }
   %path = %this.path;
   %myP = %this.getPosition();
   
   %path.from = %myP;
   if(%tObj $= "rand" || !isObject(%tObj) || %tObj.getState() $= "dead") {
      //path out a random position
      %path.to = vectorAdd(%this.getPosition(), getRandomPosition(5, 1));
   }
   else {
      %path.to = %tObj.getPosition();
      
      echo("ZDBG: "@%this@"->"@%tObj@" | "@vectorDist(%this.getPosition(), %tObj.getPosition())@" ===> "@%path.plan());
      
      if(!%path.plan()) {
         if(%mustGo) {
            %this.fails++;
            if(%this.fails >= 10) {
               error("ERROR: AIPathUpdate Failed 10 times "@%this@" -> "@%tObj@"");
               %this.fails = 0;
               %rCode = 0;
            }
            else {
               %this.schedule(50, pathUpdate, %tObj, %mustGo);
            }
         }
         else {
            //not a required thing, move elsewhere, return 2 to indicate failed
            %path.to = vectorAdd(%this.getPosition(), getRandomPosition(5, 1));
            %rCode = 2;
         }
      }
      else {
         %this.fails = 0;   //reset failed count
         %rCode = 1;
      }
   }
   //follow the path
   %this.currentNode = 0;
   %this.followPath(%path, 1024);
   return %rCode;
}

See that echo statement? Well %path.plan() is ALWAYS returning "1", which means that the followPath function is to blame... and I have no idea what is going on with that :X
#14
05/02/2012 (3:51 pm)
Where is the followPath() method that you are using defined? The stock AIPlayer::followPath() from scripts/server/aiPlayer.cs doesn't return anything....
#15
05/02/2012 (5:41 pm)
Here it is:

function AIPlayer::followPath(%this,%path,%node)
{
   // Start the player following a path
   %this.stopThread(0);
   if (!isObject(%path))
   {
      %this.path = "";
      return;
   }

   if (%node > %path.getCount() - 1)
      %this.targetNode = %path.getCount() - 1;
   else
      %this.targetNode = %node;

   if (%this.path $= %path)
      %this.moveToNode(%this.currentNode);
   else
   {
      %this.path = %path;
      %this.moveToNode(0);
   }
}

I haven't touched the stock AIPlayer code, does this look correct?
#16
05/03/2012 (2:01 pm)
That looks like it. From the looks of it, the script bails with a void return if the path doesn't exist, or drops out the end with a void return if successful. Perhaps we could have it return something useful, say false, if there is no path or return true if it succeeds. Then we can get a little feedback at least....
#17
05/03/2012 (2:20 pm)
I'll give this a shot later... having to iron out some fun Access Violations on one of my engine additions :x

#18
05/04/2012 (8:01 am)
Here's the current trace output (with return statement additions):

Entering SurvivalGame::spawnAZombie(4752)
   Entering pickPlayerSpawnPoint(ZombieDropPoints)
   Leaving pickPlayerSpawnPoint() - return 4728
   Entering spawnZombie(4, 190.992 59.2441 -56.541 1 0 0 0)
      Entering armor::onAdd(310, 4843)
      Leaving armor::onAdd() - return 
      Entering AIPlayer::doStand(4843)
          AiStand
      Leaving AIPlayer::doStand() - return 
      Entering AIPlayer::createNewPath(4843)
      Leaving AIPlayer::createNewPath() - return 4844
   Leaving spawnZombie() - return 4843
   Entering GameCore::pickPointInSpawnSphere(4843, 4728)
   Leaving GameCore::pickPointInSpawnSphere() - return 191.232 55.9135 -56.541 1 0 0 0
Leaving SurvivalGame::spawnAZombie() - return 
Entering AIPlayer::aiStartUp(4843)
   Entering AIPlayer::getNearestPlayerTarget(4843)
   Leaving AIPlayer::getNearestPlayerTarget() - return 4827
   Entering AIPlayer::pathUpdate(4843, 4827, 1)
      ZDBG: 4843->4827 | 115.42 ===> 1
      Entering AIPlayer::followPath(4843, 4844, 1024)
         Entering AIPlayer::moveToNode(4843, 0)
         Leaving AIPlayer::moveToNode() - return 
      Leaving AIPlayer::followPath() - return 1
   Leaving AIPlayer::pathUpdate() - return 1
   Entering AIPlayer::zombieHitDetect(4843)
      Entering AIPlayer::getNearestPlayerTarget(4843)
      Leaving AIPlayer::getNearestPlayerTarget() - return 4827
      Entering AIPlayer::pathUpdate(4843, 4827, 1)
         ZDBG: 4843->4827 | 115.42 ===> 1
         Entering AIPlayer::followPath(4843, 4844, 1024)
            Entering AIPlayer::moveToNode(4843, 0)
            Leaving AIPlayer::moveToNode() - return 
         Leaving AIPlayer::followPath() - return 1
      Leaving AIPlayer::pathUpdate() - return 1
      Entering AIPlayer::getTargetDistance(4843, 4827)
      Leaving AIPlayer::getTargetDistance() - return 115.424
   Leaving AIPlayer::zombieHitDetect() - return 
Leaving AIPlayer::aiStartUp() - return

So follow path returns true.... this makes things even more interesting...

function AIPlayer::followPath(%this,%path,%node) {
   // Start the player following a path
   %this.stopThread(0);
   if (!isObject(%path)) {
      %this.path = "";
      return false;
   }

   if (%node > %path.getCount() - 1)
      %this.targetNode = %path.getCount() - 1;
   else
      %this.targetNode = %node;

   if (%this.path $= %path)
      %this.moveToNode(%this.currentNode);
   else {
      %this.path = %path;
      %this.moveToNode(0);
   }
   
   return true;
}
#19
05/07/2012 (8:57 am)
Aha! Now I'm onto it... I added a few more debug calls to see exactly what was going on, and I think I may have discovered the little culprit.

Call moveToNode 5046(0): -> 
130.369 204.287 -56.975 1 0 0 0
130.369 204.287 -56.975 1 0 0 0/0^3
ZDBG: 5046->5029 | 136.154 ===> 1
Call moveToNode 5046(0): -> 
130.369 204.287 -56.975 1 0 0 0
130.369 204.287 -56.975 1 0 0 0/0^3
ZDBG: 5046->5029 | 136.154 ===> 1
Call moveToNode 5046(0): -> 
130.369 204.287 -56.975 1 0 0 0
130.369 204.287 -56.975 1 0 0 0/0^3

Notice that the two transform's match? well this is my debug call:
function AIPlayer::moveToNode(%this, %index) {
   // Move to the given path node index
   %this.currentNode = %index;
   %node = %this.path.getObject(%index);
   %this.setMoveDestination(%node.getTransform(), %index == %this.targetNode);
   
   echo("Call moveToNode "@%this@"("@%index@"): -> \n"@%this.getTransform()@"\n"@%node.getTransform()@"/"@%index TAB %this.targetNode@"");
}

The zombie is ALWAYS on node 0's position, and it is not being properly updated to follow to the next node.

I'm not 100% sure how to fix this though, any suggestions?
#20
05/08/2012 (2:49 pm)
Okay, that's interesting. So is something like onReachDestination being called to move the character to the next node?

I'm fairly sure you're not running into any internal problems with the recast resource, but if you are I'll have to have a better look. The script interface is actually a bit of a hack - NavPaths aren't really designed to work the same way as SimPaths, in that they're not groups of objects, just a single object.

When you call getObject(X) on a NavPath, the path sets an internal value saying 'user wants to know about node X'. It returns its own ID number, so when you call getTransform(), you're actually calling it on the NavPath. The NavPath returns the transform of the node the user selected, so it all works out - unless you try to do things with %node outside this function, like delete it, or store it for later use.