
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!