Voodoo Bot

“Simple BOT Tutorial emulates a client :)”


Posted by QBS_Quadrant (217.32.146.*) at 7:55 AM, 3/31/2001:
In reply to: Looking for a good bot tutorial posted by Neo_Phenox (170.215.227.*) at 7:42 AM, 3/31/2001

Lesson 1:

Since this is the first lesson, we will start with something simple. What is simpler than spawning a bot that does nothing in the game? So this is the focus of the lesson. It is actually not difficult to create an entity in Quake II, as you will see. The bot we will be creating is called VoodooBot, for the skin that we will be initially using. The design goal for VoodooBot is a complete client emulation. This means that VoodooBot will appear to the Quake II server as an ordinary real client controlled by a human.

The conventions used for code snippets:
Line beginning with [=] is original code.
Line beginning with [-] is deleted code.
Line beginning with [*] is changed code.
Line beginning with [+] is new code.

Before we tackle the problem of creating an entity, we need to set up the files for easy updating with ours.

Open up g_local.h:
[=] // g_local.h -- local definitions for game module
[+] #ifndef __G_LOCAL_H__
[+] #define __G_LOCAL_H__

[=] #include "q_shared.h"

...
...
...

(Near bottom of the file)

[=] moveinfo_t moveinfo;
[=] monsterinfo_t monsterinfo;
[=] };

[+] #include "tutorial.h"
[+] #endif

Create a file called tutorial.h:
[+] #ifndef __TUTORIAL_H__
[+] #define __TUTORIAL_H__

[+] #include "g_local.h"

[+] void SelectSpawnPoint(edict_t *ent, vec3_t origin, vec3_t angles);
[+] void ClientUserinfoChanged (edict_t *ent, char *userinfo);
[+] qboolean ClientConnect (edict_t *ent, char *userinfo);

[+] void Svcmd_Bot_f(edict_t *ent);
[+] void Bot_Create(void);
[+] void Bot_Spawn(edict_t *ent);

[+] #endif

All server commands from the console start with "sv". I have decided to use "bot" as our bot spawning command for this tutorial. So, we want to be able to type "sv bot spawn" in the console to bring a bot into the game. Here is how:

Open up g_svcmds.c, find the function ServerCommand(...):

[=] cmd = gi.argv(1);

[+] if (Q_stricmp(cmd, "bot") == 0)
[+] Svcmd_Bot_f(ent);
[+] else

[=] if (Q_stricmp (cmd, "test") == 0)
[=] Svcmd_Test_f ();
[=] else
[=] gi.cprintf (NULL, PRINT_HIGH, "Unknown server command \"%s\"\n", cmd);

Got it? It is that simple to add a new server command to Quake II. Remember that.
Next, we will provide the definition of Svcmd_Bot_f(...):

Create a file called tutorial.c:

[+] #include "tutorial.h"

[+] void Svcmd_Bot_f(edict_t *ent)
[+] {
[+] char *arg = gi.argv(2);

[+] if (Q_stricmp(arg, "spawn") == 0)
[+] Bot_Create();
[+] }

As you can see, Svcmd_Bot_f(...) allows us to process the console command line further. This setup will prove to be very useful in the future.

So now we call our Bot_Create(...) function. Here is what it looks like:

Open tutorial.c:

[+] void Bot_Create(void)
[+] {
[+] int i;
[+] char userinfo[MAX_INFO_STRING];
[+] edict_t *bot;

[+] for (i = maxclients->value; i > 0; i--)
[+] {
[+] bot = g_edicts + i + 1;
[+] if (!bot->inuse)
[+] break;
[+] }

[+] if (bot->inuse)
[+] bot = NULL;

[+] if (bot)
[+] {
[+] memset(userinfo, 0, MAX_INFO_STRING);

[+] Info_SetValueForKey(userinfo, "name", "VoodooBot");
[+] Info_SetValueForKey(userinfo, "skin", "female/voodoo");
[+] Info_SetValueForKey(userinfo, "hand", "2");

[+] ClientConnect(bot, userinfo);
[+] G_InitEdict(bot);
[+] InitClientResp(bot->client);

[+] Bot_Spawn(bot);

[+] gi.WriteByte(svc_muzzleflash);
[+] gi.WriteShort(bot - g_edicts);
[+] gi.WriteByte(MZ_LOGIN);
[+] gi.multicast(bot->s.origin, MULTICAST_PVS);

[+] gi.bprintf(PRINT_HIGH, "%s entered the game\n", bot->client->pers.netname);
[+] ClientEndServerFrame(bot);
[+] }
[+] else
[+] gi.dprintf("%s cannot connect - server is full!\n", "VoodooBot");
[+] }

That wraps up our create function. In this function, first we try to find a free client slot for our bot to appear in. Then we create a structure for holding the user info and set with our information. Next we tell the server that a client is trying to connect. When everything is ready, we create a boink and let everyone in the game know about the arrival of our bot.

The last function we will write for this lesson is a long one. So be prepared. It is the function responsible for the actual spawning. It is a slightly modified version of PutClientInServer(...) found in p_client.c

Open tutorial.c:

[+] void Bot_Spawn(edict_t *ent)
[+] {
[+] vec3_t origin, angles;
[+] vec3_t mins = {-16, -16, -24};
[+] vec3_t maxs = {16, 16, 32};
[+] int i, index;
[+] client_persistant_t pers; // Note: wrong spelling for "persistent"!
[+] client_respawn_t resp;

[+] if (!deathmatch->value)
[+] {
[+] gi.dprintf("Must be in Deathmatch to spawn Voodooent!\n");
[+] return;
[+] }

[+] SelectSpawnPoint(ent, origin, angles);

[+] index = ent - g_edicts - 1;

[+] if (deathmatch->value)
[+] {
[+] char userinfo[MAX_INFO_STRING];

[+] resp = ent->client->resp;
[+] memcpy(userinfo, ent->client->pers.userinfo, MAX_INFO_STRING);
[+] InitClientPersistant(ent->client);
[+] ClientUserinfoChanged(ent, userinfo);
[+] }
[+] else
[+] memset(&resp, 0, sizeof(client_respawn_t));

[+] pers = ent->client->pers;
[+] memset(ent->client, 0, sizeof(gclient_t));
[+] ent->client->pers = pers;
[+] ent->client->resp = resp;

[+] FetchClientEntData(ent);

[+] ent->groundentity = NULL;
[+] ent->client = &game.clients[index];
[+] ent->takedamage = DAMAGE_AIM;
[+] ent->movetype = MOVETYPE_WALK;
[+] ent->viewheight = 22;
[+] ent->inuse = true;
[+] ent->classname = "bot";
[+] ent->mass = 200;
[+] ent->solid = SOLID_BBOX;
[+] ent->deadflag = DEAD_NO;
[+] ent->air_finished = level.time + 12;
[+] ent->clipmask = MASK_PLAYERSOLID;
[+] ent->think = NULL;
[+] ent->touch = NULL;
[+] ent->pain = player_pain;
[+] ent->die = player_die;
[+] ent->waterlevel = 0;
[+] ent->watertype = 0;
[+] ent->flags &= ~FL_NO_KNOCKBACK;
[+] ent->enemy = NULL;
[+] ent->movetarget = NULL;

[+] VectorCopy(mins, ent->mins);
[+] VectorCopy(maxs, ent->maxs);
[+] VectorClear(ent->velocity);

[+] memset(&ent->client->ps, 0, sizeof(player_state_t));

[+] for (i = 0; i < 3; i++)
[+] {
[+] ent->client->ps.pmove.origin[i] = origin[i] * 8;
[+] ent->client->ps.pmove.delta_angles[i] = ANGLE2SHORT(angles[i] - ent->client->resp.cmd_angles[i]);
[+] }

[+] ent->client->ps.fov = 90;
[+] ent->client->ps.gunindex = gi.modelindex(ent->client->pers.weapon->view_model);

[+] ent->s.effects = 0;
[+] ent->s.skinnum = index;
[+] ent->s.modelindex = 255;
[+] ent->s.modelindex2 = 255;
[+] ent->s.frame = 0;

[+] VectorCopy(origin, ent->s.origin);
[+] ent->s.origin[2]++;

[+] ent->s.angles[PITCH] = 0;
[+] ent->s.angles[YAW] = angles[YAW];
[+] ent->s.angles[ROLL] = 0;

[+] VectorCopy(ent->s.angles, ent->client->ps.viewangles);
[+] VectorCopy(ent->s.angles, ent->client->v_angle);

[+] gi.unlinkentity(ent);
[+] KillBox(ent); // Telefrag!
[+] gi.linkentity(ent);

[+] ent->client->newweapon = ent->client->pers.weapon;
[+] ChangeWeapon(ent);

[+] ent->nextthink = level.time + FRAMETIME;
[+] }

Whew. The line worth noting is where we set the classname of the bot. This tells the Quake II server that we are creating an entity of the class "bot". It will be useful for determining what kind of entities we are dealing with in the future.

Okay. I am sure you typed long and hard if you have come this far. Go ahead and compile the fruits of our labor. Start a deathmatch using the new DLL. Bring down the console and type "sv bot spawn". Voila! A bot is created in the game. Now go find it. You will notice that it is in a weird position. That is alright. We will address that in the next lesson(s). See you next time!

Lesson 2:

In the first lesson, I showed you how to spawn a bot in Quake II. Although it did nothing useful at the time, it served as a foundation for us to build upon. A good start is half way to the goal.

Recall that the bot entity we have so far does not like to die, per se. If you try to kill it, Quake II will crash and complain. As all deathmatch players already know, after you get killed, you respawn. We will add this ability to VoodooBot in this lesson.

The conventions used for code snippets:
Line beginning with [=] is original code.
Line beginning with [-] is deleted code.
Line beginning with [*] is changed code.
Line beginning with [+] is new code.

First let us supply the Bot_Respawn(...) function responsible for respawning VoodooBot. We have already written the majority of it - we can reuse Bot_Spawn(...)!

Open up tutorial.h:
[+] void CopyToBodyQue(edict_t *ent);
[+] void ClientThink(edict_t *ent, usercmd_t *cmd);

[=] void Cmd_Bot_f(edict_t *ent);
[=] void Bot_Create(void);
[=] void Bot_Spawn(edict_t *ent);
[+] void Bot_Respawn(edict_t *ent);

[+] void Bot_Think(edict_t *ent);

Open up tutorial.c:
[+] void Bot_Respawn(edict_t *ent)
[+] {
[+] CopyToBodyQue(ent);

[+] Bot_Spawn(ent);

[+] ent->s.event = EV_PLAYER_TELEPORT;

[+] ent->client->ps.pmove.teleport_time = 50;
[+] }

Find function Bot_Spawn(...):
[=] ent->air_finished = level.time + 12;
[=] ent->clipmask = MASK_PLAYERSOLID;
[*] ent->think = Bot_Think;
[=] ent->touch = NULL;
[=] ent->pain = player_pain;
[=] ent->die = player_die;

Is that all there is to respawning?? Well, yes and no. The function itself is quite simple as you can see. But we need to modify the original source code a bit before VoodooBot can respawn correctly. Also, note that we have indicated to the Quake II server that the bot will have its own "think" function. We will see what that is about later in this lesson.

First, we have to take care of the help screen that comes up when a player dies. As VoodooBot is just a bot, it does not need to see it. This is what we do.

Open up p_hud.c, find the function Cmd_Help_f(...):

[=] char *sk;

[+] if (Q_stricmp(ent->classname, "bot") == 0)
[+] return;

[=] if (ent->client->showscores && !game.helpchanged)
[=] {
[=] ent->client->showscores = false;
[=] return;
[=] }

Here, we prevent the help screen from showing up by comparing ent->classname with "bot". If they match, then we know we should not show it. Are you not glad that we have set the classname in the first lesson?

The second hurdle we need to overcome is to redirect the call to respawn(...) to call our new Bot_Respawn(...) function instead. Again, ent->classname comes in handy.

Open up p_client.c, find the function respawn(...):

[=] if (deathmatch->value /*|| coop->value */)
[=] {

[+] if (Q_stricmp(self->classname, "bot") == 0)
[+] {
[+] Bot_Respawn(self);
[+] return;
[+] }

[=] CopyToBodyQue (self);
[=] PutClientInServer (self);

The respawn(...) function is automatically called when a player dies. Since VoodooBot is a client, when it dies, this function also gets called. But it calls our bot-specific respawn instead.

So far so good. But the bot still needs to press the fire button to cause a respawn, just like a real client does. How do we do that? Good question. Let me digress a little bit on how Quake II executes command from a player. There is a structure defined in q_shared.h called usercmd_t.

Look into q_shared.h:

#define BUTTON_ATTACK 1
#define BUTTON_USE 2
#define BUTTON_ANY 128 // any key whatsoever

typedef struct usercmd_s
{
byte msec; // Lag time?
byte buttons; // Button pressed
short angles[3]; // View angles
short forwardmove, sidemove, upmove; // Movement
byte impulse; // remove?
byte lightlevel; // light level the player is standing on
} usercmd_t;

The fields that we are interested in are msec, buttons, angles, forwardmove, sidemove, and upmove. They are quite self-explanatory actually. At the moment, we will deal with the buttons field. It simply tells the Quake II server which button the client is holding. The rest of the fields will be explained in the next lesson(s). Note that several #define's are present in q_shared.h. We will be using the BUTTON_ATTACK for respawning.

Let me introduce you to the most important of all bot functions now -- Bot_Think(...):

Open up tutorial.c:

[+] void Bot_Think(edict_t *ent)
[+] {
[+] usercmd_t cmd;
[+] vec3_t angles = { 0, 0, 0 };

[+] VectorCopy(ent->client->v_angle, angles);
[+] VectorSet(ent->client->ps.pmove.delta_angles, 0, 0, 0);
[+] memset(&cmd, 0, sizeof(usercmd_t));

[+] if (ent->deadflag == DEAD_DEAD)
[+] {
[+] ent->client->buttons = 0;
[+] cmd.buttons = BUTTON_ATTACK;
[+] }

[+] cmd.msec = 100;

[+] cmd.angles[PITCH] = ANGLE2SHORT(angles[PITCH]);
[+] cmd.angles[YAW] = ANGLE2SHORT(angles[YAW]);
[+] cmd.angles[ROLL] = ANGLE2SHORT(angles[ROLL]);

[+] ClientThink(ent, &cmd);

[+] ent->nextthink = level.time + FRAMETIME;
[+] }

Basically, the function initializes the usercmd_t structure and view angles to the previous settings. Then it checks to see if the bot is dead (DEAD_DEAD). If so, we stuff a BUTTON_ATTACK into the buttons field of the usercmd_t. Finally, we call the actual function that executes user commands -- ClientThink(...). We will modify this function in the next lesson(s) so that the bot can do more than just respawning itself.

Are we there yet? We are very close. We just need to do one more step, which is to re-route the game flow to call the think function of our bot. This is what we do:

Open up g_phys.c, find the function G_RunEntity(...):

[=] switch ( (int)ent->movetype)
[=] {

[+] case MOVETYPE_WALK:
[+] SV_RunThink(ent);
[+] break;

[=] case MOVETYPE_PUSH:
[=] case MOVETYPE_STOP:
[=] SV_Physics_Pusher (ent);
[=] break;

Open up g_main.c, find function G_RunFrame(...):
[=] if (i > 0 && i <= maxclients->value)
[=] {
[=] ClientBeginServerFrame (ent);

[-] continue; // Delete this!!
[=] }

[=] G_RunEntity (ent);
[=] }

With these modifications, the Quake II game engine will now call Bot_Think(...) every so often (0.1 second). As you can imagine, we can do all sort of neat things in this function. That will be explored in the next installment(s). Now compile the source code and try to kill VoodooBot. It just keeps spawning and spawning and spawning... See you next time!

Lesson 3:

In the second lesson, I showed you how to make VoodooBot respawn after being killed. The cycle of spawn and die repeated itself until you got bored and shutdown Quake II. This lesson is going to show you how to make VoodooBot do something interesting, by giving it a primitive brain. This brain knows one and only one thing: revenge. You will see what I mean as we go along. So buckle up!

The conventions used for code snippets:
Line beginning with [=] is original code.
Line beginning with [-] is deleted code.
Line beginning with [*] is changed code.
Line beginning with [+] is new code.

First up, let us provide the ability to kill a bot that is already in the game. It is relatively easy to do.

Open up tutorial.c, find function Svcmd_Bot_f(...):
[=] char *arg = gi.argv(2);

[+] if (Q_stricmp(arg, "kill") == 0)
[+] {
[+} int i;
[+] char *name = gi.argv(3);
[+] edict_t *bot;

[+] for (i = maxclients->value; i > 0; i--)
[+] {
[+] bot = g_edicts + i + 1;

[+] if (bot->client &&
[+] (Q_stricmp("bot", bot->classname) == 0) &&
[+] (Q_stricmp(name, bot->client->pers.netname) == 0))
[+] {
[+] ClientDisconnect(bot);
[+] break;
[+] }
[+] }
[+] }
[+] else

[=] if (Q_stricmp(arg, "spawn") == 0)
[=] Bot_Create();

When you issue the "sv bot kill {name}" command from the console, the Svcmd_Bot_f(...) function is invoked. We extract the last parameter, name, and compare it with every client current in the server. If a match is found, the client is disconnected from the server. Very simple, no? You may be wondering, "Why not just use the kick command?" Well, I think it is neat that we can program it ourselves, do you not agree?

Next, we will write a function to do the aiming for the bot when it wishes to attack an enemy. This requires a bit of mathematics in the third dimension.

Open up tutorial.c:
[+] void Bot_Aim(edict_t *ent, edict_t *target, vec3_t angles)
[+] {
[+] vec3_t dir, start, end;

[+] VectorCopy(target->s.origin, start);
[+] VectorCopy(ent->s.origin, end);
[+] VectorSubtract(start, end, dir);
[+] vectoangles(dir, angles);
[+] }

Well, this may not look like the world's fastest code, but it is setup with future expansion in mind. Recall from Calculus, when you subtract one point from another in 3D, you get a vector back. The vector points to the starting point. This is exactly what we have done. We modify the angles to point to the target from where the ent is at. The ent is the bot, of course.

With the ability to aim at an enemy, VoodooBot is now ready to make an attack. This is not difficult, considering that all we need to do is stuff a BUTTON_ATTACK to the command structure.

Open up tutorial.c:

[+] void Bot_Attack(edict_t *ent, usercmd_t *cmd, vec3_t angles)
[+] {
[+] if (ent->enemy->deadflag == DEAD_DEAD)
[+] ent->enemy = NULL;
[+] else
[+] {
[+] if (infront(ent, ent->enemy))
[+] {
[+] Bot_Aim(ent, ent->enemy, angles);

[+] if (random() < 0.3) // Don't fire too often!
[+] cmd->buttons = BUTTON_ATTACK;
[+] }
[+] }
[+] }

After checking that the enemy is still alive (there is no point in shooting the dead...), the bot checks to see if the enemy is in its line-of-sight. If so, it points its gun at the enemy and stuff a BUTTON_ATTACK into the command structure. Now, if the bot makes an attack everytime, it will actually fire faster than a real client. We do not wish that to happen. The if (random() < 0.3) balances things out a little better, so the bot will shoot less often.

So now VoodooBot knows how to aim and attack an enemy. But what is the definition of an enemy, anyway? Good question! At the very least, I would say whoever attacked the bot should be classified as an enemy! For this lesson, I will use this criterion. Your mileage may vary, of course. Feel free to explore the idea of what makes an enemy. Anyway, this is what we do.

Open up tutorial.c, find function Bot_Spawn(...):

[=] ent->clipmask = MASK_PLAYERSOLID;
[=] ent->think = Bot_Think;
[=] ent->touch = NULL;
[*] ent->pain = Bot_Pain;
[=] ent->die = player_die;

Still in tutorial.c:
[+] void Bot_Pain(edict_t *ent, edict_t *other, float kickback, int damage)
[+] {
[+] if (ent != other)
[+] {
[+] ent->oldenemy = ent->enemy;
[+] ent->enemy = other;
[+] }

[+] player_pain(ent, other, kickback, damage);
[+] }

What we have just done is supply the bot a so-called "pain" function. This function is called whenever the bot is in "pain". i.e. It is being hurt by something. Okay! So whoever is hurting the bot becomes the new enemy! Revenge!!!

Now the bot knows who the enemy is. To make bot actually act on this knowledge, we need to modify its think function.

Open up tutorial.c, find function Bot_Think(...):

[=] if (ent->deadflag == DEAD_DEAD)
[=] {
[=] ent->client->buttons = 0;
[=] cmd.buttons = BUTTON_ATTACK;
[=] }
[+] else if (ent->enemy)
[+] {
[+] Bot_Attack(ent, &cmd, angles); // Attack enemy!
[+] }

[=] cmd.msec = 100;

Very well. Now if the bot is dead, it will respawn. If it has an enemy, it will attack. There you have it, a primitive combat brain for VoodooBot! See you next time!

Lesson 4:

In the third lesson, we developed a primitive combat brain for VoodooBot. It could shoot back when you shot at it and it would track your position until you ran out of its line of sight. Of course, by itself, that does not make for a very good bot. For one thing, it could almost always hit in close range due to the perfect aiming function. For another, the bot just stands at the spawn point. It does not know how to move at all.

We wish to up the ante a bit by making the bot move by actively searching for an enemy instead of being a sitting duck, waiting to be attacked.

The conventions used for code snippets:
Line beginning with [=] is original code.
Line beginning with [-] is deleted code.
Line beginning with [*] is changed code.
Line beginning with [+] is new code.

First I want to refresh your memory of the usercmd_t structure. Recall:

Open up q_shared.h:
// usercmd_t is sent to the server each client frame
typedef struct usercmd_s
{
byte msec;
byte buttons;
short angles[3];
short forwardmove, sidemove, upmove;
byte impulse; // remove?
byte lightlevel; // light level the player is standing on
} usercmd_t;

The forwardmove, sidemove, and upmove are the fields that concern player movements. They are quite self-explanatory. The forwardmove describes the forward (+) and backward (-) velocities. The sidemove is the side-strafing velocity while upmove determines if the player is jumping or crouching. Very simple, really.

Now that we know what we need to do to move, we have to actually do it. Like all good C programmers, we will create the function prototype for the new move function, Bot_Move(...) and provide the definition.

Open up tutorial.h:
[=] void Bot_Think(edict_t *ent);
[=] void Bot_Aim(edict_t *ent, edict_t *target, vec3_t angles);
[=] void Bot_Attack(edict_t *ent, usercmd_t *cmd, vec3_t angles);
[+] void Bot_Move(edict_t *ent, edict_t *goal, usercmd_t *cmd, vec3_t angles);

[+] edict_t *Bot_FindEnemy(edict_t *ent);

Open up tutorial.c:
[+] void Bot_Move(edict_t *ent, edict_t *goal, usercmd_t *cmd, vec3_t angles)
[+] {
[+] Bot_Aim(ent, goal, angles);
[+] cmd->forwardmove = 200; // Walk speed
[+] }

Hold it right there! "Am I seeing what I think I am seeing?" you ask yourself. Yes, that is exactly what you are seeing. The whole Bot_Move(...) is merely 4 lines!! (Less if you use put the braces differently.) What you are looking at is the creative use of the Bot_Aim(...) function we developed in the last lesson. Assuming the bot has a goal entity, it "aims" at the goal and move forward in that direction. Done.

That is all fine and dandy. But there is one problem: what is the goal entity and how do we find it? Or rather, how does VoodooBot find a goal to move to? There are a lot of possible candidates. For example, the bot may move towards its enemy to get a better shot. Or the bot may want to collect that BFG lying over there, begging to be picked up. Or maybe health is low and the bot is trying to find a health pack to replenish it. Whatever the reason, there sure are lots of entities out there in the game world that can be a goal for the bot to reach.

For the purpose of this lesson, I will make VoodooBot a little more aggressive by providing a function to actively seek out an enemy in sight to attack. Once a target is found, it becomes the goal. With this in mind, I present to you the function called Bot_FindEnemy(...). I strongly encourage you to expand on this function or create new functions for other types of goal searching.

Open up tutorial.c:

[+] edict_t *Bot_FindEnemy(edict_t *ent)
[+] {
[+] int range = 1000;
[+] char *classname;
[+] edict_t *enemy = ent->enemy, *newenemy = NULL;

[+] if (enemy == NULL)
[+] {
[+] classname = "player";

[+] while ((newenemy = findradius(newenemy, ent->s.origin, range)) != NULL)
[+] {
[+] if (!newenemy->client)
[+] continue;

[+] // Don't fight self or mess with the dead or invisible
[+] if ((newenemy != ent) && (newenemy->deadflag != DEAD_DEAD))// && (pxE->light_level > 5))
[+] {
[+] if ((Q_stricmp(newenemy->classname, classname) == 0) &&
[+] visible(ent, newenemy))
[+] {
[+] if (infront(ent, newenemy))
[+] {
[+] enemy = newenemy;
[+] break;
[+] }
[+] }
[+] }
[+] }
[+] }

[+] return enemy;
[+] }

This medium-sized function is easy to understand if we break it down into parts. The function simply returns the old enemy if it exists. Otherwise, it scans the perimeter (range determines how big the circle is) for an entity of the class "player". All real clients (i.e. You) belong to the "player" class. The combined use of visible(...) and infront(...) as before determines if the player is in the line of sight. This is done so that the bot does not appear to have eyes on their back. It is, thus, oblivious to players standing behind it. Good.

Now that we have a function to move the bot, we should modify the Bot_Attack(...) function a little.

Open up tutorial.c, find function Bot_Attack(...):

[=] if (infront(ent, ent->enemy))
[=] {
[*] Bot_Move(ent, ent->enemy, cmd, angles);

[=] if (random() < 0.3)
[=] cmd->buttons = BUTTON_ATTACK;
[=] }

This modification causes the bot to move towards the enemy it is trying to attack. Hopefully, this will make it easier to hit. As long as the enemy is in sight, VoodooBot will chase after him/her. The bot is starting to shape up, eh?

But wait, one thing is still missing. The Bot_Think(...) function has to make a call to Bot_FindEnemy(...) to give the desirable seek and destroy behaviour.

Open up tutorial.c, find function Bot_Think(...):

[=] else if (ent->enemy)
[=] {
[=] Bot_Attack(ent, &cmd, angles);
[=] }
[+] else
[+] {
[+] ent->enemy = Bot_FindEnemy(ent);
[+] }

That is all there is to it. Compile the DLL and give VoodooBot a try. You will notice that as soon as you get within its visual range and into the bot's line of sight, it will start shooting and moving towards you. Run away! Better yet, fight back! See you next time!