
The Indecisive Bot is good for one or two laughs, but has really been useful to me for starting to get a clue about how to work with Quake C. I'll keep tinkering on this thing, and with luck will someday have it sneaking around looking for dark corners to snipe from.
//--------------------------------------------------------
// Indecisive Bot - a very simple example using player.mdl
//
// Function prototypes
//
void() GyroSpawn; // Creates an instance of Gyro
void() GyroRespawn; // Initialize and place a Gyro entity
void() GyroStand; // When standing around, do this
void() GyroPain; // Rock back on your heels and holler
void() GyroResetFrame; // Go back to holding the correct weapon
void() GyroDie; // Make sounds and throw gibs and stuff
void() GyroDead; // Respawn a Gyro
void() GyroObituary; // Frag counts and scrolling death messages
//
// Declare location of player.mdl in id's pak file
//
$cd /raid/quake/id1/models/player_4
//
// We must declare all of the frames in the model.
// These frames are referenced by index, not by name, which means
// that if we left out "stand1" below and in the code used "pain4",
// we would actually wind up with frame "pain3" being used instead.
//
$frame axrun1 axrun2 axrun3 axrun4 axrun5 axrun6
$frame rockrun1 rockrun2 rockrun3 rockrun4 rockrun5 rockrun6
$frame stand1 stand2 stand3 stand4 stand5
$frame axstnd1 axstnd2 axstnd3 axstnd4 axstnd5 axstnd6
$frame axstnd7 axstnd8 axstnd9 axstnd10 axstnd11 axstnd12
$frame axpain1 axpain2 axpain3 axpain4 axpain5 axpain6
$frame pain1 pain2 pain3 pain4 pain5 pain6
$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
$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
//
// For each frame that we're going to use, we must declare a function that
// will be executed when that frame is displayed. This is mostly how we
// get execution cycles from the game engine. We'll also get cycles when
// other entities that are running either attack or touch us.
// This is a state machine, with the state stored in self.think.
//
void() gyro_stand1 = [ $stand1, gyro_stand2 ] { GyroStand(); };
void() gyro_stand2 = [ $stand2, gyro_stand3 ] { GyroStand(); };
void() gyro_stand3 = [ $stand3, gyro_stand4 ] { GyroStand(); };
void() gyro_stand4 = [ $stand4, gyro_stand5 ] { GyroStand(); };
void() gyro_stand5 = [ $stand5, gyro_stand1 ] { GyroStand(); };
void() gyro_axstnd1 = [ $axstnd1, gyro_axstnd2 ] { GyroStand(); };
void() gyro_axstnd2 = [ $axstnd2, gyro_axstnd3 ] { GyroStand(); };
void() gyro_axstnd3 = [ $axstnd3, gyro_axstnd4 ] { GyroStand(); };
void() gyro_axstnd4 = [ $axstnd4, gyro_axstnd5 ] { GyroStand(); };
void() gyro_axstnd5 = [ $axstnd5, gyro_axstnd6 ] { GyroStand(); };
void() gyro_axstnd6 = [ $axstnd6, gyro_axstnd7 ] { GyroStand(); };
void() gyro_axstnd7 = [ $axstnd7, gyro_axstnd8 ] { GyroStand(); };
void() gyro_axstnd8 = [ $axstnd8, gyro_axstnd9 ] { GyroStand(); };
void() gyro_axstnd9 = [ $axstnd9, gyro_axstnd10 ] { GyroStand(); };
void() gyro_axstnd10 = [ $axstnd10, gyro_axstnd11] { GyroStand(); };
void() gyro_axstnd11 = [ $axstnd11, gyro_axstnd12 ] { GyroStand(); };
void() gyro_axstnd12 = [ $axstnd12, gyro_axstnd1 ] { GyroStand(); };
void() gyro_pain1 = [ $pain1, gyro_pain2 ] { PainSound(); };
void() gyro_pain2 = [ $pain2, gyro_pain3 ] { };
void() gyro_pain3 = [ $pain3, gyro_pain4 ] { };
void() gyro_pain4 = [ $pain4, gyro_pain5 ] { };
void() gyro_pain5 = [ $pain5, gyro_pain6 ] { };
void() gyro_pain6 = [ $pain6, gyro_pain6 ] { GyroResetFrame(); };
void() gyro_axpain1 = [ $axpain1, gyro_axpain2 ] { PainSound(); };
void() gyro_axpain2 = [ $axpain2, gyro_axpain3 ] { };
void() gyro_axpain3 = [ $axpain3, gyro_axpain4 ] { };
void() gyro_axpain4 = [ $axpain4, gyro_axpain5 ] { };
void() gyro_axpain5 = [ $axpain5, gyro_axpain6 ] { };
void() gyro_axpain6 = [ $axpain6, gyro_axpain6 ] { GyroResetFrame(); };
void() gyro_diea1 = [ $deatha1, gyro_diea2 ] { };
void() gyro_diea2 = [ $deatha2, gyro_diea3 ] { };
void() gyro_diea3 = [ $deatha3, gyro_diea4 ] { };
void() gyro_diea4 = [ $deatha4, gyro_diea5 ] { };
void() gyro_diea5 = [ $deatha5, gyro_diea6 ] { };
void() gyro_diea6 = [ $deatha6, gyro_diea7 ] { };
void() gyro_diea7 = [ $deatha7, gyro_diea8 ] { };
void() gyro_diea8 = [ $deatha8, gyro_diea9 ] { };
void() gyro_diea9 = [ $deatha9, gyro_diea10 ] { };
void() gyro_diea10 = [ $deatha10, gyro_diea11 ] { };
void() gyro_diea11 = [ $deatha11, gyro_diea11 ] { GyroDead(); };
void() gyro_dieb1 = [ $deathb1, gyro_dieb2 ] { };
void() gyro_dieb2 = [ $deathb2, gyro_dieb3 ] { };
void() gyro_dieb3 = [ $deathb3, gyro_dieb4 ] { };
void() gyro_dieb4 = [ $deathb4, gyro_dieb5 ] { };
void() gyro_dieb5 = [ $deathb5, gyro_dieb6 ] { };
void() gyro_dieb6 = [ $deathb6, gyro_dieb7 ] { };
void() gyro_dieb7 = [ $deathb7, gyro_dieb8 ] { };
void() gyro_dieb8 = [ $deathb8, gyro_dieb9 ] { };
void() gyro_dieb9 = [ $deathb9, gyro_dieb9 ] { GyroDead(); };
void() gyro_diec1 = [ $deathc1, gyro_diec2 ] { };
void() gyro_diec2 = [ $deathc2, gyro_diec3 ] { };
void() gyro_diec3 = [ $deathc3, gyro_diec4 ] { };
void() gyro_diec4 = [ $deathc4, gyro_diec5 ] { };
void() gyro_diec5 = [ $deathc5, gyro_diec6 ] { };
void() gyro_diec6 = [ $deathc6, gyro_diec7 ] { };
void() gyro_diec7 = [ $deathc7, gyro_diec8 ] { };
void() gyro_diec8 = [ $deathc8, gyro_diec9 ] { };
void() gyro_diec9 = [ $deathc9, gyro_diec10 ] { };
void() gyro_diec10 = [ $deathc10, gyro_diec11 ] { };
void() gyro_diec11 = [ $deathc11, gyro_diec12 ] { };
void() gyro_diec12 = [ $deathc12, gyro_diec13 ] { };
void() gyro_diec13 = [ $deathc13, gyro_diec14 ] { };
void() gyro_diec14 = [ $deathc14, gyro_diec15 ] { };
void() gyro_diec15 = [ $deathc15, gyro_diec15 ] { GyroDead(); };
void() gyro_died1 = [ $deathd1, gyro_died2 ] { };
void() gyro_died2 = [ $deathd2, gyro_died3 ] { };
void() gyro_died3 = [ $deathd3, gyro_died4 ] { };
void() gyro_died4 = [ $deathd4, gyro_died5 ] { };
void() gyro_died5 = [ $deathd5, gyro_died6 ] { };
void() gyro_died6 = [ $deathd6, gyro_died7 ] { };
void() gyro_died7 = [ $deathd7, gyro_died8 ] { };
void() gyro_died8 = [ $deathd8, gyro_died9 ] { };
void() gyro_died9 = [ $deathd9, gyro_died9 ] { GyroDead();};
void() gyro_diee1 = [ $deathe1, gyro_diee2 ] { };
void() gyro_diee2 = [ $deathe2, gyro_diee3 ] { };
void() gyro_diee3 = [ $deathe3, gyro_diee4 ] { };
void() gyro_diee4 = [ $deathe4, gyro_diee5 ] { };
void() gyro_diee5 = [ $deathe5, gyro_diee6 ] { };
void() gyro_diee6 = [ $deathe6, gyro_diee7 ] { };
void() gyro_diee7 = [ $deathe7, gyro_diee8 ] { };
void() gyro_diee8 = [ $deathe8, gyro_diee9 ] { };
void() gyro_diee9 = [ $deathe9, gyro_diee9 ] { GyroDead(); };
void() gyro_die_ax1 = [ $axdeth1, gyro_die_ax2 ] { };
void() gyro_die_ax2 = [ $axdeth2, gyro_die_ax3 ] { };
void() gyro_die_ax3 = [ $axdeth3, gyro_die_ax4 ] { };
void() gyro_die_ax4 = [ $axdeth4, gyro_die_ax5 ] { };
void() gyro_die_ax5 = [ $axdeth5, gyro_die_ax6 ] { };
void() gyro_die_ax6 = [ $axdeth6, gyro_die_ax7 ] { };
void() gyro_die_ax7 = [ $axdeth7, gyro_die_ax8 ] { };
void() gyro_die_ax8 = [ $axdeth8, gyro_die_ax9 ] { };
void() gyro_die_ax9 = [ $axdeth9, gyro_die_ax9 ] { GyroDead(); };
//--------------------------------------------------------
// GyroImpulseCommands
//
// NOTE: For IMPULSE 200 to work, you need to add a call to
// GyroImpulseCommands in weapons.qc's ImpulseCommands function.
// So far, that is the only modification that must be made to any
// other Quake C module.
//--------------------------------------------------------
void() GyroImpulseCommands = {
if (self.impulse == 200) {
if (deathmatch) {
if (!teamplay) {
dprint("Uh, prepare to die or something.\n");
GyroSpawn();
} else {
dprint("Let's go kick some, uh, something.\n");
GyroSpawn();
}
} else {
dprint("Gyro is too dumb for single-play.");
return;
}
}
};
//--------------------------------------------------------
// GyroSpawn
// Called when an impulse to spawn a Gyro is issued
// This is essentially just spawning a monster that uses player.mdl
//--------------------------------------------------------
void() GyroSpawn = {
local entity gyroent;
gyroent = spawn(); // Spawn a gyro entity
gyroent.think = GyroRespawn; // Call GyroRespawn in 1-3 seconds
gyroent.nextthink = time + 1 + random()*2;
gyroent.deadflag = DEAD_NO; // Generate no body for the body queue
gyroent.netname = "Indecisive";
gyroent.classname = "program";
gyroent.colormap = self.colormap; // Snarf player's colors?
gyroent.goalentity = self; // I guess the player is our goal
gyroent.movetarget = self;
};
//--------------------------------------------------------
// GyroRespawn
// Called to bring a new gyro body into the world
//--------------------------------------------------------
void() GyroRespawn = {
local entity spawnspot;
self.frags = 0;
self.health = 100;
self.takedamage = DAMAGE_AIM;
self.solid = SOLID_SLIDEBOX;
self.movetype = MOVETYPE_STEP; // Be like a monster
self.show_hostile = 0; // Don't wake up monsters (yet)
self.max_health = 100;
self.flags = FL_CLIENT;
self.air_finished = time + 12;
self.dmg = 2; // initial water damage
self.super_damage_finished = 0;
self.radsuit_finished = 0;
self.invisible_finished = 0;
self.invincible_finished = 0;
self.effects = 0;
self.invincible_time = 0;
self.items = IT_SHOTGUN | IT_AXE;
self.health = 100;
self.armorvalue = 0;
self.ammo_shells = 25;
self.ammo_nails = 0;
self.ammo_rockets = 0;
self.ammo_cells = 0;
self.weapon = IT_SHOTGUN;
self.armortype = 0;
self.attack_finished = time;
self.th_stand = gyro_stand1;
self.th_pain = GyroPain;
self.th_die = GyroDie;
//
// This is a hack to let us use a single function to handle spawning
// both the first instance of Gyro and all subsequent instances.
// If this is not our first time through, we need to put our dead
// body or gibbed head on the body removal queue.
//
if (self.deadflag != DEAD_NO) {
CopyToBodyQue(self);
}
self.deadflag = DEAD_NO; // We were dead, but now we're alive
self.pausetime = 0; // Set by teleports to freeze for a sec
spawnspot = SelectSpawnPoint();
self.origin = spawnspot.origin + '0 0 1';
self.angles = spawnspot.angles;
self.fixangle = TRUE; // turn this way immediately
self.ideal_yaw = self.angles * '0 1 0'; // ???
self.yaw_speed = 60; // How fast we turn?
setmodel(self, "progs/player.mdl");
setsize(self, VEC_HULL_MIN, VEC_HULL_MAX);
self.view_ofs = '0 0 22';
spawn_tfog(self.origin); // Cause the respawn plasma effect
spawn_tdeath(self.origin, self); // Telefrag whatever's there
self.think = self.th_stand; // Start off with this function,
self.nextthink = self.nextthink + 0.1; // and call it soon.
};
//--------------------------------------------------------
// GyroStand
// Called whenever we're standing around bored
//--------------------------------------------------------
void() GyroStand = {
local float rand;
rand = random();
self.nextthink = self.nextthink + 0.1;
//
// Once in a while, change weapons
//
if (rand <= 0.05) {
if (rand <= 0.01) dprint("No, ");
else if (rand <= 0.02) dprint("Wait... ");
else if (rand <= 0.03) dprint("Perhaps ");
else if (rand <= 0.04) dprint("On second thought, ");
rand = random();
if (self.weapon == IT_AXE) {
self.weapon = IT_SHOTGUN;
self.th_stand = gyro_stand1;
self.th_pain = gyro_pain1;
if (rand <= 0.2) dprint("I'll use the gun.\n");
else if (rand <= 0.3) dprint("I'll blow your head off.\n");
else if (rand <= 0.5) dprint("I prefer the boomstick.\n");
else if (rand <= 0.7) dprint("I like the shotgun more.\n");
else if (rand <= 0.9) dprint("I should switch to the gun.\n");
else dprint("you should eat lead instead.\n");
} else {
self.weapon = IT_AXE;
self.th_stand = gyro_axstnd1;
self.th_pain = gyro_axpain1;
if (rand <= 0.2) dprint("I'll use the axe.\n");
else if (rand <= 0.3) dprint("I'll split your head open.\n");
else if (rand <= 0.5) dprint("I prefer hand to hand carnage.\n");
else if (rand <= 0.7) dprint("I like the axe more.\n");
else if (rand <= 0.9) dprint("I should switch to the axe.\n");
else dprint("you should eat my axe.\n");
}
self.think = self.th_stand; // Change Gyro's state
}
};
//--------------------------------------------------------
// GyroPain
// Called whenever we've been hit
//--------------------------------------------------------
void() GyroPain = {
//
// Don't jump to pain frames if we're in the middle of an attack sequence
//
if (self.weaponframe) {
return;
}
//
// Don't pop into view to show pain if we're supposed to be invisible
//
if (self.invisible_finished > time) {
return;
}
//
// Change state to appropriate pain sequence depending on weilded weapon
//
if (self.weapon == IT_AXE) {
gyro_axpain1();
} else {
gyro_pain1();
}
};
//--------------------------------------------------------
// We are done with a pain sequence, and must resume whatever we were doing
// before we got hit.
//--------------------------------------------------------
void() GyroResetFrame = {
//
// Change state to appropriate stand sequence depending on weilded weapon
//
if (self.weapon == IT_AXE) {
self.think = gyro_axstnd1;
} else {
self.think = gyro_stand1;
}
};
//--------------------------------------------------------
// GyroDie
// Wake up. Time to die.
//--------------------------------------------------------
void() GyroDie = {
local float i;
GyroObituary();
self.items = self.items - (self.items & IT_INVISIBILITY);
self.invisible_finished = 0; // don't die as eyes
self.invincible_finished = 0;
self.super_damage_finished = 0;
self.radsuit_finished = 0;
self.modelindex = modelindex_player; // don't use eyes
if (deathmatch || coop) DropBackpack();
self.weaponmodel = "";
self.view_ofs = '0 0 -8';
self.deadflag = DEAD_DYING;
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;
if (self.health < -40) {
GibPlayer();
GyroDead(); // We're dead, Dave.
return;
}
DeathSound();
self.angles_x = 0;
self.angles_z = 0;
if (self.weapon == IT_AXE) {
gyro_die_ax1();
return;
}
i = cvar("temp1");
if (!i) i = 1 + floor(random()*6);
if (i == 1) gyro_diea1();
else if (i == 2) gyro_dieb1();
else if (i == 3) gyro_diec1();
else if (i == 4) gyro_died1();
else gyro_diee1();
};
//--------------------------------------------------------
// GyroDead
// Leave our body/head around for the body queue to clean up and
// reincarnate ourselves.
//--------------------------------------------------------
void() GyroDead = {
//
// Wait from 3-6 seconds, then respawn
//
self.think = GyroRespawn;
self.nextthink = time + 3 + random()*3;
return;
};
//--------------------------------------------------------
// GyroObituary
// A bot cannot use a classname of 'player', or else nasty things can
// happen (like sprint crashing). Unfortunately, if we're not in the
// 'player' class, we won't get obituary messages and frag updates.
// We've just died... let's pretend we're a player and call ClientObituary
// so we don't have to duplicate all that code here.
// Warning: This function assumes that self.enemy has been set to our attacker.
//--------------------------------------------------------
void() GyroObituary = {
local string realclass;
realclass = self.classname;
self.classname = "player";
ClientObituary();
self.classname = realclass;
};