The Tutor Bot is about to graduate into the real world.
I'm sure this has been one of the most anticipated tutorials. After all, everyone wants their bots to come up on the scoreboard. This is the simple most important aspect in making your artificial opponent seem like a player. The time is now.
We all have Alan Kivlin to thank for this; he did what no one thought possible. The cool thing is that this lesson is more busy work than anything else. It's one of those tuts where you just have to know how to do it, you don't have to understand it.
Step One. Open up world.qc and scroll down to the sub worldspawn(), where the Quake world is born. You will see this:
//======================= /*QUAKED worldspawn (0 0 0) ? Only used for the world entity. Set message to the level name. Set sounds to the cd track to play. World Types: 0: medieval 1: metal 2: base */ //======================= void() worldspawn = { lastspawn = world; InitBodyQue ();After this code, add the following line:
clientInitMaxClients();When the world starts, we will call this subroutine, which Alan wrote. Just as it looks, this sub will "initialize the maximum number of clients," or basically count the entities in the list. Anyway, save and close that file.
/* =========== ClientConnect called when a player connects to a server ============ */ void() ClientConnect = {After that, add the following code:
local entity bot; self.fClientNo = self.colormap - 1; if( clientIsActive( self.fClientNo ) ) botInvalidClientNo( self.fClientNo ); clientSetUsed( self.fClientNo ); bot = find( world, classname, "bot" ); while( bot ) { msgUpdateNameToPlayer( self, bot.fClientNo, bot.netname ); msgUpdateColorsToPlayer( self, bot.fClientNo, bot.fShirt, bot.fPants ); msgUpdateFragsToPlayer( self, bot.fClientNo, bot.frags ); bot = find( bot, classname, "bot" ); }This prints out every bot's name, color, and frags each time a player connects to the server. Wow. Next.
/* =========== ClientDisconnect called when a player disconnects from a server ============ */ void() ClientDisconnect = {Add the following line after that stuff:
clientSetFree( self.fClientNo );
attacker.owner.frags = attacker.owner.frags + 1;Add this one right under it:
msgUpdateFragsToAll (attacker.owner.fClientNo, attacker.owner.frags);See what we did? Since the attacker owner earned a frag, we "updated frags to all" players. (I'm telling you this in case you -- or I -- miss one of these lines. Everywhere in ClientObituary() where frags are changed, we need one of these "msg" lines.)
targ.frags = targ.frags - 1;Add this one:
msgUpdateFragsToAll (targ.fClientNo, targ.frags);Next go down to this line:
attacker.frags = attacker.frags - 1;Add this one:
msgUpdateFragsToAll (attacker.fClientNo, attacker.frags);Now go below to this line:
attacker.frags = attacker.frags - 1;And add this one:
msgUpdateFragsToAll (attacker.fClientNo, attacker.frags);Again slide down more to this line:
attacker.frags = attacker.frags + 1;Paste this one:
msgUpdateFragsToAll (attacker.fClientNo, attacker.frags);Finally, to this line:
targ.frags = targ.frags - 1;Again, paste this one:
msgUpdateFragsToAll (targ.fClientNo, targ.frags);Whoa, we're done. Now you should have six of these calls to msgUpdateFragsToAll(). You probably noticed the pattern: whatever player got his frags changed was the player who was between the parentheses. You must get that right.
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ void(entity bot) put_bot_in_server = // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ { local float clientno; clientno = clientNextAvailable(); if (clientno == -1) { bprint( "game not started correctly\nset 'maxplayers' to 16 and restart\n" ); return; } clientSetUsed( clientno ); bot.fClientNo = clientno; bot.fPants = floor(random() * 14); bot.fShirt = floor(random() * 14); bot.colormap = clientno + 1; msgUpdateNameToAll( bot.fClientNo, bot.netname ); msgUpdateColorsToAll( bot.fClientNo, bot.fShirt, bot.fPants ); msgUpdateFragsToAll( bot.fClientNo, bot.frags ); bprint(bot.netname); bprint(" joins the game\n"); };Hmm, okay, looks like dull technical stuff to me. To sum it up, we check to see if the bot can fit on the scoreboard. We give him a client number. And we give him a shirt and pants color.
bprint(bot.netname); bprint(" enters the game\n");Delete them. Since we now do this in put_bot_in_server(), we don't need them here. Next, scroll down into create_bot(). At the very end, add this:
put_bot_in_server(bot); if (!bot.fClientNo) remove(bot);Looks harmless, right? It is. First we make a call to put_bot_in_server() for that bot. Then we check to see if he came out with a client number. If he has one, cool, he's good to go. If he does not have one, that means that put_bot_in_server() did not work because of the reasons described above. So if he did not fit into the game, we just remove him.