Inside3D tutorials.
Created By: Roscoe A. Sincero
eMail: legion@keg.zymurgy.org
Difficulty Scale: Hard


Reaperbot Improvement Protocol v0.11 (RIP v0.11)
by legion, the ubiquitous llama-killer

PART II


PREFACE



This tutorial is broken up into several parts. Each week a new part of the lesson will be posted. Visit Inside3D or Scrap Heap for your weekly reaperbot fix. If you want the entire lesson, you can ask Mr? of Inside3D for a copy of the text file called rip010.txt. It is an earlier version of the tutorial. It is not jammed packed with information like this new version but it contains most of the steps involved in the lesson. Rip010 is not for the novice. In addition, it doesn't contain any instructions on merging the reaperbot with v1.06 of QuakeC. Older versions of QuakeC contain the infamous disappearing weapons bug.
If you are a newbie, then the only information and help you will get is the weekly reaperbot lesson. No additional help will be provided with rip010.txt or with anything else.
The urls for Inside3D and Scrap Heap are the following:

1) http://www.planetquake.com/iqc (Inside3D)
2) http://www.mindspring.com/~darkskye (Scrap Heap)

Mirror site at Scrap Heap is in the works. Decision is not finalized yet.
Dark_Skye is on "vacation" trying to frag people and show that keyboarders
can be good at Quake.

The parts in the lesson are as follows:

1) rip011a.txt (this text, making a compilable reaperbot source code)
2) rip011b.txt (compiling the reaperbot with v1.06 of QuakeC)
-- this lesson is currently NOT complete and it is not part of
v0.10 of RIP. In fact, I haven't started on it, yet. It looks like
this will be a long one. Hopefully, there are no delays
. -- this will increase the size of progs.dat substantially.
-- if you can complete this, then the rest of the lessons should
be very easy for you.
3) rip011c.txt (reducing the thud sounds)
4) rip011d.txt (modifying norse_movetogoal function)
5) rip011e.txt (adding norse_movetogoal function to the reaper)
6) rip011f.txt (reducing splash sounds and adding misc things)
7) rip011g.txt (adding bot to TAB scoring rankings)

Remember, the full lesson is contained in rip010.txt. Get a copy of this text from Mr? of Inside3D if you don't want to wait seven weeks to learn everything. And remember, the only help you will get is from the weekly reaperbot lesson. So don't bother wasting bandwidth by sending e-mail asking for help. There's ONE exception, however. If enough people have difficulty with the SAME problem, then I MAY give Dark_Skye some 'news' to post that may help people solve the problem. Five or six people with the same problem is not alot and therefore, not enough. Moreover, the problem, MUST have something to do with that week's or some previous week's lesson. Any other problem will be ignored. If enough people have the same problem, visit Scrap Heap for the solution. I won't respond to e-mails individually. Don't expect solutions overnight and if you don't see a solution, chances are not enough people had the same problem.

The length of the texts is due to the fact that these are TUTORIALS as well as a how-to guide. These are more than just texts that tell you to do this and then do that. These contain explanations. It is hoped that you will remember them. You will need the information you learned from earlier parts for the later parts.

RIP v0.12 will contain all the corrections, if any, that people e-mailed me.
So e-mail me any mistakes that you find to legion@keg.zymurgy.org. I will
place these corrections on my webpage when I'm finished (hopefully).

Reaperbot (c) 1996 by Steven Polge
Norse_movetogoal (c) 1997 by Roscoe A. Sincero
QCBot Ranking (c) 1997 by Alan Kivlin



INTRODUCTION

This week's lesson focuses on merging the reaperbot source code with v1.06
of QuakeC. It is pretty easy actually but the number of steps involved is
quite large and it is so damn tedious. I hope I didn't forget anything.
The number of steps involved is huge.

You will, of course, need a copy of v1.06 of QuakeC.
Visit Mr. Pink`s webpage for a link to a copy of this source. The link
to his webpage can be found on the "staff" page of Inside3D. Mr. Pink is
finally an official member of the Inside3D staff.

Unlike my previous tutorial in which I used edit.com as my editor, I am now using QCAPE (Quake-C Advanced Programming Environment). You can get a copy of this new iDE from the Fears homepage. Their homepage is at http://www.dynanet.com/~scotts. Since I am using QCAPE, I am also including instructions on using QCAPE.


MISC NOTES

Dark_Skye was involved in a vehicular accident. Stop by the Scrap Heap Newspage and send him a get-well-soon e-mail.



COPYRIGHTS AND PERMISSIONS

Copyright laws forbid individuals and groups from releasing their modified reaperbot source code or progs.dat. Additionally, you can not use a modified reaperbot on a public server. However, I am unaware of any law(s) that forbids individuals from modifying their own personal copy of the reaperbot patch and using it for their own personal use. As long as you don't release it or make it publically available, you are in the clear. Moreover, I am unaware of any laws forbidding individuals or groups from providing information on how to modify existing copyrighted material to help those like you in making a better material (in this case, a better reaperbot).

If you did not read the reaperbot copyrights, you should read it now. Polge states that his modifications can NOT be used as a basis for other patches and/or modifications. This is the reason for not releasing my modified reaperbot progs.dat. Don't bother to ask, you won't get it. You won't get the modified source code either. Any and all e-mails on this issue will go unanswered. You will have to do all the work yourself.

I am assuming that you are NOT allowed to release an unmodified reaperbot source code, either.

Reaperbot © 1996 by Steven Polge -- individuals are not allowed to use his code in publically available or commercially available mods

Norse_movetogoal © 1997 by Roscoe A. Sincero -- individuals are free to include this code to your patches provided that Roscoe A. Sincero is credited for this code

QCBot Ranking © 1997 by Alan Kivlin -- individuals are free to include this code to your patches provided that Alan Kivlin is credited for this code


PREREQUISITES



   1) Reaperbot v0.81
      - ftp.cdrom.com/pub/quake/quakec/bots/reaper
   
   2) btsk23.zip
      -ftp.cdrom.com/pub/quake/quakec/bots
      - contains norse_movetogoal as well as Alan Kivlin's QCBot ranking
      code
      - demos are included showing off the new movement code.  E-mail me
      at legion@keg.zymurgy.org if you think the Zeusbot's movement is
      better.  Provide an example (eg. a demo) proving your case
  
   3) ProQCC v1.52 or later
      - ftp.cdrom.com/pub/quake/quakec/utils
   
   4) v1.06 of QuakeC
      - many compilers come with v1.06 of QuakeC.  find one.
      - ftp.cdrom.com/pub/quake/quakec/utils
       - many compilers come with v1.06 of QuakeC.  Find one or visit
      MrPink's website.

   5) knowledge of directory structure and installation of QuakeC patches
      - visit http://www.planetquake.com/qca for help in this area
  
   6) knowledge of QuakeC (not for the novice)
      - variable definitions are a must
      - modifying and creating functions
      - visit Inside3D, http://www.planetquake.com/iqc, for plenty of
        examples on this.  The tutorials are packed with examples of
        modifying functions.  Practically all the tutorials include at
        least one example of modifying existing or creating functions.
      - function prototypes are a must
      - understanding of IF block and IF-ELSE block
      - visit Inside 3D, http://www.planetquake.com/iqc, for examples
        on this.  Many of the tutorials contain IF blocks or IF-ELSE
        block statments for you to look at and understand.
   
   7) a text editor equipped with a cut-n-paste feature
  
   8) the ability to follow instructions

 

BEGIN GUIDE: Part II

Pre-Steps:
1) You should know where all the *.qc files are from the previous lessons. Remember from the previous lesson, you created func.qc. Do not forget that file. a) For non-QcAPE users, open func.qc using your editor. b) Find the line "// found in weapons.qc", underneath this line add float() CheckWaterlevel; c) save your changes to func.qc. 2) Familiarize yourself with your editor. In my case, I am using QcAPE. 3) For QcAPE users only, follow these simple instructions: a) get a copy of v1.06 of QuakeC. Copy all *.qc files into the QCC subdirectory of your QcAPE directory. I am assuming your copy of QcAPE doesn't have v1.06 of QuakeC. b) Under FILE, select "New Project", enter the name of the project and the correct path, then press OK. After pressing okay, at the bottom of your screen should be a "white area" broken up into three columns: Filename, Path, and Order. This area is blank since you just created a NEW project. This project is compilable. Why? Because the original QuakeC files are already part of the project. Under QuakeC, select "Compile Project". (You can accomplish this same task by pressing the icon on the toolbar that looks like a stack of paper with an arrow on it pointing downwards.) After creating the project, you should notice that a new subdirectory in your QCAPE directory has been created. You will find the progs.dat for the project in this directory. c) Under PROJECT, select "Add File". This same task can be accomplished by clicking on the "Add QC File" icon, the icon with the plus sign and white sheet of paper with one of the corners folded. It is the icon to the left of the icon with the red sheet and the plus sign. You can also press SHIFT-INS. You use this feature everytime you wish to add one of your own qc files into the project. In this case, we will be adding the files for the reaperbot. d) Another window will pop up. Find the directory where all the reaperbot files are stored from your previous lesson. e) Once you find the directory, simply select all the bot* files. Since all the bot* files are together, you can select all these files at the same time. Single-click the first bot file that you see and hold down the shift key and press the down arrow until all the bot* files have been selected. Then press "OPEN". (This is incorrectly named. The button should say "Add", rather than "open"). All the selected files should now be added to the project. f) After you added all the bot* files, add dmbot.qc and func.qc as well. Follow the same method as above to add dmbot.qc and func.qc. g) Under Project, select "Edit Compile List". This will open up progs.src. If you didn't notice from the previous lessons, the files are compiled in a SPECIFIC order. You will now reorder the list in progs.src. h) After selecting "Edit Compile List", a window pops up. You will see two columns: Files to Compile and Original Files. Ignore Original FIles, you don't need to modify anything in that column. Click on botit_th.qc in the Files to Compile. You will notice that the arrows on the left are now black. Click on the UP arrow. The botit_th.qc will move up in this list. Keep clicking the up arrow until botit_th.qc is right underneath defs.qc so botit_th.qc should be the second file on the list. Now click on func.qc. Again, the arrows on the left are now black. Click the up arrow until func.qc is underneath botit_th.qc. That is, func.qc should be the third file on the list. i) Click on botfight.qc. Following a similar method as in the previous step, place botfight.qc underneath oldone.qc. Click on botthink.qc and place that file underneath botfight.qc. Keep doing this until the order of the files are as follows: [other *.qc files ] fish.qc shalrath.qc enforcer.qc oldone.qc botfight.qc botthink.qc botspawn.qc botscore.qc botsignl.qc botmove.qc botnoise.qc botvis.qc botgoal.qc bot_ai.qc dmbot.qc botroute.qc botimp.qc j) Press OK when you are finished ordering the files. k) Remember that white area I mentioned earlier that was blank. Now you should see the list of files that you added in that white area. Find "func.qc" and double-click on that file to open it. A window, the edit window, should pop-up showing the contents of func.qc. l) In func.qc, search for the line "// found in weapons.qc". Underneath that line add this line of code: float() CheckWaterLevel; m) Under FILE, select "Save File" or click on DISK icon in the edit window.

Step 1

In the QcAPE, under the PROJECT menu, select "Add Original QC". You can also select the RED folder on the tool bar to accomplish the same task or press CTRL-INS. A selection of *.qc files should pop up. Select buttons.qc, then press "OPEN". The "OPEN" button is incorrectly named. It should be ADD. After you made your selection, the name "Buttons.qc" should now show up in the bottom window and the long list of *.qc files that you saw would have disappeared. a) Double-click on Buttons.qc. A window, the edit window, should pop up showing the contents of buttons.qc. b) In this window, search for the button.use function definition. AFTER the line "self.enemy = activator;" add the following: if (other.classname == "dmbot") bot_triggered (other); c) search for button_touch function definition. Change the line that says if (other.classname != "player") to if (other.classname != "player" && other.classname != "dmbot") Why change it to that? Well, only a player or a bot can touch the button. Suppose a monster touches a button. The monster is not a player AND it is not a bot. Thus, the button is not activated. Remember this change. You will perform this change often for many other functions. d) also in the button_touch function definition. ABOVE the line "button_fire();" add the following: if (other.classname == "dmbot") bot_triggered (other); e) search for button_killed function definition. ABOVE the line "button_fire();" add the following: if (other.classname == "dmbot") bot_triggered (other); Yes, you are seeing double. You will discover that the changes fall in a pattern. Didn't I mention that these changes are tedious? f) search for func_button definition. AFTER the line "self.use = button_use;" add the following: self.th_weight = buttonweight; g) save the changes made to button.qc by clicking on the DISK icon in the edit window. Close the file by clicking the "x" button on the upper right corner of the edit window. This will also close the edit window.

STEP 2

In the QcAPE, under the PROJECT menu, select "Add Original QC". You can also select the RED folder on the tool bar to accomplish the same task or press CTRL-INS. A selection of *.qc files should pop up. Select buttons.qc, then press "OPEN". The "OPEN" button is incorrectly named. It should be ADD. After you made your selection, the name "Client.qc" should now show up in the bottom window and the long list of *.qc files that you saw would have disappeared. Recognize this instruction. You should. a) Double-click on Client.qc. A window, the edit window, should pop up showing the contents of Client.qc. The changes you will be making to this file is extensive so pay attention. b) search for SetChangeParms function definition. Remove the following lines from this function: if (self.health <= 0) { SetNewParms (); return; } Replace the above lines with the following: local float offset, toffset, team1, nb, nt; local entity e; At the bottom of the function AFTER the line that says "parm9 = self.armortype * 100;" add the following: /* Begin code */ if (SKINSMODE) parm4 = parm4 + (self.skin * 512) + (SKINSMODE * 16384); offset = 1; toffset = 1; parm10 = 0; parm11 = 0; team1 = 100; nb = 0; nt = 0; e = find(world, classname, "dmbot"); while (e) { if (e.team == 99) { nb = nb + 1; if (nb < 6) { parm10 = parm10 + (((offset * e.skil) * 10) & (offset * 31)); offset = offset * 32; } } else { if (team1 == 100) team1 = e.team; if (e.team == team1) { nt = nt + 1; if (nt < 6) { parm11 = parm11 + (((toffset * e.skil) * 10) & (toffset * 31)); toffset = toffset * 32; } } } e = find (e,classname,"dmbot"); } // end while /* End code */ c) search for DecodeLevelParms function definition. Remove the following lines from the function. if (serverflags) { if (world.model == "maps/start.bsp") SetNewParms (); // take away all stuff on starting new episode } Replace the above lines with the following: /* Begin code */ local float skinno; if (parm4 > 511) { SKINSMODE = (parm4 & 49152) / 16384; skinno = (parm4 & 7680); parm4 = (parm4 & 511); skinChange (self, skinno); } /* End code */ d) search for execute_changelevel function definition. At the END of the function after the line that says "WriteByte (MSG_ALL, SVC_INTERMISSION);" add this: serverflags = (serverflags | INITLEVEL); e) search for ClientKill function definition. After the line that says "self.frags = self.frags - 2; // extra penalty", add this: countkill (self, self); f) search for PutClientInServer function definition. After the line that says "local entity spot;" add this: local float dedflags; After the line, "self.th_die = PlayerDie;", add this: self.th_cache - cacheenemy; After the line that says this: self.view_ofs = '0 0 22'; add the following code: /* Begin code */ if (!self.movetarget) NewCarriedPath (); setorigin (self.movetarget, self.origin); self.movetarget.movetarget = world; self.pathtype = NEVERTARGET; if (!self.pather) NUMPATHERS = NUMPATHERS + 1; self.pather = TRUE; /* End code*/ At the bottom of this function definition, after the line: spawn_tdeath (self.origin, self); add the following code: /* Begin code */ dedflags = cvar("temp1"); if (dedflags & DEDICATED) { serverflags = (dedflags | INITLEVEL); cvar_set ("temp1", "0"); } if (serverflags & INITLEVEL) initBotLevel (); /* End code */ g) search for PlayerPostThink function definition. AFTER the lines that says: if (self.deadflag) return; Add the following code: /* Begin code */ if (CheckDropPath()) DropBotPath (); // The following sets the flags for the waypoints called // "BotPath". These "BotPath" have the same .flags as the // player. This means that "BotPath" waypoints can pick // up weapons. Thus, this is the cause for the disappearing // weapons bug that the reaperbot is famous for. // When we modify items.qc, we will remove this bug. // On a related note, older versions of QuakeC also have // this disappearing weapons bug. self.movetarget.flags = self.flags; setorigin (self.movetarget, self.origin); /* End code */ Now some of you may have noticed that the reaperbot also spawns another entity called BotTarget. As MrElusive pointed out to me, BotTarget does not take the same .flags values as a player or bot. So the BotTarget "beacons" can't pick up weapons. h) Somewhere in this client.qc file, create the following function definition. (For instance, AFTER the PlayerPostThink function, create the following function definition). /* Begin code */ void() printIntro = { local float num, skinno; if (teamplay) { if (parm4 > 511) { SKINSMODE = (parm4 & 49152) / 16384; skinno = (parm4 & 7680); parm4 = (parm4 & 511); skinChange (self, skinno); } num = (serverflags & TEAMBOTS) / 256; if (num > 0) { addTeamBots (self, num); } centerprint (self, "Server running team Reaper bots v0.8"); } else { centerprint (self, "Server running Reaper bots v0.8"); } }; /* End code */ It is hoped that you realize that you do NOT create a new function definition inside another one. I know many of you will try to create the printIntro function inside another function like PlayerPostThink or PutClientInServer. Then many of you will e-mail me or others saying that there is a "bug" in the tutorial. Stupid and grossly ignorant application of knowledge such as this will be ignored. You are creating a NEW function called printIntro, you are not adding code to an existing function. At this point, you now have a compilable reaperbot source code that uses v1.06 of QuakeC. So under QuakeC, select "Compile Project". A window should pop up telling you what QCAPE is compiling and when it is finished, the results. If you made typos, and other mistakes, the compiler will find it. Keep in mind that you are no where near finished. If you try to execute Quake with this patch, it may crash with some sort of infinite loop error. It is a good idea to periodically compile the project to check for typos and such. That way when you are finish making the changes, you won't have a whole lot of typos to correct in the end--assuming any errors exists. i) search for ClientConnect function definition. After the lines: bprint (self.netname); bprint (" entered the game\n"); add the following lines of code: self.impulse = 204; // prints the introduction initscore (); j) search for ClientDisconnect function definition. ABOVE the line that says set_suicide_frame (); add this following line: self.classname = "nobody"; k) search for ClientObituary function definition. Now pay close attention to these instructions. You will need to remember them when we add bots to the TAB scoring system. Change the line that says: if (targ.classname == "player") to if (targ.classname == "player" || targ.classname == "dmbot") Now look for the line that says: attacker.owner.frags = attacker.owner.frags + 1; AFTER that line add the following lines of code: /* Begin code*/ if (attacker.owner.classname == "dmbot") TELEFRAGFLAG = TRUE; countkill (attacker.owner, targ); /* End code */ The above three lines of code (or four lines if include the blank line) should be in the "teledeath" IF block. Go back previous lesson if you don't know what an IF block or a block of code is. I call it the "teledeath" IF block because the first line for this block of code checks to see if the attacker.classname is equal to "teledeath". In the IF block following it (the "teledeath2" IF block), AFTER the line targ.frags = targ.frags - 1; Add this line of code: countkill (targ, targ); Now Change the line that says: if (attacker.classname == "player") to if (attacker.classname == "player" || attacker.classname == "dmbot") AFTER the line that says: attacker.frags = attacker.frags - 1; Add this line of code: countkill (attacker, attacker); Now look for the other line that says: attacker.frags = attacker.frags - 1; Add this line of code AFTER that line: countkill (attacker, attacker); AFTER the line that says: attacker.frags = attacker.frags + 1; Add this line of code: countkill (attacker, targ); AFTER the line that says: targ.frags = targ.frags - 1; Add this line of code: countkill (targ, targ); l) Save the changes made to client.qc by clicking on the DISK icon in the edit window. Close the file by clicking the "x" button on the upper right corner of the edit window. This will also close the edit window.

STEP 3

In the QcAPE, under the PROJECT menu, select "Add Original QC". You can also select the RED folder on the tool bar to accomplish the same task or press CTRL-INS. A selection of *.qc files should pop up. Select buttons.qc, then press "OPEN". The "OPEN" button is incorrectly named. It should be ADD. After you made your selection, the name "Combat.qc" should now show up in the bottom window and the long list of *.qc files that you saw would have disappeared. a) Double-click on Combat.qc. A window, the edit window, should pop up showing the contents of combat.qc. It is hoped that you recognize a pattern here. I will go over it one more time with the next file we will modify. After that I will assume you will know how to open a file using QcAPE. Of course, I am already assuming you know how to do these things with your editor if you don't use QcAPE. b) In this edit window which shows the contents of combat.qc, search for T_Damage function definition. After the lines that says: // react to the damage oldself = self; self = targ; Add this following lines of code: /* Begin code */ if (self.classname == "dmbot") { if (attacker) { if (inflictor) { if (attacker != self) { if (self.enemy) secondEnemy (attacker); else { self.enemy = attacker; BotFoundTarget (); } } } } } /* End code */ Change the line that says: if ( (self.flags & FL_MONSTER) && attacker != world) to else if ( (self.flags & FL_MONSTER) && attacker != world) Yes, you are adding the keyword "else" to the beginning of the line. c) save the changes made to combat.qc by clicking on the DISK icon in the edit window. Close the file by clicking the "x" button on the upper right corner of the edit window. This will also close the edit window. I hope you recognize a pattern here. I will go over this one more time but after that, I will assume you know how to close and save files in QcAPE.

STEP 4

In the QcAPE, under the PROJECT menu, select "Add Original QC". You can also select the RED folder on the tool bar to accomplish the same task or press CTRL-INS. A selection of *.qc files should pop up. Select buttons.qc, then press "OPEN". The "OPEN" button is incorrectly named. It should be ADD. After you made your selection, the name "Doors.qc" should now show up in the bottom window and the long list of *.qc files that you saw would have disappeared. a) Double-click on Combat.qc. A window, the edit window, should pop up showing the contents of combat.qc. It is hoped that you recognize a pattern here. At this point on, I will assume you know how to add files, open files and edit files. b) search for the door_use function definition. AFTER the line that says: local entity oself; Add this following lines of code: /* Begin code */ signalnoise (other, self); if (other.classname == "dmbot") bot_touched (); /* End code */ c) search for door_touch function definition. Change the line that says if (other.classname != "player") to if (other.classname != "player" && other.classname != "dmbot") Why change it to that? Well, only a player or a bot can touch the button. Suppose a monster touches a button. The monster is not a player AND it is not a bot. Thus, the button is not activated. Remember this change. You will perform this change often for many other functions. Doesn't this step look familiar? It should. Remember the buttons.qc file. Did you forget that already? In this SAME function definition called door_touch, you will find several centerprint function calls that look like this: centerprint (other, self.owner.message); or something similar. ABOVE EACH AND EVERYONE of these function calls, add this line of code: If (other.classname == "player") Why? Because you can only print stuff to real players, not to monsters or bots. Not only is this true for centerprint function calls but this is also true for sprint function calls and stuffcmd function calls. In this function definition, you will only need to make the change just seven times since there are seven centerprint function calls. There are no sprint or stuffcmd functions calls to worry about in this function. You will, though, in items.qc file. In this same function, look for this following lines of code: /* Begin code */ // key door stuff if (!self.items) return; /* End code */ ABOVE these lines, add the following: /* Begin code */ if (other.enemy.classname == "dmbot") enemy_touched (); if (other.classname == "dmbot") bot_touched (); /* End code */ d) search for fd_secret_move3 function definition. AFTER the line that says: sound (self, CHAN_VOICE, self.noise3, 1, ATTN_NORM); Add the following: if (self.lastbot.goalentity.goalentity == self) self.lastbot.search_time = time; e) search for secret_touch function definition. The steps for this function should be burned into your brain. You will be following these steps over and over and over and over and over again. Change the line that says: if (other.classname != "player") to if (other.classname != "player" && other.classname != "dmbot") Look for all function calls involving centerprint, sprint, and stuffcmds. ABOVE EACH AND EVERYONE of these function calls, add this following line: if (other.classname == "player") I hope you realize that you followed these same steps for door_touch function defintion as well. f) search for func_door_secret function definition. AFTER the line that says: self.touch = secret_touch; Add this following line of code: self.th_weight = doorweight; g) save the changes made to doors.qc by clicking on the DISK icon in the edit window. Close the file by clicking the "x" button on the upper right corner of the edit window. This will also close the edit window. I hope you recognize a pattern here. From this point on, I will assume you know how to save the file and close the edit window.

STEP 5

Modifying items.qc If you thought that modifying client.qc took a long time, then this file will be a huge burden on your life. a) open items.qc. Remember to ADD items.qc into the project so you can edit the file. You can not modify the original qc files so you must add a copy of it first before opening it. That is why I had you add the Buttons.qc, Client.qc, Combat.qc and Doors.qc FIRST before you modify the files. b) search for item_health function definition. AFTER the line that says: self.touch = health_touch; Add this following line of code: self.th_weight = healthweight; AFTER the line that says: self.healtype = 2; Add these following lines of code: self.th_cache = cachepowerup; self.th_update = updatepowerup; c) search for health_touch function definition. Change the line that says: if (other.classname != "player") to if (other.classname != "player" && other.classname != "dmbot") Look for this following lines of code: /* Begin code */ sprint(other, "You receive "); s = ftos(self.healamount); sprint(other, s); sprint(other, " health\n"); /* End code */ Noticed that the sprint function calls above are grouped together. So instead of creating a separate IF block for each and every sprint function call, you can group them together into one IF block. So remove the above code and replace it with the following: /* Begin code */ if (other.classname == "player") { sprint(other, "You receive "); s = ftos(self.healamount); sprint(other, s); sprint(other, " health\n"); } /* End code*/ Now look for the line that says: stuffcmd (other, "bf\n"); ABOVE this line, add the following: if (other.classname == "player") Doesn't the above instructions look familiar? It should. From now on, I will call the above instructions as "DO THE USUAL". The point to remember is to look for all function calls involving centerprint, sprint, and stuffcmds. ABOVE EACH AND EVERYONE of these function calls, add this following line: if (other.classname == "player") And, of course, don't forget to change that line of code to: if (other.classname != "player" && other.classname != "dmbot") In this function definition called health_touch, look for the following block of code: /* Begin code */ else { if (!T_Heal(other, self.healamount, 0)) return; } /* End code */ AFTER this block of code, add the following: if (other.classname == "dmbot") bot_toucheditem (); AFTER the lines that says: self.think = item_megahealth_rot; self.owner = other; Add these lines of code: if (other.movetarget.movetarget) cacheRoute (other.movetarget, other.movetarget.movetarget, FALSE, self); d) search for armor_touch function definition. DO THE USUAL. Recall what an IF block is. To make sure you understand what I mean by an IF block, I will go over it one more time. Inside the IF block that checks for "item_armor2", you have the following code: /* Begin code */ if (self.classname == "item_armor2") { type = 0.6; value = 150; bit = IT_ARMOR2; } /* End code */ The above is a collection or a BLOCK of code that are executed together when the conditions in the IF line has been satisfied (ie. it is true). I call the above IF block the "item_armor2" IF block. Now change the "item_armor2" IF block to the following: /* Begin code */ if (self.classname == "item_armor2") { type = 0.6; value = 150; bit = IT_ARMOR2; if (other.movetarget.movetarget) cacheRoute (other.movetarget, other.movetarget.movetarget, FALSE, self); } /* End code */ Make the same change for the "item_armorInv" IF block. Now you may be wondering, what about "item_armor1" (the green armor)? Steven Polge didn't modify this IF block. So leave that IF block alone. AFTER the line that says: self.think = SUB_regen; Add these following lines of code: if (other.classname == "dmbot") bot_toucheditem (); Doesn't the above code look familiar? It should. If you haven't noticed it by now, you are following a pattern. You will be following this same pattern again for the weapons and ammo. e) search for item_armor1 function definition. ABOVE the line that says: StartItem (); Add these following lines of code: self.armorvalue = 100; self.th_weight = armorweight; f) search for item_armor2 function definition. ABOVE the line that says: StartItem (); Add these following lines of code: self.armorvalue = 150; self.th_weight = armorweight; self.th_cache = cachearmor; self.th_update = updatearmor; g) search for item_armorInv function definition. ABOVE the line that says: StartItem (); Add these following lines of code: self.armorvalue = 150; self.th_weight = armorweight; self.th_cache = cachearmor; self.th_update = updatearmor; h) search for weapon_touch function definition. Check for sprint, centerprint, stuffcmd functions as usual. AFTER the line that says: local float leave; Add these following lines: if (other.classname == "peeper") return; // observers can't pick up weapons Change the line that says: if (!(other.flags & FL_CLIENT)) to if (other.classname != "player" && other.classname != "dmbot") The check for the FL_CLIENT is the cause for the disappearing weapons bug that the reaperbot is famous for. The waypoint called BotPath can have the the FL_CLIENT flag so it will pass the check. Thus, this waypoint can pick up weapons. By making this change, only bots and players can pick up weapons. ABOVE the line that says: best = W_BestWeapon (); Add this line of code: if (self.classname == "player") Now examine the entire weapon_touch function definition more carefully. You should see several IF blocks that check for various types of weapons. The modifications to these IF blocks are identical to the modifications you made to the "item_armor2" and "item_armorInv" IF blocks discussed earlier. So for example, the IF block for the rocket launcher is the following: /* Begin code */ else if (self.classname == "weapon_rocketlauncher") { if (leave && (other.items & IT_ROCKET_LAUNCHER) ) return; hadammo = other.ammo_rockets; new = IT_ROCKET_LAUNCHER; other.ammo_rockets = other.ammo_rockets + 5; } /* End code */ You will change this IF block to the following: /* Begin code */ else if (self.classname == "weapon_rocketlauncher") { if (leave && (other.items & IT_ROCKET_LAUNCHER) ) return; hadammo = other.ammo_rockets; new = IT_ROCKET_LAUNCHER; other.ammo_rockets = other.ammo_rockets + 5; if (other.movetarget.movetarget) cacheRoute (other.movetarget, other.movetarget.movetarget, FALSE, self); } /* End code */ You will make this SAME change to the super-nailgun, grenade launcher, and lightning gun IF blocks. Now you may be wondering about the nailgun and super shotgun? Leave those IF blocks alone; Steven Polge didn't modify those IF blocks. /* NEW ADDITION TO PART II */ a) search for weapon_supershotgun function definition. ABOVE the line that says: StartItem (); Add this following line of code: self.th_weight = ssgweight; b) search for weapon_nailgun function definition. ABOVE the line that says: StartItem (); Add this following line of code: self.th_weight = nailgunweight; /* END NEW ADDITION TO PART II */ i) search for weapon_supernailgun function definition. ABOVE the line that says: StartItem (); Add this following line of code: self.th_weight = supernailweight; self.th_cache = cachenail; self.th_update = updatenail; j) search for weapon_grenadelauncher function definition. ABOVE the line that says: StartItem (); Add this following line of code: self.th_weight = glauncherweight; self.th_cache = cachegrenade; self.th_update = updategrenade; k) search for weapon_rocketlauncher function definition. ABOVE the line that says: StartItem (); Add this following line of code: self.th_weight = rlauncherweight; self.th_cache = cacherocket; self.th_update = updaterocket; m) search for weapon_lightning function definition. ABOVE the line that says: StartItem (); Add this following line of code: self.th_weight = lightningweight; self.th_cache = cachelightning; self.th_update = updatelightning; n) search for ammo_touch function definition. DO THE USUAL. ABOVE the line that says: bound_other_ammo (); Add these lines of code: if (other.classname == "dmbot") bot_toucheditem (); o) search for item_shells function definition. ABOVE the line that says: StartItem (); Add this following line of code: self.th_weight = shellweight; p) search for item_spikes function definition. ABOVE the line that says: StartItem (); Add this following line of code: self.th_weight = nailweight; q) search for item_rockets function definition. ABOVE the line that says: StartItem (); Add this following line of code: self.th_weight = rocketweight; r) search for item_rockets function definition. ABOVE the line that says: StartItem (); Add this following line of code: self.th_weight = cellweight; s) search for key_touch function definition. DO THE USUAL. t) search for powerup_touch function definition. DO THE USUAL. Recall the changes you made in the rocket launcher IF block discussed above in the weapon_touch function definition. Well, you will have to make the same changes for the "item_artifact_invulnerability", "invisibility" and the "super_damage" IF blocks. Leave the "envirosuit" (the rad suit) IF block alone. u) search for item_artifact_invulnerability function definition. ABOVE the line that says: StartItem (); Add these following lines of code: self.th_weight = artifactweight; self.th_cache = cachepowerup; self.th_update = updatepowerup; Make the SAME addition for item_aritifact_invisibility and item_artifact_super_damage function definitions. v) search for item_artifact_envirosuit function definition. ABOVE the line that says: StartItem (); Add these following lines of code: self.th_weight = artifactweight; w) search for BackpackTouch function definition. DO THE USUAL. ABOVE the line that says: if (other.health <=0) Add these lines of code: if (other.classname == "dmbot") bot_toucheditem (); x) search for DropBackpack function definition. AFTER the line that says: item.touch = BackpackTouch; Add these lines of code: item.classname == "Backpack"; item.th_weight = backpackweight; y) save and close file. You have now completed the modifications for items.qc. Before you continue, you should check over all the changes, not only in this file but for the other files as well. In particular, make sure you spell "player" and "dmbot" correctly, checked for stuffcmd function calls, sprint function calls, and all the rest. Common errors I know many of you will have is the following: a) stuffcmd errors b) sprint to non-client error messages c) bot is unable to pick up certain items. d) "not a name" errors, e) expected ";" but found if (and other variations). All these errors are the direct result of not following the instructions properly and making typos. Take a deep breath. You are now more than half-way finished this lesson

STEP 7

Modifying triggers.qc. a) search for multi_trigger function definition. Change the line that says: if (self.enemy.classname != "player") to if (self.enemy.classname != "player" && self.enemy.classname != "dmbot") AFTER the line that says: activator = self.enemy; Add these lines: if (activator.classname == "dmbot") bot_triggered (activator); b) search for multi_touch function definition. Change the line that says: if (other.classname != "player") to if (other.classname != "player" && other.classname != "dmbot") AFTER the "return;" line for the above IF block (ie. above the line that says "// if the trigger ...", add this following code: /* Begin code */ if (other.classname == "dmbot") { self.enemy = other; bot_touched (); multi_trigger (); return; } /* End code */ c) search for trigger_multiple function definition. AFTER the line that says: self.use = multi_use; Add these lines: self.th_weight = triggerweight; self.istrigger = TRUE; d) search for the spawn_tfog function definition. AFTER the line that says: s.think = play_teleport; Add this line of code: signalnoise (other, s); e) search for tdeath_touch function definition. Change the line that says: if (other.classname == "player") to if (other.classname == "player" || other.classname == "dmbot") Change the line that says: if (self.owner.classname != "player") to if (self.owner.classname != "player" && self.owner.classname != "dmbot") ABOVE the line that says: if (other.health) Insert this lines of code: if (other.classname == "peeper") other.takedamage = DAMAGE_AIM; f) search for teleport_touch function definition. Change the line that says: if (other.classname != "player") to if (other.classname != "player" && other.classname != "dmbot") AFTER the line that says: spawn_tdeath (t.origin, other); Add these lines: if (other.enemy.classname == "dmbot") enemy_touched (); AFTER the line that says: other.angles = t.mangle; Add these lines: /* Begin code */ if (other.movetarget.movetarget) { addTarget (other.movetarget.movetarget, self); other.movetarget.movetarget = self; } if (other.classname == "dmbot") bot_touched (); /* End code */ Change the line that says: if (other.classname == "player") to if (other.classname == "player" || other.classname == "peeper" || other.classname == "dmbot") g) search for trigger_teleprot function definition. AFTER the line that says: self.touch = teleport_touch; Add these lines: self.istrigger = TRUE; self.pathtype = TELEPORT; h) search for trigger_setskill function definition. AFTER the line that says: self.touch = trigger_skill_touch; Add these lines: self.istrigger = TRUE; self.pathtype = TELEPORT; i) search for trigger_onlyregistered_touch function definition. Change the line that says: if (other.classname != "player") to if (other.classname != "player" && other.classname != "dmbot") Change the line that says: if (self.message != "") to if (self.message != "" && other.classname != "dmbot") j) search for trigger_onlyregistered function definition. AFTER the line that says: self.touch = trigger_onlyregistered_touch; Add these lines: self.istrigger = TRUE; self.th_weight = triggerweight; k) search for trigger_push_touch function definition. Look for this IF block: /* Begin code */ else if (other.health > 0) { other.velocity = self.speed * self.movedir * 10; if (other.classname == "player") { if (other.fly_sound < time) { other.fly_sound = time + 1.5; sound (other, CHAN_AUTO, "ambience/windfly.wav", 1, ATTN_NORM); } } } /* End code */ Replace the above IF block with the following: /* Begin code */ else if (other.health > 0) { other.velocity = self.speed * self.movedir * 10; if (other.classname == "player") { if (other.fly_sound < time) { other.fly_sound = time + 1.5; sound (other, CHAN_AUTO, "ambience/windfly.wav", 1, ATTN_NORM); } } else { if (other.classname == "dmbot") { if ( (self.movedir_x < 0.1) && (self.movedir_y < 0.1) ) { other.velocity_x = other.velocity_x + (80 * random()) - 40; other.velocity_y = other.velocity_y + (80 * random()) - 40; } if (other.fly_sound < time) { other.fly_sound = time + 1.5; sound (other, CHAN_AUTO, "ambience/windfly.wav", 1, ATTN_NORM); } } } if (other.enemy.classname == "dmbot") enemy_touched (); } /* End code */ l) search for trigger_push function definition. AFTER the line that says: self.touch = trigger_push_touch; Add these lines: self.istrigger = TRUE; self.th_weight = trigpushweight; m) save and close triggers.qc.

STEP 8

Modifying weapons.qc. a) search for W_FireAxe function definition. AFTER the line that says: local vector org; Add this line: local vector dir; AFTER the line that says: source = self.origin + '0 0 16'; Add these lines: dir = v_forward; if (self.classname == "dmbot") dir = botaim (); In the rest of this function, you will see two instances of "v_forward". Change "v_forward" to "dir" since we are now using the vector dir, instead of v_forward. b) search for W_FireShotun function definition. AFTER the line that says: dir = aim (self, 100000); Add these lines: if (self.classname == "dmbot") dir = botaim (); c) search for W_FireSuperShotun function definition. AFTER the line that says: dir = aim (self, 100000); Add these lines: if (self.classname == "dmbot") dir = botaim (); d) search for W_FireRocket function definition. AFTER the line that says: local entity missile, mpuff; Add this line: local vector bfwd; Search for the line that says: missile.velocity = aim (self, 1000); Delete this line and replace it with this: /* Begin code */ if (self.classname == "dmbot") { missile.velocity = botaim (); bfwd = botaim (); bfwd_z = 0; } else missile.velocity = aim(self, 1000); /* End code */ Search for the line that says: setorigin (missile, self.origin + v_forward * 8 + '0 0 16'); Delete this line and replace it with the following: /* Begin code */ if (self.classname == "dmbot") setorigin (missile, self.origin + bfwd*8 + '0 0 16'); else setorigin (missile, self.origin + v_forward*8 + '0 0 16'); /* End code */ e) search for LightningDamage function definition. Change the line that says: if (self.classname == "player") to if (self.classname == "player" || self.classname == "dmbot") Change the line that says: if (other.classname == "player" || other.classname == "dmbot") f) search for W_FireLightning function definition. AFTER the line that says: local float cells; Add these lines of code: local float wlevel; local vector dir; Search for the line that says: if (self.waterlevel > 1) Delete this line and replace it with the following: /* Begin code */ if (self.classname == "dmbot") { wlevel = CheckWaterLevel (); if (wlevel > 1) { self.ammo_cells = 0; W_SetCurrentAmmo (); T_RadiusDamage (self, self, 35*cells, world); return; } } else if (self.waterlevel > 1) /* End code */ AFTER the line that says: org = self.origin + '0 0 16'; Add these lines: dir = v_forward; if (self.classname == "dmbot") dir = botaim (); In the rest of this function, you will see two instances of "v_forward". Change "v_forward" to "dir" since we are now using the vector dir, instead of v_forward. g) search for W_FireGrenade function definition. Look for this code: /* Begin code */ else { missile.velocity = aim(self, 10000); missile.velocity = missile.velocity * 600; missile.velocity_z = 200; } /* End code */ Delete the above code and replace with the following: /* Begin code */ else { if (self.classname == "dmbot") { missile.velocity = botaim (); missile.velocity = missile.velocity * 600; missile.velocity_z = missile.velocity_z + 200; } else { missile.velocity = aim(self, 10000); missile.velocity = missile.velocity * 600; missile.velocity_z = 200; } } /* End code */ h) search for W_FireSuperSpikes function definition. AFTER the line that says: dir = aim (self, 1000); Add these lines: if (self.classname == "dmbot") dir = botaim (); i) search for W_FireSpikes function definition. AFTER the line that says: W_SetCurrentAmmo (); Add these lines: if (self.classname == "dmbot") self.think = self.th_run; AFTER the line that says: dir = aim (self, 1000); Add these lines: if (self.classname == "dmbot") dir = botaim (); j) search for W_SetCurrentAmmo function definition. ABOVE the line that says: player_run (); Add this: if (self.classname == "player") k) search for ImpulseCommands function definition. ABOVE the line that says: self.impulse = 0; Add this line: botImpulseCommand (); l) search for W_WeaponFrame function definition. AFTER the line that says: ImpulseCommands (); Add these lines: if (self.classname == "peeper") return; m) save and close weapons.qc.

STEP 9

Modifying world.qc. a) search for worldspawn function definition. AFTER the line that says: precache_model ("progs/v_light.mdl"); Add this line: setBotGravity (); b) search for CopyToBodyQue function definition. AFTER the line that says: bodyque_head.flags = 0; Add these lines: if (SKINSMODE) bodyque_head.skin = ent.skin; c) save and close world.qc. You now have compilable and workable reaperbot that has been merged with v1.06 of QuakeC. When you run Quake with this patch, everything seems to work fine for a little while. Then you may notice some strange bugs. On my machine, the grenade launcher and rocket launcher no longer works properly. In particular, the grenade launcher no longer throws grenades even though you still hear the sound of the grenades being launched. The rocket immediately explodes when you fire the rocket launcher. The reapers, of course, suffer from the same problem. You may be wondering what the hell is going on? Well, the problems comes from the use of global variables. To make a long story short, Quake doesn't like it when global variables that were given a specific value when declared has been changed. Variables that have been given a value when it was declared are called CONSTANTS. Quake doesn't like it when you mess with the constants. This wasn't much of a problem with older versions of QuakeC but this has become a problem with v1.06. Of course, the knowledgeable people will tell you that the real cause of the problem lies in how Quake stores its global variables. They might say such things like Quake stores the global variables in the same stack and give you other "who cares" information. STEP 10 Modifying botit_th.qc. You will see a lot of global variables being declared such as: float NUMGIBS = 0.000; float NUMPATHERS = 0.000; float SKINSMODE = 0.000; The above variables are global variables that have been declared with an initial value. In this case, the initial value is zero. Quake doesn't like it when you change these global variables. So what should you do to correct this stupid problem? The answer is simple. Let us take the above three global variables as an example. Simply change the variable declarations to this: float NUMGIBS; float NUMPATHERS; float SKINSMODE; It is that simple. Don't specify a value. When variables are declared in Quake, the initial value is zero. So you don't have to worry about its initial value. You will need to do this for *ALL* the global variables in which the initial value was set to zero. Do the same thing for the vector REBOUNDSPOT. That is, change it to "vector REBOUNDSPOT;". Now what about the other global variables with an initial value not equal to zero? Guess what? If the values for these variables change, too, then you will have the same problem as above. Fortunately for us, the values for these variables don't change so you don't have to worry about them. So this means that if you specify a value for global variables, DO NOT CHANGE its value. It has to be treated like a real constant or else you will have Quake behaving in an unpredictable manner. If you must have global variables with values that changes from time to time AND it must have some initial value, then you must declare these variables WITHOUT specifying any values. Then create a new function that sets the initial value for these variables. For instance, you would create a function like this: void() setglobalvar = { VERBOSEBOT = 0; BOTSPEED = 320; NUMBOTS = 0; GRAVITY = 800; MAXJUMP = 0; .... }; Then you execute this function within the function called worldspawn found in world.qc. That is, you insert the line: setglobalvar (); somewhere in the worldspawn function definition. Executing this function will set the initial values for all the global variables that must have an initial value. It is important to note that you can only change the values IN A FUNCTION. That is why you need a function like setglobalvar.

STEP 11

COMPILE the reaperbot. You are finished. Miscellaneous Information. Many thanks goes to Thomas Zajic for pointing out another problem with decompilers like ProQCC. Suppose your patch contains the following line of code: a = b = b - 1; When you decompile this patch, the code will look like the following: b = b - 1; a = b - 1; This is obviously not correct. In weapons.qc, you will see lines like: self.currentammo = self.ammo_cells = self.ammo_cells - 1; This is in the original code. But when you decompile it, it will look like this: self.ammo_cells = self.ammo_cells - TRUE; self.currentammo = self.currentammo - TRUE; The value for TRUE is one. Decompilers have a habit of sticking in the variable names instead of the numbers. The only way around this is to correct it. :-)