Friday, 29 April 2011

The following is the code that will let you access rexbot code from the botAPI

I've been scratching my head trying to figure out how to access the RexBot code from inworld.

Obviously when things are stable and the game AI is built in to the RexBot code it should run smoothly, but there will be need for hooks into the bot itself from inworld so that I can test it. That leads to the need for me to be able to access the methods and properties of the RexBot code from within the botAPI. Basically they exhibit two different interfaces. Rexbot is IClientAPI and botAPI doesn't have IClientAPI.

Luckily however, we can get access to the Rexbot code through the botmanager code. What I had to do was add a function to the botmanager code which pulled out the appropriate rexbot from the internal private m_bots collection of bots. From there I can get access to the public members.
Which is exactly what I need to do in order to be able to easily test. So *now* I believe I'm well on the way to being able to make the changes I want to make.

public void botSetState(string bot, string State)
{
//this is a test case of being able to get access to a property
//of the actual rexbot itself through the botAPI
IBotManager manager = World.RequestModuleInterface();
if (manager != null)
{
RexBot rxbot;
rxbot = (RexBot)manager.GetBot(UUID.Parse(bot));
//follow up with this
switch (State.ToLower())
{
case "walking":
rxbot.State = RexBot.RexBotState.Walking;
break;
case "idle":
rxbot.State = RexBot.RexBotState.Idle;
break;
case "flying":
rxbot.State = RexBot.RexBotState.Flying;
break;

default:
rxbot.State = RexBot.RexBotState.Unknown;
break;
}
}

}

Thursday, 28 April 2011

Code to animate the avatar

Here's a rebased version of Christy Lock's code which will animate the bot:

Add the following code to the Bot_API.cs in the Botmanager module:

public void botAnimate(string bot, string AnimationUUID)
{
m_host.ParentEntity.Scene.ForEachScenePresence(delegate(IScenePresence sp)
{
// this should be the bot id
if (sp.UUID == UUID.Parse(bot))
{
sp.Animator.AddAnimation(UUID.Parse(AnimationUUID), UUID.Zero);
}
});

}

Code to determine the distance between NPC and human's avatar

One of the code snippets I need to do is determine the distance between the NPC and the human avatar in order to determine if I should be running the animation code or not. There's already existing code by Christy Lock to do that for her AStar pathfinding class. This is a rebased version of it which should do the trick hopefully:

Vector3 diffAbsPos = FollowedSP.AbsolutePosition - m_botscenePresence.AbsolutePosition;
if (Math.Abs (diffAbsPos.X) < m_closeToPoint || Math.Abs (diffAbsPos.Y) < m_closeToPoint)
{
// run the attack code here which includes animation and reducing the health points of
// the attacked human, possibly pushing et cetera depending on the force applied
}

So what this does is get a vector between the scenepresence of the followed avatar (i.e. the human) and the scenepresence of the bot. Then it does some math which amounts to checking if either of the X or Y coordinates of the vector are less than the closetopoint (which is 1 i.e. one unit). Depending on the animation to be run, this could vary but 1 unit is good for now.

On decision trees and goal oriented planning

I like to do a lot of research before I actually start coding so I read up on the latest in AI gaming theory and it turns out that the most lifelike NPCs utilize something called goal oriented planning. I kind of know what this is: it's a sequence of steps to get to a goal i.e. a decision tree.

Where it differs from a decision tree is that a decision tree is typically a hard coded best-estimate of the route to get from where you are now to a planned goal (whether it's a physical location or a state or an action or whatever).

But if we switch it up a little bit and allow competing sub-goals to compete for the fastest path to the goal and update the subgoals-in-running and various choices of paths to the goal then we have something more interesting and this is in fact the goal oriented planning situation I'm reading about.

What's interesting about this is that it means effectively that pathfinding and goal oriented planning are exactly the same thing. In other words A Star (or A*). So I need to dig into A* a little more.

Interestingly, Christy Lock has been bending my ear about A* for pathfinding already and she has already coded some A* into the code, so maybe it could be switched up a little to handle pathfinding to a goal.

But that's for later. For now I'm going to stick to a simplified attack decision tree for the first step.

Any case, here is pseudo code (from wikipedia) for the A* algorithm in case anyone is interested.

function A*(start,goal)
closedset := the empty set // The set of nodes already evaluated.
openset := set containing the initial node // The set of tentative nodes to be evaluated.
came_from := the empty map // The map of navigated nodes.

g_score[start] := 0 // Cost from start along best known path.
h_score[start] := heuristic_cost_estimate(start, goal)
f_score[start] := h_score[start] // Estimated total cost from start to goal through y.

while openset is not empty
x := the node in openset having the lowest f_score[] value
if x = goal
return reconstruct_path(came_from, came_from[goal])

remove x from openset
add x to closedset
foreach y in neighbor_nodes(x)
if y in closedset
continue
tentative_g_score := g_score[x] + dist_between(x,y)

if y not in openset
add y to openset
tentative_is_better := true
else if tentative_g_score < g_score[y]
tentative_is_better := true
else
tentative_is_better := false

if tentative_is_better = true
came_from[y] := x
g_score[y] := tentative_g_score
h_score[y] := heuristic_cost_estimate(y, goal)
f_score[y] := g_score[y] + h_score[y]

return failure


function reconstruct_path(came_from, current_node)
if came_from[current_node] is set
p = reconstruct_path(came_from, came_from[current_node])
return (p + current_node)
else
return current_node

K so Christy solved the animation problem

The Awesome Christy Lock solved the animation thing even while I was writing the last post. I'll post the code later on tonight for how to do it.

So for me the next thing is to implement a simple spawn/locate human/move closer/attack/defend/withdraw decision tree.

The code to get the position of the nearest avatar could be in the chat code because I'm vaguely sure I saw a function that returns the avatar's location who chatted the message.

If that code is generic, it should be useable to determine the distance to the avatar so the decision tree should look like this:

Spawn!
Is human near enough to me that I can start the attack animation?
Nope
Move closer
Is human near enough to me that I can start the attack animation?
Yup
Start the attack animation
etc

Alternatively I could just do this:
Spawn!
Is human near enough to me that I can start the attack animation?
Nope
Run the follow avatar code
Is human near enough to me that I can start the attack animation?
Yup
Start the attack animation
etc

Then after that it would hook up to some kind of code that determined the human's hit points and damage he/she was taking etc or fed it directly into the health monitor you see in damage enabled areas when using imprudence.

Animations: k so that's interesting

So that's interesting.

I've dug through the code of osAvatarPlayAnimation and it ultimately leads back to SendAnimation which is a virtual member of the IClientAPI interface which is an interface of RexBot.cs and the old GenericNPCCharacter.cs.

But there's nothing in it. i.e. no implementation.

But yet I know the the osAvatarPlayAnimation works because I've already called it from a script inworld on an Aurora Sim.

So I guess I have to try it again but leave a breakpoint in the code to see where it goes. Maybe then I can cut and paste the relevant sections of the code into the bot so we don't need to set the threat level to high and call it inworld, but instead have it run on the server code.

Coding up animations

One of the things that it will be useful for an NPC to do is run animations.

Consider:

A human player walks down a street past a bot-spawning prim. A script in this prim detects the human player and boots up the decision tree which cascades through the criteria until the decision is taken to spawn an NPC in an angry hostile state with the goal of attacking the human interloper.

So the bot spawns, moves forward into range and then needs to attack. One of the things it should do when attacking (in order to make the experience enjoyable for the human player) is play a reasonable animation which is relevant to the attack.

So... it's necessary to code in some kind of animations to the bot. One of the things we'll need to do is make sure that it's only bots that can be animated or else the potential for griefing would be astronomical. We obviously don't want the ability to script any other player to perform a random animation without their permission, so we need a permissions check built in to make sure that it is in fact a bot and not a human player. That's easy enough to do though.

So the next piece is the actual animation code. Currently that's sitting in the OSSL_API.cs, which I think is not the right place for the code for bots because to run it you need the threat level to be high. Animating a bot is not a high threat level even though animating another human character against their will might be. Any case, I'm going to see if I can't hack up something from the code and put it into the bot code. The following are the two relevant functions, startanimation and stopanimation:

public void osAvatarPlayAnimation(string avatar, string animation)
{
ScriptProtection.CheckThreatLevel(ThreatLevel.VeryHigh, "osAvatarPlayAnimation", m_host, "OSSL");

UUID avatarID = (UUID)avatar;


if (World.Entities.ContainsKey((UUID)avatar) && World.Entities[avatarID] is ScenePresence)
{
ScenePresence target = (ScenePresence)World.Entities[avatarID];
if (target != null)
{
UUID animID=UUID.Zero;
lock (m_host.TaskInventory)
{
foreach (KeyValuePair inv in m_host.TaskInventory)
{
if (inv.Value.Name == animation)
{
if (inv.Value.Type == (int)AssetType.Animation)
animID = inv.Value.AssetID;
continue;
}
}
}
if (animID == UUID.Zero)
target.Animator.AddAnimation(animation, m_host.UUID);
else
target.Animator.AddAnimation(animID, m_host.UUID);
}
}
}

public void osAvatarStopAnimation(string avatar, string animation)
{
ScriptProtection.CheckThreatLevel(ThreatLevel.VeryHigh, "osAvatarStopAnimation", m_host, "OSSL");

UUID avatarID = (UUID)avatar;


if (World.Entities.ContainsKey(avatarID) && World.Entities[avatarID] is ScenePresence)
{
ScenePresence target = (ScenePresence)World.Entities[avatarID];
if (target != null)
{
UUID animID=UUID.Zero;
lock (m_host.TaskInventory)
{
foreach (KeyValuePair inv in m_host.TaskInventory)
{
if (inv.Value.Name == animation)
{
if (inv.Value.Type == (int)AssetType.Animation)
animID = inv.Value.AssetID;
continue;
}
}
}

if (animID == UUID.Zero)
target.Animator.RemoveAnimation(animation);
else
target.Animator.RemoveAnimation(animID);
}
}
}