Inside3D tutorials.
Created By: Roscoe A. Sincero
eMail: legion@softhome.net
Difficulty Scale: Hard


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

PART I


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@softhome.net. 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

I am changing the order in which the lessons are going to be taught. This week's lesson focuses on adding multiplayer coop support, Alan Kivlin's bot ranking code, a modified Alan Kivlin thud reduction code and player-like momentum effects from weapon hits. As I did with last week's lesson, I will be using QcAPE as my QuakeC editor. I am assuming you are familiar with whatever editor you chose to use even if it is notepad or edit.com or QcAPE. You can get a copy of QcAPE from The Fear's homepage at this following URL: http://www.dynanet.com/~scotts.



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@softhome.net 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


CORRECTION PART I

Thanks to Sam Kenny, I have discovered that I made a mistake in one of my explanations in Part I. Recall that I said that when you decompile a patch like the reaperbot, there can be missing lines of code. Well, that part is correct but then I said that the compiler doesn't understand bad code so it leaves it blank. It is the second part of the explanation that is wrong. The compiler does compile the line, it is the DECOMPILER that does not understand it and leaves it blank. So for instance, lines like this found in misc.qc if (!self.speed) self.speed == 1000; are actually compiled into the progs.dat. But when a decompiler like ProQCC sees this code in the progs.dat, it gets "confused" and doesn't bother with it any further because the next line it sees is the end of the function or end of the IF block. The result is that the decompiler does not print out the code--it leaves it blank. Thus, you have missing lines of code. The line itself, of course, is not actually executed by Quake since it is garbage. However, it is still part of the progs.dat and decompilers like ProQCC leaves it blank in the decompiled source. I realize that when some people decompile a patch and find the following lines in misc.qc: if ( !self.speed ) { } they believe, for some strange reason, that there are no missing lines of code. Their explanation, of course, is what I call the stupidly obvious. Their explanation is that the line "self.speed == 1000;" is garbage so it isn't executed by Quake. That is obvious. What isn't obvious is why they insist that there are no missing lines of code in the above example? I see nothing in between the brackets. Do you see anything between the brackets? I knew that decompilers leave out code but the experts said otherwise. I thought these experts were right; hence, my wrong explanation in Part I. Decompilers are being developed as we speak that are able to print out the missing lines of code. I have seen and tested a prototype decompiler. It does indeed decompile lines of code like the one above. Currently, it is only able to decompile the line if that code is the very last line in an IF block or the very last line of the function. The other mistakes (if any) in that function or IF block or WHILE block are not decompiled. The prototype is also able to correctly decompile correctly lines of code like the following: self.currentammo = self.ammo_cells = self.ammo_cells - 1; When the bugs are ironed out, don't be surprised to see a new decompiler at ftp.cdrom.com. On a related note, a descrambler is also in the works. I haven't seen the descrambler but I have seen the results. The eliminatorbot v2.0 has been successfully descrambled and decompiled. If you haven't read my review on the eliminatorbot or are still confused, the eliminatorbot v2.0 IS the reaperbot with few additions like realistic momentum effects from weapon hits and Alan Kivling's QCBot ranking code.

CORRECTION PART II

In Part II of the tutorial, I forgot a step in the W_FireLightning function definition. In the IF block that checks for self.ammo_cells < 1, add the following lines of code BEFORE the return statement: if (self.classname == "dmbot") self.think = self.th_run; If you see a problem with the reaperbot stop moving after using the lightning gun, then the above addition should remedy that problem. Many thanks goes to TheJoker for e-mailing me that there was something wrong with the reapers while using the lightning gun. Mr. Pink´s note: The RIP II tutorial has been updated correcting that error.

BEGIN GUIDE: Part III

STEP 1

Modifying func.qc. You will be adding a few new functions. These functions will be used for coop support and thud reduction. a) Using a method called cut-n-paste, add the following functions to the bottom of func.qc. /* Begin code */ // New functions entity() find_bot = { local float dist; local float best_dist; local entity head; local entity selected; selected = world; best_dist = 9999; head = find (world, classname, "dmbot"); while (head) { dist = vlen (self.origin - head.origin); if (infront(head) && visible(head) && (dist < best_dist) ) { best_dist = dist; selected = head; } head = find (head, classname, "dmbot"); } return selected; }; entity() find_monster = { local float dist; local float best_dist; local entity head; local entity selected; selected = world; best_dist = 9999; head = findradius (self.origin, 600); while (head) { dist = vlen (self.origin - head.origin); if (infront(head) && visible(head) && (dist < best_dist) && (head.flags & FL_MONSTER) ) { best_dist = dist; selected = head; } head = head.chain; } return selected; }; void() thud_gone = { local float tol; tol = -80; tol = tol * cvar("sv_gravity")/800; if (self.flags & FL_ONGROUND) { if (self.velocity_z < tol) { self.flags = self.flags - (self.flags & FL_ONGROUND); self.velocity_z = tol; } } }; void(entity me) set_bot_suicide_frame = { if (me.model != "progs/player.mdl") return; // already gibbed me.frame = 60; me.solid = SOLID_NOT; me.movetype = MOVETYPE_TOSS; me.deadflag = DEAD_DEAD; me.nextthink = -1; }; /* End code */ b) save changes.

STEP 2

Modifiying world.qc. a) search for the worldspawn function definition. Add this line of code at the VERY BEGINNING of the function: clientInitMaxClients (); b) look for the line that says: entity bodyque_head; The above line should above the bodyque function definition. Delete that line. c) save changes.

STEP 3

Modifying botit_th.qc. a) Add the following lines of code at the top of the file: entity bodyque_head; .float LoggedIn; b) save changes.

STEP 4

Modifying bot_ai.qc. a) search for BotFoundTarget function definition. Change the line that says: if ( ((self.enemy.classname != "player") && (self.enemy.classname != "dmbot")) ) { to if ( ((self.enemy.classname != "player") && (self.enemy.classname != "dmbot")) && !(self.enemy.flags & FL_MONSTER)) { b) search for BotfindBot function definition. Look for the following IF block: /* Begin code */ if ( (bots.health > FALSE) ){ self.enemy = bots; return (BotFoundTarget () ); } /* End code */ Replace the above code with the following: /* Begin code */ if ( (bots.health > 0) ) { if (!coop) { self.enemy = bots; return (BotFoundTarget () ); } } /* End code */ c) search for the BotFindTarget function definition. Delete the lines that says the following: /* Begin code */ client = checkclient (); if ( !client ) { return ( BotfindBot () ); } /* End code */ Replace the above lines of code with the following: /* Begin code */ if (coop) client = find_monster (); if (!coop) { if (!client) client = checkclient (); } if (!client && !coop) return (BotfindBot ()); /* End code */ d) search for ai_botrun function definition. AFTER the line that says: local float ang_ceil; Add the following: /* Begin code */ if (self.enemy.flags & FL_MONSTER) { if (self.enemy.health <= 0) { endEnemy (); return; } } /* End code */ e) search for aibot_chase function definition. AFTER the line that says: local float rnd; Add the following: /* Begin code */ if (self.enemy.flags & FL_MONSTER) { if (self.enemy.health <= 0) { endEnemy (); return; } } /* End code */ f) save changes.

STEP 5

modifying botspawn.qc. a) search for PutBotInServer function definition. AFTER the line that says: self.th_cache = cacheenemy; Add this line of code: self.touch = thud_gone; AT THE END of the function, add the following: /* Begin code */ msgUpdateNameToAll (self.fClientNo, self.netname); msgUpdateColorsToAll (self.fClientNo, self.fShirt, self.fPants); msgUpdateFragsToAll (self.fClientNo, self.frags); /* End code */ b) search for AddAnotherBot function definition. AFTER the line that says: local string st; Add the following: /* Begin code */ local float cno; cno = clientNextAvailable (); if (cno == -1) { bprint ("Unable to spawn bot. Server is full.\n"); return; } clientSetUsed (cno); /* End code */ Look for the lines of code that says the following: newbot = AddBot (); newbot.colormap = FALSE; AFTER those lines, add the following: /* Begin code */ newbot.fClientNo = cno; if (!teamplay) { newbot.colormap = cno + 1; newbot.fShirt = floor (random() * 13); newbot.fPants = floor (random() * 13); } if (teamplay) { newbot.colormap = cno + 1; newbot.fShirt = 2;//newbot.colormap; newbot.fPants = 2; } /* End code */ In teamplay mode, the reaperbots on the bots only team all have the same color--color #2. You can choose another color if you want. Keep in mind that the bots will hunt down people on the other TEAM who may have the same color as the bots. c) search for addTeamBots function definition. AFTER the line that says: local float i; Add this line of code: local float cno; AFTER the line that says: while ( (i > FALSE) ) { Add the following lines of code: /* Begin code */ cno = clientNextAvailable (); if (cno == -1) { bprint ("Unable to spawn bot. Server is full.\n"); return; } clientSetUsed (cno); /* End code */ And yes, the above code should be inside the while block. Also inside the while block, look for these lines of code: newbot.team = ply.team; newbot.teamname = ply.netname; AFTER those lines, add the following: /* Begin code */ newbot.fClientNo = cno; newbot.fShirt = newbot.team - 1; newbot.fPants = newbot.team - 1; /* End code */ Here, the bots on your team will have the same color as your pants. If you change teams after you spawned the bots, then they will no longer be on your team. You just created another bots-only team. d) save changes.

STEP 6

Modifying ai.qc. a) look for FindTarget function definition. Delete the following lines of code from the function: /* Begin code */ if (sight_entity_time >= time - 0.1 && !(self.spawnflags & 3) ) { client = sight_entity; if (client.enemy == self.enemy) return; } else { client = checkclient (); if (!client) return FALSE; // current check entity isn't in PVS } /* End code */ Replace the above lines of code with the following: /* Begin code */ client = world; if (coop) client = find_bot(); if (!client) client = checkclient(); /* End code */ Also in this same function definition, change the line that says: if (self.enemy.classname != "player") to if (self.enemy.classname != "player" && self.enemy.classname != "dmbot") Change the OTHER line that says: if (self.enemy.classname != "player") to if (self.enemy.classname != "player" && self.enemy.classname != "dmbot") b) save changes.

STEP 7

Modifying client.qc. a) search for ClientKill function definition. AFTER the line that says: countkill (self, self); Add the following: msgUpdateFragsToAll (self.fClientNo, self.frags); b) search for PutClientInServer function definition. AT THE END of the function, add the following: /* Begin code */ local float cno; if (!self.LoggedIn) { self.LoggedIn = TRUE; cno = clientNextAvailable (); self.fClientNo = cno; clientSetUsed (cno); msgUpdateNameToAll (cno, self.netname); } /* End code */ c) search for ClientConnect function definition. AFTER the line that says: local entity e; Add the following: /* Begin code */ local float cno; cno = clientNextAvailable (); self.fClientNo = cno; clientSetUsed (cno); msgUpdateNameToAll (cno, self.netname); self.LoggedIn = TRUE; /* End code */ d) search for ClientDisconnect function definition. AFTER the line that says: self.classname = "nobody"; Add this line of code: clientSetFree (self.fClientNo); e) search for ClientObituary function definition. Remember the pattern I mentioned the last time you modified this function. Specifically, remember where you put all those countkill function calls. Guess what? You'll need to remember them. There should be six countkill function calls. So you will need to add six msgUpdateFragsToAll function calls as well. These msgUpdateFragsToAll function calls are the following: msgUpdateFragsToAll (attacker.owner.fClientNo, attacker.owner.frags); msgUpdateFragsToAll (targ.fClientNo, targ.frags); msgUpdateFragsToAll (attacker.fClientNo, attacker.frags); msgUpdateFragsToAll (attacker.fClientNo, attacker.frags); msgUpdateFragsToAll (attacker.fClientNo, attacker.frags); msgUpdateFragsToAll (targ.fClientNo, targ.frags); Obviously, you place the above function calls in the same place that the countkill function calls are. And yes, there is a pattern. Whenever the frags has been updated (eg. you see something like "attacker.frags = attacker.frags + 1"), there is also a countkill function call and a msgUpdateFragsToAll function call. f) save changes.

STEP 8

Modifying combat.qc. a) search for T_Damage function definition. Look for this following lines of code: /* Begin code */ if ( (inflictor != world) && (targ.movetype == MOVETYPE_WALK) ) { dir = targ.origin - (inflictor.absmin + inflictor.absmax) * 0.5; dir = normalize(dir); targ.velocity = targ.velocity + dir*damage*8; } /* End code */ AFTER the above IF block, add this IF block: /* Begin code */ if ( (inflictor != world) && (targ.movetype == MOVETYPE_STEP) && (targ.classname == "dmbot") ) { dir = targ.origin - (inflictor.absmin + inflictor.absmax) * 0.5; dir = normalize(dir); targ.flags = targ.flags - (targ.flags & FL_ONGROUND); targ.velocity = targ.velocity + dir*damage*8; } /* End code */ Look for the following lines of code: /* Begin code */ if (attacker != self) { if (self.enemy) secondEnemy (attacker); else { self.enemy = attacker; BotFoundTarget (); } } /* End code */ Replace the above code with the following: /* Begin code */ if (attacker != self && !coop) { if (self.enemy) secondEnemy (attacker); // tag the other target else { // we've been hit, make him the enemy self.enemy = attacker; BotFoundTarget (); } } /* End code */ b) save changes.

STEP 9

Compile and have fun!