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;
};