AI Tutorial
Firing Custom Weapons

 

      Okay class, raise your hand if you think the weapons the Quake monsters use are boring...
      Whoa, that's a lot of hands. We all agree, the grunts and enforcers and others have dumb pea-shooters for guns. Look at a mod like Killer Quake Pack. It was fun not only because it gave you new weapons, but because the enemy bots used these guns, too.
      In this tutorial we will teach a grunt how to fire a new weapon, say a lavaball launcher. First, we'll start with taking a look at a player weapon subroutine, the rocket launcher for instance.
      Open up WEAPONS.QC and scroll down until you see this:

/*
================
W_FireRocket
================
*/
void() W_FireRocket =
{
      local      entity missile, mpuff;

      self.currentammo = self.ammo_rockets = self.ammo_rockets - 1;

      sound (self, CHAN_WEAPON, "weapons/sgun1.wav", 1, ATTN_NORM);

      self.punchangle_x = -2;

      missile = spawn ();
      missile.owner = self;
      missile.movetype = MOVETYPE_FLYMISSILE;
      missile.solid = SOLID_BBOX;
      missile.classname = "missile";

      Okay, that's about half the routine. Let's say we want to call this routine from the grunt's perspective. There is nothing here that prevents the grunt from using this part. It creates a new missile, makes it solid, gives it a name. It also creates a recoil effect and subtracts from the player's rockets. This is not relevant to the grunt, but it doesn't hurt anything.
      Let's look at the next part:

// set missile speed      

      makevectors (self.v_angle);
      missile.velocity = aim(self, 1000);
      missile.velocity = missile.velocity * 1000;
      missile.angles = vectoangles(missile.velocity);

      Alright, now we run into an obstacle. Look at the second line. You see, the function called aim() is the player's auto-aim feature. That is, it looks at the player's location and finds the nearest monster in front of him.
      This won't work for the grunt, obviously, because he needs to aim at his enemy, and that may not be the nearest monster. But we can let him use the routine by changing that line to this:

      if (self.classname == "grunt")
            missile.velocity = normalize(self.enemy.origin - self.origin);
            else missile.velocity = aim(self, 1000);

      Now we're playing with power. Here's what are change says in English:

      "If the guy who is using this routine is called a grunt, then he should aim straight at his enemy. Or if he's anything besides a grunt, he should use QuakeC's auto-aim feature."

      That's easy, isn't it? With this modification, we could call this subroutine from the grunt's brain, and he would fire a rocket at his enemy. Honest. I'm not lying.
      By the way, what that line of code does is draw a line from his location (self.origin) to his opponent's location (self.enemy.origin). Don't really worry what "normalize" means. It just sort of smooths out that line.

      Now we are ready for the main event, the grunt's lavaball launcher. We want to replace his stupid shogtun routine with this new weapon. First, open up the file SOLIDIER.QC and scroll down until you see this:

void() army_fire =
{
      local      vector      dir;
      local      entity      en;

      ai_face();

      That's the beginning of his shotgun routine. We want to render it obsolete, so we'll rename it. Change the first line to this:

void() army_fire_old =

      Right, now for the second part, I'm going to assume you know a little bit of QuakeC instead of explaining each line. Just copy the following two routines and paste them *above* the army_fire_old() routine:

void() army_lavaball_touch =
{
      local float      damg;

// don't explode on owner

      if (other == self.owner)
            return;

// disappear if it hits the sky
      if (pointcontents(self.origin) == CONTENT_SKY)
      {
            remove(self);
            return;
      }

// do some damage
      damg = 100 + random()*20;

      if (other.health)
            T_Damage (other, self, self.owner, damg );

      T_RadiusDamage (self, self.owner, 120, other);

      self.origin = self.origin - 8*normalize(self.velocity);

// a lava splash effect from Quake's boss
      WriteByte (MSG_BROADCAST, SVC_TEMPENTITY);
      WriteByte (MSG_BROADCAST, TE_LAVASPLASH);
      WriteCoord (MSG_BROADCAST, self.origin_x);
      WriteCoord (MSG_BROADCAST, self.origin_y);
      WriteCoord (MSG_BROADCAST, self.origin_z);

// turn into a spirite then disappear
      BecomeExplosion ();
};

void() army_fire =
{
      local      entity missile, mpuff;

       // he should face his foe
      ai_face();

      sound (self, CHAN_WEAPON, "weapons/sgun1.wav", 1, ATTN_NORM);

// create a solid, flying entity
      missile = spawn ();
      missile.owner = self;
      missile.movetype = MOVETYPE_FLYMISSILE;
      missile.solid = SOLID_BBOX;
      missile.classname = "missile";
// grunt fires straight at his enemy
      missile.velocity = normalize(self.enemy.origin - self.origin);
      missile.velocity = missile.velocity * 1000;
      missile.angles = vectoangles(missile.velocity);

// what happens when the lavaball hits something
      missile.touch = army_lavaball_touch;
      missile.nextthink = time + 5;
      missile.think = SUB_Remove;

// here's the new model:
      setmodel (missile, "progs/lavaball.mdl");
      setsize (missile, '0 0 0', '0 0 0');
      setorigin (missile, self.origin + v_forward*8 + '0 0 16');
};

      Basically, these two subroutines are the same as the two for the player rockets. As you see, the only changes are the direction and the model, plus the lava splash effect. During the grunt's attack animation, he will call army_fire(), which is now our new subroutine.
      Did all that make sense? The important thing to remember is this: when the player fires, he auto-aims, and monsters cannot use the auto-aim feature.
      If you have some source code from a custom weapon that you like, just look for the line with aim() in it, and change it like we did.
      Remember though, I'm not responsible for any psychological damage you may suffer when you see a lavaball explode in your face.

 


What We Learned

1. Monsters cannot auto-aim
2. A monsters aims by drawing an invisible line from his location to his enemy's location
3. Many custom weapons will work for monsters
4. When a monster fights, everything he thinks relates to "self.enemy," for instance "self.enemy.origin" or "self.enemy.health" or even "self.enemy.weapon"

 


     Author: Coffee
     Questions: coffee@planetquake.com
     AI chat on ICQ: 2716002