Improved Swimming

 

      You could joke that the Tutor Bot and water don't mix. You could quip that the Tutor Bot shouldn't be allowed in the deep end of the pool. You could laughingly say that the Tutor Bot will never win a gold medal in swimming.
      Yeah, you could make all these jokes, but you'd be a cold-hearted bully that got his jollies at the expense of others. Remember, I meant the Tutor Bot code to be as simple and short as possible. Naturally, I planned to improve his skills all along.
      So now is the time to improve his swimming. This will not be easy. We will not perfect his water skills. There are plenty of methods you as a bot author could implement to achieve the best swimming possible. But as with most lessons, we will learn a simple solution here and now.
      It is important to note that there are two different goals to reach for while working on bot swimming: collecting items underwater, and getting the hell out of the water. With any luck, will will achieve both of these today. To some extent anyway. Basically, our bot will try looking for items until he runs out of air, then he'll head for the surface.
      The file tutor.qc uses the subroutine check_for_water() to see if the bot is submerged in water. This is where it all starts. The beginning of that code looks like so:


// ------------------------------------------------
void() check_for_water =
// ------------------------------------------------
{
local float p;

// bots don't see water like players do, so we check for it

	makevectors(self.angles);
	p = pointcontents(self.origin + v_forward*16);
	if (p != CONTENT_WATER && p != CONTENT_SLIME && p != CONTENT_LAVA)
		return;

      This part checks a point 16 units in front of the bot for contents other than just air. If it does not detect anything, it leaves the subroutine. We want to change that to the following:

// ------------------------------------------------
void() check_for_water =
// ------------------------------------------------
{
local float p;

// bots don't see water like players do, so we check for it

	makevectors(self.angles);
	p = pointcontents(self.origin + v_forward*16);
	if (p != CONTENT_WATER && p != CONTENT_SLIME && p != CONTENT_LAVA)
		{
		self.air_finished = time + 10;
		return;
		}


      Alright, now if this code does not detect water, it makes sure the bot has ten seconds of air in his cute little lungs. (Oppositely, if it does see water, that air will begin to expire.) Then it leaves the subroutine.
      In the middle of this routine, you will see the following snippet of code:

	if (p == CONTENT_WATER && time > self.pain_finished)
		{
		T_Damage (self, world, world, 5);
		self.pain_finished = time + 2;
		sound (self, CHAN_VOICE, "player/gasp2.wav", 1, ATTN_NORM);
		}

      This part damages the bot if he is underwater. We want to change this to reflect the air stored in his lungs. Change it to look like this:

	if (p == CONTENT_WATER && time > self.pain_finished && time > self.air_finished)
		{
		T_Damage (self, world, world, 5);
		self.pain_finished = time + 2;
		sound (self, CHAN_VOICE, "player/gasp2.wav", 1, ATTN_NORM);
		}

      We changed the first line, so now he will only "choke" when his air is expired. Cool. Pretty simple, huh. Now we will examine the end of that routine, which looks like this:

	self.flags = self.flags - (self.flags & FL_ONGROUND);

// he'll try to swim upward here
	self.velocity = self.velocity + (v_forward * 200);
	self.velocity_z = self.velocity_z + 200;
	if (random() < 0.4)
		self.velocity_x = self.velocity_x + 100;
	else if (random() > 0.8)
		self.velocity_y = self.velocity_y + 100;

};

      This is butt-simple code that basically pushes the bot upward, then left or right. Here we shall make are significant alterations. Change the above code to the below code:

	if (p == CONTENT_SLIME || p == CONTENT_LAVA || time > self.air_finished)
		{
		bot_swim1();
		return;
		}


};

      I bet you were expecting our changes to be longer than the original code, huh? Well, in this instance what we don't do is just as important as what we do. Here, if slime or lava is detected, the bot will swim upwards (we assume that all bots want to keep on living) and leave the subroutine. If the bot is out of air, he will also try to get out of the water, leaving the subroutine as well.
      But if he is underwater and he still has air, he will execute whatever code falls after check_for_water(), which means he will walk around and look for items (as you probably tell him to do in the routine bot_walk(), right?). In other words, if he doesn't begin the routine bot_swim1(), he will continue to use movetogoal() or walkmove() as usual.
      Okay, we got the thinking part down. I hope that makes sense to you. I'm not a smart guy, and I can understand it, you know. Next we have to implement the swimming part. We are actually going to do this in a seperate animation script, since the behavior of swimming is significantly different from walking. Go down to the end of your animation section, around the end of the routine bot_diee9(), and add this code:

void()  bot_swim1		=[	$rockrun1,	bot_swim2	] {swim_up();};
void()  bot_swim2		=[	$rockrun2,	bot_swim3	] {swim_up();};
void()  bot_swim3		=[	$rockrun3,	bot_swim4	] {swim_up();};
void()  bot_swim4		=[	$rockrun4,	bot_swim5	] {swim_up();};
void()  bot_swim5		=[	$rockrun5,	bot_swim6	] {swim_up();};
void()  bot_swim6		=[	$rockrun6,	bot_swim7	] {swim_up();};
void()  bot_swim7		=[	$rockrun1,	bot_swim8	] {swim_up();};
void()  bot_swim8		=[	$rockrun2,	bot_swim9	] {swim_up();};
void()  bot_swim9		=[	$rockrun3,	bot_swim10	] {swim_up();};
void()  bot_swim10	=[	$rockrun4,	bot_swim11	] {swim_up();};
void()  bot_swim11	=[	$rockrun5,	bot_swim12	] {swim_forward();};
void()  bot_swim12	=[	$rockrun6,	bot_swim13	] {swim_forward();};
void()  bot_swim13	=[	$rockrun1,	bot_swim14	] {swim_forward();};
void()  bot_swim14	=[	$rockrun2,	bot_swim15	] {swim_forward();};
void()  bot_swim15	=[	$rockrun3,	bot_swim16	] {swim_forward();};
void()  bot_swim16	=[	$rockrun4,	bot_swim17	] {swim_forward();};
void()  bot_swim17	=[	$rockrun5,	bot_swim18	] {swim_forward();};
void()  bot_swim18	=[	$rockrun6,	bot_swim19 	] {swim_forward();};
void()  bot_swim19	=[	$rockrun1,	bot_swim20	] {swim_forward();};
void()  bot_swim20	=[	$rockrun2,	bot_walk1 	] {swim_forward();};

      Hmm, interesting. This is a script that lasts two seconds. As you can see from the end of each line, the bot will call the routine swim_up() for the first ten frames. He will call swim_forward() for the last ten frames. Thus, he is moving up, then forward, like an upside-down L shape.
      Why does he do this, you ask? No, he's not smoking too much crack. As you know, when a player reaches the shore, Quake gives him a little lift to get him out of the water. Bots do not have this benefit, so we have to make him lift himself upward, as if he is jumping out of the water. This allows him to lift himself up onto the land. Take note that he is executing only this and no other code during this little script. Afterward, he will return to the scene bot_walk1().
      Of course we must now write the two swimming routines. Before that script, paste these two things:

// -----------------------------------
void() swim_up =
// -----------------------------------
{
	self.flags = self.flags - (self.flags & FL_ONGROUND);
	self.velocity_z = 300;
};

// -----------------------------------
void() swim_forward =
// -----------------------------------
{
	self.flags = self.flags - (self.flags & FL_ONGROUND);
	makevectors(self.angles);
	self.velocity = v_forward * 300;
};

      These routines do exactly what they say, so that is simple stuff. One last note: near the beginning of the tutor.qc file, you must declare the routine bot_swim1() with a line like this:

void() bot_swim1;

      And, if I'm not mistaken, that's a done deal. Just keep in mind there are two kinds of swimming AI routines: 1) the perfect swimming AI routine; 2) all the others. And to my knowledge, the perfect swimming AI routine has never been written. So that leaves us all in, um, the same boat, so to speak. What I'm trying to say is that there are a hundred and one ways to improve this improvement.
      So what are you waiting for? Get to work.

 


What We Learned

1. Bots have to look for water a different way from the way players do
2. Swimming is physically different from walking, so it should be handled seperately
3. Swimming is better handled with velocity code than movetogoal()

 


Author: Coffee
AI chat on ICQ: 2716002
Return to home