Damn it Crash, Use My Boomstick!
A Quake3:Arena Mod Tutorial
by
David Frey aka Mastaba
Does this scenario sound familiar, "you've just finished implementing your very own custom weapon but the bots simply refuse to use it"?
Well don't sweat it, this tutorial will try to get you out of that fix. How? I will demonstrate all the necessary steps required to make a bot use
a custom weapon. By custom weapon, I do not mean a tweaked pre-existing gun. I mean a full fledged new gun that exists in addition to the stock weapons.
I'm assuming that you already have the gun implemented in both the server, client, and ui modules. And that it works fine in multiplayer.
This tutorial will demonstrate how to make one bot, Crash, use one new gun, the Boomstick. Why just one bot? Because once you have one bot using the new weapon
it is a really trivial matter to get the other bots to use it too. Let's get started!
There are basically three steps to go through to make our first bot, Crash, use the Boomstick. After Crash is weilding our creation, only the last step has to be done for other
bots to use it too. Oh? What are those steps you say?
The first step is to implement the weapon in the server module's bot ai code.
Now if you were really thorough in implementing your weapon, you may have already done this first step. If so just skip to the second step.
What you need to do first is define the slot in the bot ai's inventory to hold the new weapon. So in inv.h, open it up and below all the other inventory define's add in the new one for the weapon, like so:
#define INVENTORY_BOOMSTICK 35
My Boomstick also uses its own type of ammo so I need a slot for that too:
#define INVENTORY_BOOMSTICK_AMMO 36
Now comes the potentially tricky part, defining the model indices. Every item needs a model index. The value of the index is that item's (base 0) ordinal position in the bg_itemlist array which is defined in bg_misc.c.
So let's assume I went off and stuck my Boomstick entry just after the Gauntlet's entry. Every model index from the first item, and up to and including the Gauntlet, will be left unchanged. Every other item, above the Gauntlet will need a new model index.
So you can see, if you want to minimize your work at this step, put new item entries in bg_itemlist up high in the array. I'll just live with where I put my Boomstick entry and adjust model indices accordingly. So in my case I have:
#define MODELINDEX_BOOMSTICK 9
And let's say I put the its ammo entry somewhere higher in the array:
#define MODELINDEX_BOOMSTICK_AMMO 23
NOTE: Your actual values may very well be different from mine. It is dependent upon how items appear in your bg_itemlist array.
Also, the number of items can not exceed MAX_ITEMS. In my case I have nothing to worry about, I'm nowhere near that limit.
Now at this point you are finished with this file. We will be coming back to it in the second step however. Now you need to open up ai_dmq3.c.
And go to the function BotUpdateInventory. This function needs to be modified so that each bot can keep track of its new stuff. In my case the amendment is:
bs->inventory[INVENTORY_BOOMSTICK] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_BOOMSTICK)) != 0;
bs->inventory[INVENTORY_BOOMSTICK_AMMO] = bs->cur_ps.ammo[WP_BOOMSTICK];
That's all there is to the first step, so go ahead and rebuild the server module.
The second step is to code the generalized bot runtime code. This portion of the bot code is compiled at runtime by Quake3.
So what you need to do now is use Winzip (or your preferred unzipping utility) and extract all the files from the botfiles and botfiles\bots directories of the pak*.pk3 files.
Pak0.pk3 contains all the original bot ai, but some newer pk3's released with the various point releases appear to contain some updated bot code.
So you may want to extract from Pak0.pk3 first then work your way on up through the Pak*.pk3 files, letting it overwrite any files.
To make things simple to package when I'm done, I extracted to the root directory of my D: drive and had "Use folder info" enabled.
This left me with a botfiles directory that contains all the generalized bot code, and also a bots directory in it that contains all the individualized bot code. Now remember that file I adjusted in step 1, inv.h?
That file must replace the inv.h that I extracted. So I simply copy inv.h from my game's source directory into my botfiles directory letting it overwrite of course.
If you like to be oraganised, you could create a project in MSVC just to keep the files orderly. Again, you will not be building this code, Quake 3 will.
Ok, open up weapons.c. In this file I need to define the weapon index of the Boomstick as well as define the nature of the Boomstick and Boomstick ammo. So in my case I have:
#define WEAPONINDEX_GAUNTLET 1
#define WEAPONINDEX_BOOMSTICK 2
#define WEAPONINDEX_MACHINEGUN 3
#define WEAPONINDEX_SHOTGUN 4
#define WEAPONINDEX_GRENADE_LAUNCHER 5
#define WEAPONINDEX_ROCKET_LAUNCHER 6
#define WEAPONINDEX_LIGHTNING 7
#define WEAPONINDEX_RAILGUN 8
#define WEAPONINDEX_PLASMAGUN 9
#define WEAPONINDEX_BFG 10
#define WEAPONINDEX_GRAPPLING_HOOK 11
(Note! The order of the weapon indices should be the same as the order of the WP_* in the weapons_t enum in bg_public.h)
And then below the defines I have:
//===========================================================================
// Boomstick
//===========================================================================
projectileinfo
{
name "boomstickshell"
damage 90
damagetype DAMAGETYPE_IMPACT
}
weaponinfo
{
name "Boomstick"
number WEAPONINDEX_BOOMSTICK
projectile "boomstickshell"
numprojectiles 1
speed 0
}
Ok, that's all there is to weapons.c, so now I open items.c. In this file the items are defined for the bots. This definition is what basically ties all these indices together.
So I have to add to items.c:
iteminfo "weapon_boomstick"
{
name "Boomstick"
model "models/weapons2/boomstick/boomstick.md3"
modelindex MODELINDEX_BOOMSTICK
type ITEM_WEAPON
index INVENTORY_BOOMSTICK
respawntime 45
mins {-15,-15,-15}
maxs {15,15,15}
}
iteminfo "ammo_boomstickshells"
{
name "Boomstick Shells"
model "models/powerups/ammo/boomstickam.md3"
modelindex MODELINDEX_BOOMSTICK_AMMO
type ITEM_AMMO
index INVENTORY_BOOMSTICK_AMMO
respawntime 45
mins {-15,-15,-15}
maxs {15,15,15}
}
Ok, so now being done with items.c, I move on to fw_weap.c. This file contains all the code the bots use for determining how desirable each weapon is when it wants or needs to switch weapons. So following by example I have to add:
weight "Boomstick"
{
switch(INVENTORY_BOOMSTICK)
{
case 1: return 0;
default:
{
switch(INVENTORY_BOOMSTICK_AMMO)
{
case 1: return 0;
default:
{
return W_BOOMSTICK;
}
}
}
}
}
Now looking at that code you may feel a little funny and asking yourself,"why is the switch based on a constant?" My answer to that question is that that switch-case logic is not real ANSI C code. It looks very similar. It actually is a special C-like construct that defines a fuzzy logic relation. The Quake 3 vm expects that switch constant to reference an inventory item.
The case succeeds if the switch value is less than the case value. So this code could be translated as, "If the bot does not have the Boomstick then do not use it (return a weight of 0), else if the bot does not have any Boomstick ammo, do not use the Boomstick, else return the desired statistical weight of the Boomstick."
Also you may be worried about the appearance of a completely new constant, W_BOOMSTICK. That is not a typo, also, it has nothing to do with WP_BOOMSTICK from the weapon_t enum. W_BOOMSTICK is a weight that is defined by each individual bot. We'll see it again in step 3.
Ok being done with fw_weap.c, let's make the final necessary modification to the genralized bot code in fw_items.c. This code is what the bot uses when it decides what item to pick up.
At the top of this file some important macros are defined. Unfortunately I don't know exacly what they do as I have no idea what the balance function does in detail. But no matter, I can still follow by example and play with the values later if I want to. So again by example I simply add:
weight "weapon_boomstick"
{
switch(INVENTORY_BOOMSTICK)
{
case 1:
{
switch(INVENTORY_BOOMSTICK_AMMO)
{
case 40: return WEAPON_SCALE(W_BOOMSTICK - 10);
default: return WEAPON_SCALE(W_BOOMSTICK);
}
}
default:
{
#ifdef WEAPONS_STAY
switch(INVENTORY_BOOMSTICK_AMMO)
{
case 50: return WEAPON_SCALE(GWW_BOOMSTICK);
case 200: return WEAPON_SCALE(GWW_BOOMSTICK - 10);
default: return balance(5, 3, 7);
}
#else
return 1;
#endif
}
}
}
weight "ammo_boomstickshells"
{
switch(INVENTORY_BOOMSTICK_AMMO)
{
case 20: return AMMO_SCALE(20);
case 40: return AMMO_SCALE(20);
case 60: return AMMO_SCALE(20);
case 80: return AMMO_SCALE(20);
case 100: return AMMO_SCALE(20);
case 120: return AMMO_SCALE(20);
case 140: return AMMO_SCALE(20);
case 160: return AMMO_SCALE(20);
case 180: return AMMO_SCALE(20);
case 200: return AMMO_SCALE(20);
default: return 0;
}
}
That's all there is to step 2.
Step 3 is to set the statistcal weights of the Boomstick for each bot. I'm only going to do Crash for now. All the other bots are done in exactly the same manner.
So I open up crash_w.c, this file contains the statistcal weights of the weapons Crash uses when she is deciding what weapon to use. I'm gonna make Crash a Boomstick whore. :)
And so I add the Boomstick weight:
#define W_BOOMSTICK 400
Now in crash_i.c, I need to set the statistical weights of the Boomstick pickup item:
#define W_BOOMSTICK 400
#define GWW_BOOMSTICK 100
GWW_BOOMSTICK is an alternate weight that is used when the bot already has the Boomstick and weapons stay is enabled. That's all that needs to be done! Besides packaging the files into a pk3 that is, which I assume you can do. Note: I've never actually tried just setting up one bot, in reality I set up all the bots and then went back and tweaked them.
You may or may not be able to compile a bot (by adding it to the game) if only that one is changed. I only know it works when all the bots have been setup.
But I would assume it will work for a given bot as long as that bot is setup.
Damn it Crash! Give me back my Boomstick!
- Mastaba
mastabaprime@hotmail.com
© 2000 David Frey