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!
|