Simple helper bot

Discuss Artificial Intelligence and Bot programming.
Post Reply
Orion
Posts: 476
Joined: Fri Jan 12, 2007 6:32 pm
Location: Brazil

Simple helper bot

Post by Orion »

Hello.

I'm here to distribute a qc file that is the code of a simple helper bot.
His AI is a little better than any other monsters, and his functions aren't fat like id's AI functions, because the file has only 351 lines of code.

When you spawn him, he'll just stand where you created him. He's great for deathmatch, bot for SinglePlayer he isn't, just because he has no following code. I've made it only for deathmatch, but he only follows his enemy, if he follows the enemy and 4 seconds are up, he'll stop and will search for others every frame (0,1 seconds).

He uses the super shotgun at melee range, and the rocket launcher at long distances.

Well, to add it to your mod, just copy and paste the code below and create a file called 'helper.qc'. Write 'helper.qc' without quotes at progs.src right before 'weapons.qc'. Open 'weapons.qc' and create an impulse to it. E.g. if (self.impulse == 13) spawn_clone ();

You can create unlimited clones, but only one per minute. He starts with 400 health, and after 3 minutes he gibs himself.

I've got started with bot coding by coding helper bots, and now I have my own one-file bot -- pretty good AI, colors in GLQuake, shown at scoreboard, and about 2000 lines of code (!!), with a bunch of fighting functions.

You can alter his code and make'em a deathmatch bot.

Here's it:

Code: Select all

$cd id1/models/player_4
$origin 0 -6 24
$base base		
$skin skin

//
// running
//
$frame axrun1 axrun2 axrun3 axrun4 axrun5 axrun6

$frame rockrun1 rockrun2 rockrun3 rockrun4 rockrun5 rockrun6

//
// standing
//
$frame stand1 stand2 stand3 stand4 stand5

$frame axstnd1 axstnd2 axstnd3 axstnd4 axstnd5 axstnd6
$frame axstnd7 axstnd8 axstnd9 axstnd10 axstnd11 axstnd12


//
// pain
//
$frame axpain1 axpain2 axpain3 axpain4 axpain5 axpain6

$frame pain1 pain2 pain3 pain4 pain5 pain6


//
// death
//

$frame axdeth1 axdeth2 axdeth3 axdeth4 axdeth5 axdeth6
$frame axdeth7 axdeth8 axdeth9

$frame deatha1 deatha2 deatha3 deatha4 deatha5 deatha6 deatha7 deatha8
$frame deatha9 deatha10 deatha11

$frame deathb1 deathb2 deathb3 deathb4 deathb5 deathb6 deathb7 deathb8
$frame deathb9

$frame deathc1 deathc2 deathc3 deathc4 deathc5 deathc6 deathc7 deathc8
$frame deathc9 deathc10 deathc11 deathc12 deathc13 deathc14 deathc15

$frame deathd1 deathd2 deathd3 deathd4 deathd5 deathd6 deathd7
$frame deathd8 deathd9

$frame deathe1 deathe2 deathe3 deathe4 deathe5 deathe6 deathe7
$frame deathe8 deathe9

//
// attacks
//
$frame nailatt1 nailatt2

$frame light1 light2

$frame rockatt1 rockatt2 rockatt3 rockatt4 rockatt5 rockatt6

$frame shotatt1 shotatt2 shotatt3 shotatt4 shotatt5 shotatt6

$frame axatt1 axatt2 axatt3 axatt4 axatt5 axatt6

$frame axattb1 axattb2 axattb3 axattb4 axattb5 axattb6

$frame axattc1 axattc2 axattc3 axattc4 axattc5 axattc6

$frame axattd1 axattd2 axattd3 axattd4 axattd5 axattd6

//=====================================================================

void() CloneFindTarget =
{
	local entity en;
	
	en = findradius(self.origin, 2000);
	while (en)
	{
		if (en != self)
		if (en != self.owner)
		if (en.health > 0)
		if (en.classname == "player" || en.classname == "clone" || en.flags & FL_MONSTER)
		if (range(en) != RANGE_FAR)
		if (visible(en))
		{
			self.enemy = en;
			self.goalentity = en;
			self.search_time = time + 4;
		}
		en = en.chain;
	}
};

//=========================================================================

void() T_MissileTouch;
void() CloneThink;

void() CloneFireRocket =
{
	local entity missile;
	
	self.effects = self.effects | EF_MUZZLEFLASH;
	
	sound (self, CHAN_WEAPON, "weapons/sgun1.wav", 1, ATTN_NORM);
	
	missile = spawn ();
	missile.owner = self;
	missile.movetype = MOVETYPE_FLYMISSILE;
	missile.solid = SOLID_BBOX;
	missile.classname = "missile";
	
	makevectors (self.angles);
	missile.velocity = normalize(self.enemy.origin - self.origin);
	missile.velocity = missile.velocity * 1000;
	missile.angles = vectoangles(missile.velocity);
	
	missile.touch = T_MissileTouch; // rocket explosion
	
	missile.nextthink = time + 5;
	missile.think = SUB_Remove;
	
	setmodel (missile, "progs/missile.mdl");
	setsize (missile, '0 0 0', '0 0 0');
	setorigin (missile, self.origin + v_forward*8 + '0 0 16');
};


void(float shotcount, vector dir, vector spread) FireBullets;

void() CloneFireSuperShotgun =
{
	local vector dir;
	local entity en;
	
	self.effects = self.effects | EF_MUZZLEFLASH;
	
	sound (self, CHAN_WEAPON, "weapons/shotgn2.wav", 1, ATTN_NORM);
	
	en = self.enemy;
	
	dir = en.origin - en.velocity*0.2;
	dir = normalize(dir - self.origin);
	FireBullets (14, dir, '0.14 0.08 0');
};

void() clone_face = {self.angles = vectoangles(self.enemy.origin - self.origin);};

void() clone_rocket1 = [$rockatt1, clone_rocket2] {clone_face();};
void() clone_rocket2 = [$rockatt2, clone_rocket3] {clone_face();};
void() clone_rocket3 = [$rockatt3, clone_rocket4] {clone_face();};
void() clone_rocket4 = [$rockatt4, clone_rocket5] {clone_face();};
void() clone_rocket5 = [$rockatt5, clone_rocket6] {clone_face();};
void() clone_rocket6 = [$rockatt6, CloneThink] {clone_face();};

void() clone_shot1 = [$shotatt1, clone_shot2] {clone_face();};
void() clone_shot2 = [$shotatt2, clone_shot3] {clone_face();};
void() clone_shot3 = [$shotatt3, clone_shot4] {clone_face();};
void() clone_shot4 = [$shotatt4, clone_shot5] {clone_face();};
void() clone_shot5 = [$shotatt5, clone_shot6] {clone_face();};
void() clone_shot6 = [$shotatt6, CloneThink] {clone_face();};


void(vector org) spawn_tfog;
void(vector org, entity death_owner) spawn_tdeath;
void() CloneThink =
{
	local float pc;
	local entity dest;
	
	self.nextthink = time + 0.1;
	
	if (self.ltime < time)
	{
		bprint (self.owner.netname);
		bprint ("'s clone removed\n");
		sound (self, CHAN_BODY, "player/tornoff2.wav", 1, ATTN_NONE);
		T_Damage (self, self, self, 50000);
		return;
	}
	
	pc = pointcontents(self.origin);
	if (pc == CONTENT_LAVA || pc == CONTENT_SLIME || pc == CONTENT_WATER)
	{
		dest = find(world, classname, "info_teleport_destination");
		while (dest != world)
			dest = find(dest, classname, "info_teleport_destination");
		
		spawn_tfog (self.origin);
		setorigin (self, dest.origin);
		self.angles = dest.mangle;
		spawn_tfog (self.origin);
		spawn_tdeath (self.origin, self);
	}
	
	if (self.enemy == world)
	{
		if (self.walkframe >= 5)
			self.walkframe = 0;
		self.frame = $stand1 + self.walkframe;
		
		self.walkframe = self.walkframe + 1;
		
		CloneFindTarget ();
	}
	
	if (self.enemy != world)
	{
		if (self.enemy.health <= 0 || self.enemy.deadflag != DEAD_NO)
		{
			self.enemy = world;
			self.goalentity = world;
			return;
		}
		
		if (self.search_time < time && !visible(self.enemy))
		{
			self.enemy = world;
			self.goalentity = world;
			return;
		}
		
		frames ($rockrun1, $rockrun6);
		clone_face ();
		
		if (self.walkframe >= 6)
			self.walkframe = 0;
		self.frame = $rockrun1 + self.walkframe;
		
		self.walkframe = self.walkframe + 1;
		
		if (visible(self.enemy) && self.attack_finished < time)
		{
			if (vlen(self.enemy.origin - self.origin) > 120)
			{
				clone_rocket1 ();
				CloneFireRocket ();
				self.attack_finished = time + 0.8;
			}
			else
			{
				clone_shot1 ();
				CloneFireSuperShotgun ();
				self.attack_finished = time + 0.7;
			}
		}
	}
};


//==========================================================================

void(string gibname, float dm) ThrowHead;
void(string gibname, float dm) ThrowGib;
void(float atten) DeathSound;

void() player_diea1;
void() player_dieb1;
void() player_diec1;
void() player_died1;
void() player_diee1;

void() CloneDie =
{
	local float i;
	
	self.solid = SOLID_NOT;
	self.flags = self.flags - (self.flags & FL_ONGROUND);
	self.movetype = MOVETYPE_TOSS;
	if (self.velocity_z < 10)
		self.velocity_z = self.velocity_z + random()*300;
	
// check for gib
	if (self.health < -40)
	{
		ThrowHead ("progs/h_player.mdl", self.health);
		ThrowGib ("progs/gib1.mdl", self.health);
		ThrowGib ("progs/gib2.mdl", self.health);
		ThrowGib ("progs/gib3.mdl", self.health);
		
		if (random() < 0.5)
			sound (self, CHAN_VOICE, "player/gib.wav", 1, ATTN_NORM);
		else
			sound (self, CHAN_VOICE, "player/udeath.wav", 1, ATTN_NORM);
		return;
	}
	
	DeathSound();
	
	self.angles_x = 0;
	self.angles_z = 0;
	
	i = 1 + floor(random()*6);
	
	if (i == 1)
		player_diea1();
	else if (i == 2)
		player_dieb1();
	else if (i == 3)
		player_diec1();
	else if (i == 4)
		player_died1();
	else
		player_diee1();
};

void(entity attacker, float damage) clone_pain =
{
	if (self.enemy != attacker && attacker != world && attacker != self && attacker.health > 0)
		self.enemy = attacker;
};


void() spawn_clone =
{
	local entity clone;
	
	if (self.yaw_speed > time)
		return;
	self.yaw_speed = time + 60;
	
	bprint ("Clone created by ");
	bprint (self.netname);
	bprint ("!\n");
	
	spawn_tfog (self.origin);
	
	clone = spawn ();
	clone.owner = self;
	clone.movetype = MOVETYPE_STEP;
	clone.solid = SOLID_SLIDEBOX;
	clone.classname = "clone";
	clone.frame = $stand1;
	
	clone.health = 400;
	clone.takedamage = DAMAGE_AIM;
	clone.th_pain = clone_pain;
	clone.th_die = CloneDie;
	clone.ltime = time + 180;
	clone.colormap = self.colormap;
	
	clone.angles = self.angles;
	
	clone.nextthink = time + 0.1;
	clone.think = CloneThink;
	
	setmodel (clone, "progs/player.mdl");
	setsize (clone, VEC_HULL_MIN, VEC_HULL_MAX);
	setorigin (clone, self.origin);
};
Enjoy!
Post Reply