I'm sure by this point you have come to realize the strength of my waypoint navigation system. However, I'm also certain that you have come to notice its weakness: though the Tutor Bot can now roam a whole level and travel right over items, he can't even pick them up.
This is simple to fix. You see, id Software basically locked all of the monsters out of the item touch routines. It's true that the player does possess special physics and features, but our bot can easily be allowed to touch these objects.
First, a few simple changes in the file TUTOR.QC. Go down to the routine bot_walk(). You will see a line that says bot_grab_items(). Delete it. Since he will actually be touching items, he won't have to reach out and make them invisible.
Then in both respawn_bot() and create_bot(), you will see a call to give_random_weapon(). Delete it from each routine. Since he will be picking up weapons, you won't need to give him any.
Next we have to add a couple of lines that I forgot. Drop down to the routine respawn_bot(). After the line "self.health = 100", add this:
self.maxhealth = 100;Drop down to create_bot() and where you see "bot.health = 100" add this:
bot.maxhealth = 100;These two lines just make him ready to be healed by health boxes. Save and close that file. Next open up ITEMS.QC and slide down to health_touch(), where you'll see this:
void() health_touch = { local float amount; local string s; if (other.classname != "player") return;I bet you, being the sophisticated coder you are, can spot our problem already, right? When another entity touches the health box, that entity is the "other." Here, as you can see, our bot is locked out. We want to change those last two lines to:
if (other.classname != "player" && other.classname != "bot") return;Very easy. Now our bot is allowed inside the touch routine. All entities *not* named "player" or "bot" are locked out. We have to edit the routine a bit though. You will see this stuff:
sprint(other, "You receive "); s = ftos(self.healamount); sprint(other, s); sprint(other, " health\n"); // health touch sound sound(other, CHAN_ITEM, self.noise, 1, ATTN_NORM); stuffcmd (other, "bf\n");Unfortunately, this code is geared toward a player. A bot cannot accept an sprint() command, nor a stuffcmd() statement. So we will change all of that to this:
if (other.classname == "player") { sprint(other, "You receive "); s = ftos(self.healamount); sprint(other, s); sprint(other, " health\n"); stuffcmd (other, "bf\n"); } // health touch sound sound(other, CHAN_ITEM, self.noise, 1, ATTN_NORM);Here we targeted the player-only code towards entities named player. We also left the sound() command outside the brackets, so when a bot picks up a health box, it will make a sound. Our bot will now be able to touch any health box and be healed.
void() armor_touch = { local float type, value, bit; if (other.health <= 0) return; if (other.classname != "player") return;We will use the same solution as before, changing those last two lines to this:
if (other.classname != "player" && other.classname != "bot") return;That lets him inside. But again, we have to edit the player-specific code. Drop to the bottom of the routine, where this is:
sprint(other, "You got armor\n"); // armor touch sound sound(other, CHAN_ITEM, "items/armor1.wav", 1, ATTN_NORM); stuffcmd (other, "bf\n");Paste the following code over that crap:
if (other.classname == "player") { sprint(other, "You got armor\n"); stuffcmd (other, "bf\n"); } // armor touch sound sound(other, CHAN_ITEM, "items/armor1.wav", 1, ATTN_NORM);Just like before, the bot can trigger the sound but not the message. And now our bot can touch and collect armor. Seems too simple, doesn't it? That just goes to show you that id software really didn't foresee the advent of bots.
void() weapon_touch = { local float hadammo, best, new, old; local entity stemp; local float leave; if (!(other.flags & FL_CLIENT)) return;Yes, the last two lines are a bit different, but we can still change them to this:
if (other.classname != "player" && other.classname != "bot") return;That was okay. Now slide down a bit and look at this:
sprint (other, "You got the "); sprint (other, self.netname); sprint (other, "\n"); // weapon touch sound sound (other, CHAN_ITEM, "weapons/pkup.wav", 1, ATTN_NORM); stuffcmd (other, "bf\n");Once more, this is player-only code. We have to change that to this:
if (other.classname == "player") { sprint (other, "You got the "); sprint (other, self.netname); sprint (other, "\n"); stuffcmd (other, "bf\n"); } // weapon touch sound sound (other, CHAN_ITEM, "weapons/pkup.wav", 1, ATTN_NORM);I know, that's getting repetitive, but we got to do it. A little down past that you will see a line that says W_SetCurrentAmmo(). Change that line to this:
if (other.classname == "player") W_SetCurrentAmmo(); if (other.classname == "bot") bot_set_currentammo();The subroutine W_SetCurrentAmmo() has a lot of player-only code in it, so we have to tell the bot to stay out of it and to call his own routine. The bot will now pick up the weapon.
void() ammo_touch = { local entity stemp; local float best; if (other.classname != "player") return;That damn id Software again, making our lives difficult. Anyhow, change the last two lines of that to this:
if (other.classname != "player" && other.classname != "bot") return;Slip down a bit and you will see the second familiar problem:
sprint (other, "You got the "); sprint (other, self.netname); sprint (other, "\n"); // ammo touch sound sound (other, CHAN_ITEM, "weapons/lock4.wav", 1, ATTN_NORM); stuffcmd (other, "bf\n");Dammit, this is getting old. Oh well, we must press on. We also must change that to this:
if (other.classname == "player") { sprint (other, "You got the "); sprint (other, self.netname); sprint (other, "\n"); stuffcmd (other, "bf\n"); } // ammo touch sound sound (other, CHAN_ITEM, "weapons/lock4.wav", 1, ATTN_NORM);Ah, that's much better. Okay, now we're onto the biggest change yet. When you pick up ammo, sometimes you switch to the best weapon you have if you ran out of ammo for it. This is problematic for us and our bot. Look at this stuff:
if ( other.weapon == best ) { stemp = self; self = other; self.weapon = W_BestWeapon(); W_SetCurrentAmmo (); self = stemp; } // if changed current ammo, update it stemp = self; self = other; W_SetCurrentAmmo(); self = stemp;Yes, that is a bit weird. Basically the code is changing the "other" to the "self" in order to call W_SetCurrentAmmo(), since that routine refers to a "self." And of course, that's not going to work for our bot. So change *all* of that to *all* of this:
if (other.classname == "player") { if ( other.weapon == best ) { stemp = self; self = other; self.weapon = W_BestWeapon(); W_SetCurrentAmmo (); self = stemp; } // if changed current ammo, update it stemp = self; self = other; W_SetCurrentAmmo(); self = stemp; } if (other.classname == "bot") { local float botbest; botbest = bot_bestweapon(); if ( other.weapon == botbest ) { stemp = self; self = other; self.weapon = bot_bestweapon(); bot_set_currentammo(); self = stemp; } // if changed current ammo, update it stemp = self; self = other; bot_set_currentammo(); self = stemp; }Damn this is ugly. Code is sometimes like that. First we put brackets around the player-only code to keep the bot out. Next we set up an area for the bot himself. Then we arm his best weapon and set his ammo to go along with it. Boring, huh?
void() BackpackTouch = { local string s; local float best; local entity stemp; local float botbest; if (other.classname != "player" && other.classname != "bot") return; if (other.health <= 0) return; // if the player was using his best weapon, change up to the new one if better stemp = self; self = other; best = W_BestWeapon(); botbest = bot_bestweapon(); self = stemp; // change weapons other.ammo_shells = other.ammo_shells + self.ammo_shells; other.ammo_nails = other.ammo_nails + self.ammo_nails; other.ammo_rockets = other.ammo_rockets + self.ammo_rockets; other.ammo_cells = other.ammo_cells + self.ammo_cells; other.items = other.items | self.items; bound_other_ammo (); // backpack touch sound sound (other, CHAN_ITEM, "weapons/lock4.wav", 1, ATTN_NORM); if (other.classname == "player") { sprint (other, "You get "); if (self.ammo_shells) { s = ftos(self.ammo_shells); sprint (other, s); sprint (other, " shells "); } if (self.ammo_nails) { s = ftos(self.ammo_nails); sprint (other, s); sprint (other, " nails "); } if (self.ammo_rockets) { s = ftos(self.ammo_rockets); sprint (other, s); sprint (other, " rockets "); } if (self.ammo_cells) { s = ftos(self.ammo_cells); sprint (other, s); sprint (other, " cells "); } sprint (other, "\n"); stuffcmd (other, "bf\n"); // change to a better weapon if appropriate if ( other.weapon == best ) { stemp = self; self = other; self.weapon = W_BestWeapon(); self = stemp; } remove(self); self = other; W_SetCurrentAmmo (); } if (other.classname == "bot") { if (other.weapon == botbest) { stemp = self; self = other; self.weapon = bot_bestweapon(); self = stemp; } remove(self); self = other; bot_set_currentammo(); } };You know I don't usually like to just serve you up with the code like that, but these tutorials don't have to be mundane. I have faith that you will see that all we did was lock the bot out of the player code and gave him bot code of his own. This way he could switch to his own best weapon and his own ammo. That's the whole idea.