If you're like me, you've always wanted a pet demon. Ever since I was a wee boy I wished for a leathery, drooling hellhound to love me and follow me around and eat those big bullies.
QuakeC gives me the opportunity to fulfill this dream, sort of. In fact, you and I both will learn how to program fiends -- or any other monster -- to escort us and protect us. You got it, man: we're learning how to do a helper bot.
Let's get to work. We'll be working with three important subroutines today: FindTarget(), in which creatures look for players, along with the simple ai_walk() and ai_stand(). Simply stated, we don't want the beast to get mad at a player, just to follow him.
First of all, we need to give the fiend a reminder that he's escorting a player. Open up DEFS.QC, the declaration file. Go to the very bottom and insert this:
float ESCORTING = 5;This is what we coders call a "global constant." We will set his attack state to this when he spots a marine. It is 5 because the first 4 attack states were defined already. It will make more sense later. Save that.
// // got one // self.enemy = client; if (self.enemy.classname != "player") { self.enemy = self.enemy.enemy; if (self.enemy.classname != "player") { self.enemy = world; return FALSE; } } FoundTarget (); return TRUE; };This is the part we need to stop from happening. The "client" is the player who was spotted, and FoundTarget() will make the creature begin attacking him.
if (self.attack_state == ESCORTING) return FALSE; if (self.classname == "monster_demon1") { self.goalentity = client; self.attack_state = ESCORTING; self.th_walk(); return TRUE; }Damn that was easy. In English, this means: "If I'm already escorting, then I'm done here. If I am a demon, the client will become my goal in life, my state of mind will be to escort, and I will start walking. I will leave the rest of this stinky subroutine." Now he won't get angry at the player, just start walking.
if (self.attack_state == ESCORTING && vlen(self.origin - self.goalentity.origin) < 80) { self.pausetime = time + 3; self.th_stand(); }The "vlen" means vector length, or the distance between two points in the Quake world. Okay, I bet you want another English explanation. Alright: "If I have already told myself to escort and the distance between me and my goal is short, I'll just stop walking and pause for a bit."
And there you have it: all of the demons in Quake will follow you around. Huh, what's that? You want them to fight for you, too? Damn, I'm gonna have to start charging for these tutorials, you're getting demanding.
Alright, this last step will be more difficult, but it's still darn simple. Since monsters don't search for other monsters, we'll write our own targeting routine. Paste this above FindTarget():
// ------------------------------------------------ float() FindMonster = // ------------------------------------------------ { local entity beast; if (self.attack_state != ESCORTING) return FALSE; if (self.enemy) return FALSE; beast = findradius(self.origin, 1500); while(beast) { if ( (beast.flags & FL_MONSTER) && visible(beast) && beast != self && beast.health > 0) self.enemy = beast; beast = beast.chain; } if (!self.enemy) return FALSE; FoundTarget(); return TRUE; };Don't be scared; it's just code. Easy stuff. First, if he's not escorting or if he has an enemy, he won't look for monsters. Next we look at all the entities in a 1500-unit circle around him. Then we analyze all those entities. If it is a monster and if it is visible and it is not the demon and it's alive, then it will become our enemy.
float() FindTarget = { local entity client; local float r; // if the first spawnflag bit is set, the monster will only wake up on // really seeing the player, not another monster getting angry // spawnflags & 3 is a big hack, because zombie crucified used the first // spawn flag prior to the ambush flag, and I forgot about it, so the second // spawn flag works as wellRight after the line that reads "local float r" we will paste this:
if (FindMonster()) return TRUE;We are so slick. You see, FindTarget() is "true" when a monster finds a player. Our subroutine is "true" when our demon finds another monster. We are telling FindTarget() to think it is true when ours is true.
A real danger in tutoring -- and in artificial intelligence in general -- is over-thinking, over-explaining. I won't do that here. I think you understand this lesson. I think it was rather simple. I think you see ways you could customize it. So just save, compile, and enjoy.
Ah, one last note: never abuse a pet demon.