game-source/paroxysm/quake/botmain.qc
Bill Currie 7f3ef60d60 compiles with qfcc (this was probably a pointless exercise due to Deek's
work, but it's nice having a compilable base paroxysm).
2002-02-21 20:36:23 +00:00

2501 lines
65 KiB
C++

/*
POXbots - by Frank Condello (for the Paroxysm Deathmatch Mod)
pox@planetquake.com - http://www.planetquake.com/paroxysm/
This is a modified version of the Tutor Bot by Darryl "Coffee" Atchison (see below)
The Roaming, Targeting and Fighting AI are essentially the same as the original with some changes
and additions - most changes are related to Fighting - bots use all of the Paroxysm weapons (except the bonesaw),
including second triggers (except mines and shrapnelbombs)
Bots have a set of personality variables set when spawned (botprefs) that can
dramatically alter the bots success during a game. Bots have attack types (rush,
retreat, smart-(combination attack types)) favorite weapons, alertness, accuracy, health awareness,
reaction speed, etc..
These variables are set randomly at spawn so you can get a variety of bot skill levels in a game.
I may make these user-configurable in the future (they are currently semi-configurable via skill level).
The most notable change is the addition of true item usage. A seperate file (botgrab.qc)
contains all the code necessary to determine items and define their usage. POXbots do not start with a random weapon.
They all start with the tShot and must find and grab other weapons.
Heath is used as well as Megahealth (with rot). Bots also understand how to use the new sheild generators
and pick up ammo and backpacks. Bots now check for items during combat and roaming so any accidental
item touches are registered. Bots have 'smart' item seeking based on prefs and needs (eg. low health, no weapon).
The naming function has been re-written - no duplicate bot names are allowed and their is a limit
of 8 bots per game - Skins are chosen at random (only two skins right now).
Some other changes:
- Bots die quicker in liquids and have better liquid avoidance
- smater jumping - step/ramp recognition (needs work)
- footstep sound (just like players)
- dead bot bodies remain (CARNAGE!)
- replaced the landing thud sound with a null wav file (the thud gets anoying)
- numerous little 'fixes' -- I may have broke some stuff to begin with ;)
I've commented the changes made with the prefix: POX-
Here's the original Tutor Bot Header...
NOTE: these install instructions WILL NOT WORK with these modified files!
---------------------------------------------------------------------------
T U T O R B O T
Author: Darryl "Coffee" Atchison
coffee@planetquake.com
Website: http://www.planetquake.com/minion/tutorial/main.htm
http://www.planetquake.com
Version: 1.0
1.10.99
I n s t r u c t i o n s
Yes, you heard correctly, this one file acts as an almost-
fully-functional bot. You can plug him into deathmatch patches and
mods. Or you can read his tutorials and customize him.
Here's what to do.
1. Open up the PROGS.SRC file, and insert the words "tutor.qc"
without quotes after the words "misc.qc"
2. Open up the file CLIENT.QC and scroll down to ClientObituary().
You'll see an early line looking like this:
if (targ.classname == "player")
Change it to this:
if (targ.classname == "player" || targ.classname == "bot")
About 20 lines down, you see this line:
if (attacker.classname == "player")
Change it to this:
if (attacker.classname == "player" || attacker.classname == "bot")
3. Open up WEAPONS.QC and add this line to the top of the file:
void(float teem) create_bot;
Then, down below, add these lines to ImpulseCommands():
if (self.impulse == 100)
create_bot(1);
if (self.impulse == 101)
create_bot(2);
4. Compile as you normally would and enjoy.
---------------------------------------------------------------------------
*/
/*
==========================================================================
==========================================================================
==========================================================================
Section 1: AI
Don't let this part worry you. Basically it's just walking and
running thoughts. He'll check around himself for items, use
movetogoal() to navigation, then grab his goal when he's close
enough. He always looks for enemy bots and players. In addition,
he must manually check for water, lava, and gaps in front of him.
Remember, though, he's made to be simple, not smart.
==========================================================================
==========================================================================
==========================================================================
*/
// declaring the routines before they are called
void() bot_jump1;
void() respawn_bot;
void() bot_check_ammo;
float() bot_bestweapon;
void() bot_set_currentammo;
void() bot_regen1; //POX - Regen Animation (stand)
.float regenstop; //POX - Holds the armour station's max armour count
.entity lastgoalentity; //POX- saves last goal target so any item touched on the way can be picked up
.float bot_step_finished; //POX - footstep sound timeout
.float bot_needs_item; //POX - TRUE or FALSE - retreat and go for item
.float needs_search_time; //POX - Bots will give up on a needed item after about 3 seconds if under attack
//POX - BotPrefs
.float botpref_health; //how soon a bot looks for health
.float botpref_aim ; //average quality of aim
.float botpref_weapon; //Prefered best weapon (Plasma Gun or Anihilator)
.float botpref_attack; //lean toward RUSH , RETREAT, or SMART attack styles
//.float botpref_camp; //determines if a bot is a camper or not (TRUE or FALSE)
.float botpref_react; //Time delay between seeing enemy and firing
.float botpref_aware; //Determines whether or not a bot will 'see' you even if you're not infront of him
//.float botpref_goof; //Bots can make mistakes too (missfire)
.float botpref_speed; //How fast the bot moves and reacts overall
.float bot_speed; //Holds the next think time
//POX Attack style constants
float AS_RUSH = 1;
float AS_RETREAT = 2;
float AS_SMART = 0;
void() bot_attack;
//POX- new Function - made this found target because Id's had a LOT of stuff in there just for monsters
//POX - This also supports new AI - item search during combat (bot_search_for_needed)
void() bot_FoundTarget =
{
//Bot gave up on item
if (self.needs_search_time < time)
self.bot_needs_item = FALSE;
//Bot is trying to get an item
if (self.bot_needs_item)
{
//bprint("bot_needs overides new target\n");
return;
}
else
{
self.goalentity = self.enemy;
//bprint("Enemy found\n");
self.think = self.th_run;
self.ideal_yaw = vectoyaw(self.enemy.origin - self.origin);
self.nextthink = time + self.botpref_react;
}
};
//POX- New Function - Footsteps - called when moving on solid ground
void() bot_footstep =
{
local float r;
if (self.bot_step_finished > time)
return;
//No footsteps under water
//I didn't bother checking the water level here, but it should be accurate enough
if (self.waterlevel)
return;
r = random();
if (r < 0.25)
sound (self, CHAN_AUTO, "misc/foot1.wav", 0.3, ATTN_NORM);
else if (r < 0.5)
sound (self, CHAN_AUTO, "misc/foot2.wav", 0.3, ATTN_NORM);
else if (r < 0.75)
sound (self, CHAN_AUTO, "misc/foot3.wav", 0.3, ATTN_NORM);
else
sound (self, CHAN_AUTO, "misc/foot4.wav", 0.3, ATTN_NORM);
self.bot_step_finished = time + self.botpref_speed + 0.2;
};
//POX - new item search routine - called during combat if taking a beating
//Only look for health or a better weapon
// ------------------------------------------------
void() bot_search_for_needed =
// ------------------------------------------------
{
local entity item;
local float n;
// already found something
if (self.goalentity.flags & FL_ITEM)
return;
// checks a radius around him for items - shorter radius
item = findradius(self.origin, 1200);
//POX- Bot prefers health, then weapons during combat - Will not search for anything else
while(item)
{
if ( (item.flags & FL_ITEM) && visible(item) && item.model != string_null)
{
if ((item.healtype == 2) || (item.touch == powerup_touch)) // MegaHealth or powerup
n = 1;
else if (self.health < self.botpref_health && item.healamount)
n = 1;
else if ((item.touch == weapon_touch) && (self.weapon == IT_TSHOT) && (bot_has_weapon(item)))
n = 1;
else
n = 0;
if (n)
{
self.search_time = time + 20;
self.goalentity = item;
self.bot_needs_item = TRUE;
self.needs_search_time = time + 3 + random();
/*TESTING
bprint(self.netname);
bprint(" needs that ");
bprint(item.netname);
bprint("\n");
return;
*/
}
}
item = item.chain;
}
};
// ------------------------------------------------
void() bot_search_for_items =
// ------------------------------------------------
{
local entity item;
//POX - don't search if a needed item is spotted
if (self.bot_needs_item == TRUE)
return;
//POX - Don't stray if going for a regen station
if (self.goalentity.armregen || self.regenstop)
return;
// he gives up on that item and marks it to avoid it for a while
if (time > self.search_time && self.goalentity != world)
{
self.goalentity.search_time = time + 30;
self.goalentity = world;
}
if (self.goalentity != world)
return;
//POX - I made the bots check a larger radius and not check for visibility (helps them roam?)
// checks a radius around him for items
item = findradius(self.origin, 2500);
//POX- Bot prefers health, then weapons, then ammo (depends on health & weapon status & bot prefs)
while(item)
{
if ( (item.flags & FL_ITEM) && item.model != string_null && time > item.search_time)
{
if (self.health < self.botpref_health && !item.healamount)
item = item.chain;
else if (self.weapon == IT_TSHOT && !item.weapon)
item = item.chain;
else if (self.items & item.items)
item = item.chain;
else if ( !item.aflag || (item.aflag && bot_max_ammo(item)) )
item = item.chain;
else
{
self.search_time = time + 10;
self.goalentity = item;
}
}
item = item.chain;
}
};
/*
POX-
Lots of additions here....see botgrab.qc
Bots now only start with the tShot and PulseGun (like real players) and must find a better weapon
Bots look for stuff more often, and use most items Weapons, health, ammo, and backpacks are all used
Armour stations are used as well as MegaHealth, which and rots like player's.
All powerups are used (except for the BioSuit, even though they can pick it up)
*/
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void() bot_grab_items =
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
{
local float bnew;
local float bold;
// sees if he's close enough to pick that item up
if (self.goalentity == world)
return;
//POX - not close enough so go away
if (!(vlen(self.origin - self.goalentity.origin) <= 70))
return;
//POX - touching a player can null him out (?)
//POX - This was an early problem and this check is probably nolonger necessary due to increased item discrimination
if (!(self.goalentity.flags & FL_ITEM))
return;
//POX - Shouldn't happen but...
if (self.goalentity.model == string_null)
{
self.goalentity = world;
self.bot_needs_item = FALSE;
return;
}
//POX - If Bot Health is maxed out and target is a health pack, leave it alone - except for MegaHealth
if (self.goalentity.healamount && self.health >= self.max_health && self.goalentity.healtype != 2)
{
self.bot_needs_item = FALSE;
self.goalentity = world;
return;
}
//POX- Already have weapon so leave it
if (self.goalentity.touch == weapon_touch && bot_has_weapon(self.goalentity))
{
self.bot_needs_item = FALSE;
self.goalentity.search_time = time + 20;
self.goalentity = world;
return;
}
//POX- Already has max ammo so leave it
if (self.goalentity.classname == "ammo" && bot_max_ammo(self.goalentity))
{
self.bot_needs_item = FALSE;
self.goalentity.search_time = time + 20;
self.goalentity = world;
return;
}
//POX- Backpack Pick up - Does not re-spawn
if (vlen(self.origin - self.goalentity.origin) <= 70 && self.goalentity.classname == "pack")
{
sound (self, CHAN_ITEM, "weapons/pkup.wav", 1, ATTN_NORM);
self.ammo_shells = self.ammo_shells + self.goalentity.ammo_shells;
self.ammo_nails = self.ammo_nails + self.goalentity.ammo_nails;
self.ammo_rockets = self.ammo_rockets + self.goalentity.ammo_rockets;
self.ammo_cells = self.ammo_cells + self.goalentity.ammo_cells;
bound_bot_ammo();
bnew = self.goalentity.items;
if (!bnew)
bnew = self.weapon;
bold = self.items;
self.items = self.items | bnew;
//POX - Packs contain health in FFA mode, so check for it here
if (self.goalentity.healamount)
bot_grab_health();
remove(self.goalentity);
self.goalentity = world;
self.bot_needs_item = FALSE; //POX - Reset this even if the target wasn't picked up (the Bot should find it again quickly)
return;
}
//Pick up a re-spawn item
if (vlen(self.origin - self.goalentity.origin) <= 70)
{
self.goalentity.search_time = time + 60;
self.goalentity.solid = SOLID_NOT;
self.goalentity.model = string_null;
//POX - Needed for the dual model Quad hack
if (self.goalentity.classname == "item_artifact_super_damage")
{
self.goalentity.quadcore.mdl = self.quadcore.model;
self.goalentity.quadcore.model = string_null;
}
//POX - Turn off the powerup's 'glow'
if (self.goalentity.classname == "item_artifact_super_damage" || self.goalentity.classname == "item_artifact_invulnerability" || self.goalentity.classname == "item_artifact_invisibility")
self.goalentity.effects = self.goalentity.effects - (self.goalentity.effects & EF_DIMLIGHT);
//POX - respawn times vary for powerups
if (self.goalentity.touch == powerup_touch)
{
if ((self.goalentity.classname == "item_artifact_invulnerability") || (self.goalentity.classname == "item_artifact_invisibility"))
self.goalentity.nextthink = time + 75;
else
self.goalentity.nextthink = time + 60;
}
else //POX regular respawn
self.goalentity.nextthink = time + 30;
self.goalentity.think = SUB_regen;
//POX-Health Pack (botgrab.qc)
if (self.goalentity.healamount)
bot_grab_health();
//POX-Weapons (botgrab.qc)
else if (self.goalentity.touch == weapon_touch)
bot_grab_weapon();
//POX-Ammo (botgrab.qc)
else if (self.goalentity.classname == "ammo")
bot_grab_ammo();
//POX - Powerups - Quad, Cloak & MegaSheilds (botgrab.qc)
else if (self.goalentity.touch == powerup_touch)
bot_grab_powerup();
//POX- If a bot was looking for something during combat, get him to chase his enemy again
if (self.enemy)
{
self.goalentity = self.enemy;
//bprint("enemy is now goal\n");
}
else
self.goalentity = world;
self.bot_needs_item = FALSE; //POX - Reset this even if the target was picked up (the Bot should find it again quickly)
}
};
/*
POX- new Function
Added for better item support
Bots are forced to grab stuff that's close to them (if they need it)
*/
.float close_time;
// -----------------------------------------
void() bot_check_close =
// -----------------------------------------
{
local entity item;
//POX - Don't stray if going for or standing at regen station
if (self.goalentity.armregen)
return;
//check a small radius
item = findradius(self.origin, 300);
while(item)
{
if ( (item.flags & FL_ITEM) && visible(item) && item.model != string_null)
{
//Don't go for health if healthy, only MegaHealth
if (item.healamount && self.health >= self.max_health && item.healtype !=2)
item = item.chain;
//Don't go for a weapon if already in inventory
else if ((item.touch == weapon_touch) && (bot_has_weapon(item)))
item = item.chain;
//Don't go for ammo if already maxed out
else if (item.aflag && bot_max_ammo(item))
item = item.chain;
else
{
//save old target, target new item, try to grab the item then revert to old target
self.lastgoalentity = self.goalentity;
self.goalentity = item;
//give'm a little nudge in the right direction
movetogoal(1);
bot_grab_items();
//Keep switching goals just in case it's an enemy
self.goalentity = self.lastgoalentity;
}
}
item = item.chain;
}
};
/*
POX- new Function
Added for better item support
NOT REALLY A TOUCH FUNCTION!!
This is called during combat and roaming so bots can pick up stuff they accidentally 'touch'
There is no item discrimination here (it's checked in bot_grab_items)
*/
// -----------------------------------------
void() bot_check_touch =
// -----------------------------------------
{
local entity item;
//check a small radius (touch distance)
item = findradius(self.origin, 70);
while(item)
{
if ( (item.flags & FL_ITEM) && item.model != string_null)
{
//save old target, target new item, grab the item then revert to old target
self.lastgoalentity = self.goalentity;
self.goalentity = item;
bot_grab_items();
self.goalentity = self.lastgoalentity;
}
item = item.chain;
}
};
/*
POX- new Function
Bots select an attack based on prefs and enemy's weapon
Returns TRUE if enemy has a better weapon.
This function also doubles as an Attack Style Selection based on botprefs
*/
// -----------------------------------------
float() better_weapon =
// -----------------------------------------
{
local float sweap = 0;
local float eweap = 0;
//Attack Style - Rush
if (self.botpref_attack == AS_RUSH && random() > 0.02)
return FALSE;
//Attack Style - Retreat
if (self.botpref_attack == AS_RETREAT && random() > 0.02)
return TRUE;
//Can't fake out a bot by running around with an empty Anihilator
if (self.enemy.currentammo < 1)
return FALSE;
//Smart Attack
if (self.weapon == IT_TSHOT)
sweap = 1;
if (self.weapon == IT_SUPER_NAILGUN)
sweap = 2;
if (self.weapon == IT_GRENADE_LAUNCHER)
sweap = 3;
if (self.weapon == IT_PLASMAGUN)
sweap = 4;
if (self.weapon == IT_COMBOGUN)
sweap = 5;
if (self.weapon == IT_ROCKET_LAUNCHER)
sweap = 6;
if (self.enemy.weapon == IT_TSHOT || self.enemy.weapon == IT_BONESAW)
eweap = 0;
if (self.enemy.weapon == IT_SUPER_NAILGUN)
eweap = 2;
if (self.enemy.weapon == IT_GRENADE_LAUNCHER)
eweap = 3;
if (self.enemy.weapon == IT_PLASMAGUN)
eweap = 4;
if (self.enemy.weapon == IT_COMBOGUN)
eweap = 5;
if (self.enemy.weapon == IT_ROCKET_LAUNCHER)
eweap = 6;
//if enemy is a bot and has an equal strength weapon, do a reatreat attack (this will tend to spread out fighting bots - needs work)
if (eweap == sweap && self.enemy.classname == "bot")
return TRUE;
//if enemy weapon is better, do retreat attack
else if (eweap > sweap)
return TRUE;
//Bot's weapon is better so chase enemy
else
return FALSE;
};
// --------------------------------
void() bot_run_slide =
// --------------------------------
{
local float ofs;
// this is a cool strafing routine
if (self.lefty)
ofs = 90;
else
ofs = -90;
makevectors(self.angles);
if (walkmove (self.angles_y + ofs, 20))
{
if (ofs == -90)
{
makevectors(self.angles);
self.flags = self.flags - (self.flags & FL_ONGROUND);
self.velocity = self.velocity + v_right * 300;
}
else
{
makevectors(self.angles);
self.flags = self.flags - (self.flags & FL_ONGROUND);
self.velocity = self.velocity + v_right * -300;
}
return;
}
self.lefty = 1 - self.lefty;
walkmove (self.angles_y - ofs, 20);
if (ofs == -90)
{
makevectors(self.angles);
self.flags = self.flags - (self.flags & FL_ONGROUND);
self.velocity = self.velocity + v_right * 300;
}
else
{
makevectors(self.angles);
self.flags = self.flags - (self.flags & FL_ONGROUND);
self.velocity = self.velocity + v_right * -300;
}
};
// ----------------------
void() bot_ai_back=
// ----------------------
{
makevectors(self.angles);
if (pointcontents(self.origin + v_forward*36 + '0 0 36') == CONTENT_SOLID || pointcontents(self.origin + v_forward*22 + '0 0 36') == CONTENT_SOLID)
bot_run_slide();
else
{
self.flags = self.flags - (self.flags & FL_ONGROUND);
self.velocity = v_forward*-460;
}
};
// -----------------------------------------
void() jump_forward =
// -----------------------------------------
{
// propels him into the air
if (!(self.flags & FL_ONGROUND))
return;
self.flags = self.flags - (self.flags & FL_ONGROUND);
makevectors(self.angles);
self.velocity = self.velocity + (v_forward * 220);
self.velocity_z = self.velocity_z + 220;
};
//POX - I've incorporated norse_waterlevel for liquid detection (see botliq.qc)
//POX - These guys still suck hard when it comes to liquids (can't swim), I try to kill them quickly
// ------------------------------------------------
void() check_for_water =
// ------------------------------------------------
{
self.waterlevel = norse_waterlevel(self);
//POX - No liquid detected
if (self.waterlevel == 0)
return;
//POX - Water detected below head so don't bother
if (self.waterlevel < 2 && self.watertype2 == CONTENT_WATER)
return;
//POX - water above head
if (self.waterlevel > 2 && self.watertype2 == CONTENT_WATER && time > self.pain_finished)
{
T_Damage (self, world, world, 8);
self.pain_finished = time + 0.8;
if (random() > 0.5)
sound (self, CHAN_VOICE, "player/drown1.wav", 1, ATTN_NORM);
else
sound (self, CHAN_VOICE, "player/drown2.wav", 1, ATTN_NORM);
}
//POX - Slime - anywhere
if (self.watertype2 == CONTENT_SLIME && time > self.pain_finished)
{
T_Damage (self, world, world, self.waterlevel*15);
self.pain_finished = time + 0.2;
sound (self, CHAN_VOICE, "player/lburn2.wav", 1, ATTN_NORM);
}
//POX - Lava - anywhere
if (self.watertype2 == CONTENT_LAVA && time > self.pain_finished)
{
T_Damage (self, world, world, self.waterlevel*35);
self.pain_finished = time + 0.05;
sound (self, CHAN_VOICE, "player/lburn1.wav", 1, ATTN_NORM);
}
self.flags = self.flags - (self.flags & FL_ONGROUND);
//POX - Give'm an extra boost if in deep
if (self.waterlevel > 2)
self.velocity_z = self.velocity_z + 40;
// he'll try to swim upward here
self.velocity = self.velocity + (v_forward * 140);
self.velocity_z = self.velocity_z + 180;
if (random() < 0.4)
self.velocity_x = self.velocity_x + 90;
else if (random() > 0.8)
self.velocity_y = self.velocity_y + 90;
};
//POX - Bots will avoid jumping into liquids
//POX - This still needs LOTS of work, broken ground over liquids confuses these guys (DM4 is a good example)
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void() check_for_ledge =
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
{
local vector spot, spot2, look;
local float n;
// movetogoal() will never move over a legde, so we have to
// check for a break in front of him and force him to jump
n = 30;
if (random() < 0.4)
return;
if (!(self.flags & FL_ONGROUND))
return;
makevectors (self.angles);
spot = self.origin + (v_forward * 60);
spot = spot - '0 0 35';
//POX - check closer for a step
spot2 = self.origin + (v_forward * 40);
spot2 = spot2 - '0 0 35';
look = spot - '0 0 8';
//particle (spot2 + '0 0 24', '0 0 0', 500, 5); //TESTING
//particle (spot2 - '0 0 24', '0 0 0', 500, 5); //TESTING
//POX - Don't bother to check for liquid if solid ground is ahead (or a step up/down)
if (pointcontents(spot) == CONTENT_SOLID || pointcontents(spot2 + '0 0 24') == CONTENT_SOLID || pointcontents(spot2 - '0 0 24') == CONTENT_SOLID)
return;
//FIXME!
//POX - Check for a gap (not big enough to fall through)
//if (pointcontents(spot + '24 0 0') == CONTENT_SOLID)
// return;
//POX - check for liquid at a series of depths
while (n > 0)
{
if (pointcontents(look) == CONTENT_LAVA || pointcontents(look) == CONTENT_SLIME)
{
n = -1;
self.angles_y = self.angles_y + self.button1; //POX - Turn 90
return;
}
else if (pointcontents(look) == CONTENT_WATER)
{
n = -1;
self.angles_y = self.angles_y + self.button1; //POX - Turn 90
return;
}
//POX - Found a ledge so jump
else if (pointcontents(look) == CONTENT_SOLID)
{
n = -1;
}
//particle (liq, '0 0 0', 500, 5); //TESTING
look = look - '0 0 8';
n = n - 1;
}
//POX - Double Check (do I need this?)
if (pointcontents(spot) == CONTENT_EMPTY)
bot_jump1();
};
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
float() bot_look_for_players =
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
{
local entity client;
local float r;
// this is just like id's FindTarget(), he's looking for clients
//POX - If a bot is beating on another bot, decide if he'll stay on that target or turn to a client
//Higher skill bots will usually choose the client (skill 3 always choose client) - ya it's dirty fight'n
if (self.enemy.classname == "bot" && random() * 3 > skill)
return FALSE;
client = checkclient ();
if (!client)
return FALSE;
if (teamplay)
if (self.team == client.team)
return FALSE;
if (client.classname == "KasCam")
return FALSE;
if (client == self.enemy)
return FALSE;
if (client.flags & FL_NOTARGET)
return FALSE;
//POX - bots may not notice invisible players
if (client.items & IT_INVISIBILITY && random() > 0.4)
return FALSE;
r = range (client);
if (r == RANGE_FAR)
return FALSE;
if (!visible (client))
return FALSE;
if (r == RANGE_NEAR && self.botpref_aware == FALSE)
{
if (client.show_hostile < time && !infront (client))
return FALSE;
}
else if (r == RANGE_MID)
{
if ((!infront (client)) && (random() < 0.8))
return FALSE;
}
self.enemy = client;
bot_FoundTarget ();
return TRUE;
};
// ------------------------------------------------
void() bot_look_for_bots =
// ------------------------------------------------
{
local entity found, foe;
// bots aren't clients, so we have to check fo them manually
// we just see if any of the bots in the entity list are visible
if (self.enemy)
return;
found = world;
foe = find(world, classname, "bot");
while(foe)
{
if (visible(foe) && foe != self && foe.health > 0)
found = foe;
if (teamplay && found.team == self.team)
found = world;
if ((foe.items & IT_INVISIBILITY) && (random() < 0.4)) //POX - check for invisibility
found = world;
foe = find(foe, classname, "bot");
}
if (found != world)
{
self.enemy = found;
bot_FoundTarget ();
}
};
// ----------------------
void() bot_face =
// ----------------------
{
// this turns him directly toward his enemy
self.angles_y = vectoyaw(self.enemy.origin - self.origin);
};
/*
POX - New Regen Station Functions
If a bot is happy with his weapons and health, he'll go for armour
These three functions work together, first he'll target a nearby station,
when reached, he'll stand at it until he's maxed out or an enemy is spotted
*/
.float lastarmorvalue; //POX- A hacky fix for bots not standing close enough to a regen station
//POX - This is very similar to bot_stand, the rules for walking again are slightly different
// -----------------------------------------
void() bot_regen =
// -----------------------------------------
{
local float turn;
BotCheckPowerups (self);
//POX - Force a turn if facing a wall
//POX - I could probably make this neater by using a traceline...
makevectors(self.angles);
if (pointcontents(self.origin + v_forward*20) == CONTENT_SOLID)
turn = TRUE;
else if (pointcontents(self.origin + v_forward*36) == CONTENT_SOLID)
turn = TRUE;
else if (pointcontents(self.origin + v_forward*52) == CONTENT_SOLID)
turn = TRUE;
else if (pointcontents(self.origin + v_forward*68) == CONTENT_SOLID)
turn = TRUE;
else
turn = FALSE;
bot_look_for_bots();
bot_look_for_players();
//Armour maxed out (for current station)
if (self.armorvalue >= self.regenstop)
{
self.regenstop = 0;
self.goalentity = world;
self.think = self.th_walk;
return;
}
// do a cute little turn
//POX - Added wall detection so bots don't stare at walls when standing/camping (needs work)
if (random() < 0.1 || turn == TRUE)
self.angles_y = self.angles_y - 45;
else if (random() > 0.9)
self.angles_y = self.angles_y + 15;
//POX - HACK! - bots standing still don't trigger touch (?) so this will move'em *slightly* side to side (not noticeable)
//POX - Due to this hack, bots will regen slower than real clients so their armour is incremented by 2 at each think (in shields.qc)
walkmove (self.angles_y + 90, 2);
walkmove (self.angles_y - 90, 2);
//POX - sometimes a bot won't stand close enough to a regen station.
//POX - This checks if the armour is actually increasing after standing for 2 second
//POX - THIS DOESN'T ALWAYS WORK (?) - Hence the next check
if (self.lastarmorvalue == self.armorvalue && self.pausetime < time - 6)
self.regenstop = 0;
//POX - spawning over a regen station that's near a weapon can mess these guys up so I forced a move after 8 seconds
if (self.pausetime < time)
self.regenstop = 0;
self.nextthink = time + 0.05;
};
// -----------------------------------------
void() bot_regen_touch =
// -----------------------------------------
{
local entity regen;
//check a VERY small radius since they must 'physically' touch regen triggers
regen = findradius(self.origin, 20);
while(regen)
{
if (regen.armregen > self.armorvalue)
{
//Touching a regen station with better armor value
self.regenstop = regen.armregen;
self.lastarmorvalue = self.armorvalue;
self.pausetime = time + 8;
bot_regen1();
}
regen = regen.chain;
}
};
// -----------------------------------------
void() bot_regen_find =
// -----------------------------------------
{
local entity regen;
regen = findradius(self.origin, 800);
while(regen)
{
if (regen.armregen > self.armorvalue && visible(regen))
{
//Found a regen station with better count than current armour
self.goalentity = regen;
movetogoal(1); //nudge'em
}
regen = regen.chain;
}
};
// ----------------------
void() bot_stand =
// ----------------------
{
local float turn;
local entity item;
// POX - This is called in the bot's main functions - not as accurate as player's per-frame check but it's good enough
//POX - Check DM rules so a bot can end a game at max frags
CheckRules ();
BotCheckPowerups (self);
//POX - Force a turn if facing a wall (needs work)
makevectors(self.angles);
if (pointcontents(self.origin + v_forward*20) == CONTENT_SOLID)
turn = TRUE;
else if (pointcontents(self.origin + v_forward*36) == CONTENT_SOLID)
turn = TRUE;
else if (pointcontents(self.origin + v_forward*52) == CONTENT_SOLID)
turn = TRUE;
else if (pointcontents(self.origin + v_forward*68) == CONTENT_SOLID)
turn = TRUE;
else
turn = FALSE;
// do a cute little turn
if (random() < 0.1 || turn == TRUE)
self.angles_y = self.angles_y - 45;
else if (random() > 0.9)
self.angles_y = self.angles_y + 15;
//POX - stand still if on plat, if not check for other stuff
//POX - Bot Plat detection is more like Plat Bot detection
//POX - Plats know when a bot is on them, and set on_moveplat to time + 1frame when moving, it's a little hacky but it really works well
if (self.on_moveplat > time)
return;
bot_look_for_bots();
bot_look_for_players();
check_for_water();
//POX - accidental item touch - It could happen at item/bot respawn
bot_check_touch();
//POX - If an item is close by, go into walk / search mode
item = findradius(self.origin, 400);
while(item)
{
if ( (item.flags & FL_ITEM) && item.model != string_null)
self.think = self.th_walk;
item = item.chain;
}
//POX - Don't stand around if Health is low
if (self.health < self.botpref_health)
{
self.think = self.th_walk;
return;
}
//POX - Don't stand around if best weapon is tShot
if (self.weapon == IT_TSHOT)
{
self.think = self.th_walk;
return;
}
if (time > self.pausetime)
{
self.think = self.th_walk;
return;
}
self.nextthink = time + 0.05;
};
// ******************************
void() coffee_move =
// ******************************
{
//POX - Platform recognition - only checked during roaming, if fighting, he'll keep doing his attack dance
if (self.on_moveplat > time)
self.think = self.th_stand;
// this is the best subroutine i've ever written, and probably the
// most powerful bot roaming function. i hope you credit me if you use
// it. basically he strafes along a wall, then turns at a 45 or -45
// degree angle at the wall's corner. i have seen my bots do laps
// around entire levels with these three lines of code
if (walkmove (self.angles_y, 20) == FALSE)
{
if (walkmove (self.angles_y + self.button1, 20) == FALSE)
self.angles_y = self.angles_y + (self.button1 / 2);
}
//POX - This reduces the facing-the-wall-strafe by turning away from the wall
//POX - This checks above the head for a wall (so steps will work) - some barriers will still be strafe-navigated
makevectors(self.angles);
//POX - the random call is there so they'll occasionally run into walls (mainly for teleport/button use)
if ( (pointcontents(self.origin + v_forward*80 + '0 0 36') == CONTENT_SOLID || pointcontents(self.origin + v_forward*60 + '0 0 36') == CONTENT_SOLID) && (random() > 0.2) )
self.angles_y = self.angles_y + (self.button1 / 4);
else if (pointcontents(self.origin + v_forward*36 + '0 0 36') == CONTENT_SOLID || pointcontents(self.origin + v_forward*22 + '0 0 36') == CONTENT_SOLID)
self.angles_y = self.angles_y + (self.button1 / 3);
// every so often, he'll change his wall-hugging direction
if (random() <= 0.02)
{
if (random() > 0.5)
{
if (self.button1 == 90)
self.button1 = -90;
else
self.button1 = 90;
}
}
//POX - Make a footstep sound
bot_footstep();
//POX - Bots sometimes avoid death in liquids (?)
//POX - This is the second attempt to put them to rest
if (self.health < 1)
self.th_die();
};
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void() bot_walk =
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
{
// this is his main AI routine, where he will look for items and enemies
self.nextthink = time + 0.05;
if (self.bot_speed > time)
return;
BotCheckPowerups (self);
self.bot_speed = time + self.botpref_speed;
//POX - Check DM rules so a bot can end a game at max frags
CheckRules ();
//POX - Caught these guys running around with whatever weapon they grabbed last
bot_check_ammo();
//POX - reset max armour count
if(self.regenstop)
self.regenstop = 0;
bot_look_for_bots();
bot_look_for_players();
if (!(self.flags & FL_ONGROUND))
return;
//regular item search and grab
bot_search_for_items();
bot_grab_items();
//accidental item touch
bot_check_touch();
bot_check_close();
check_for_ledge();
check_for_water();
//POX - If health is good look for armour
//This is the only time when a bot will actively seek armour, but since armour
//stations work by touch, a bot can accidentally gain armour just by running around
if (self.health > self.botpref_health - 20)
{
bot_regen_find();
bot_regen_touch();
}
//POX - This if statement fixed a few problems I have seen with these bots ignoring enemies, firing at walls...
//POX - It just tells them to look for stuff if they aren't going after a bot, player, or item
//POX - There must be something I missed elsewhere causing these guys to target junk they can't reach
if (self.goalentity.classname != "bot" || self.goalentity.classname != "player" || !(self.goalentity.flags & FL_ITEM) || self.goalentity.model == string_null)
{
self.goalentity = world;
self.enemy = world;
bot_look_for_bots();
bot_look_for_players();
}
// of course movetogoal() is id's C function, it moves randomly
// toward what his self.goalentity is; don't let it worry you,
// this function takes a long time to get where its going
// the coffee_move is his cool running function
if (self.goalentity != world)
{
movetogoal(20);
//POX - Make a footstep sound
bot_footstep();
}
else
{
coffee_move();
}
};
/*Moved above jump_forward for use in bot_ai_back
// --------------------------------
void() bot_run_slide =
// --------------------------------
{
local float ofs;
// this is a cool strafing routine
//POX - Make a footstep sound
bot_footstep();
if (self.lefty)
{
ofs = 90;
ofs = ofs + floor(random() * 15);
}
else
{
ofs = -90;
ofs = ofs - floor(random() * 15);
}
bot_footstep();
if (walkmove (self.angles_y + ofs, 20))
return;
self.lefty = 1 - self.lefty;
walkmove (self.angles_y - ofs, 20);
};
*/
// ----------------------
void() bot_strafe =
// ----------------------
{
local float r;
// this routine is called every frame during combat,
// so he strafes and dodges even while shooting
self.nextthink = time + 0.05;
if (self.bot_speed > time)
return;
BotCheckPowerups (self);
self.bot_speed = time + self.botpref_speed;
bot_check_ammo();
//POX- accidental item touch
bot_check_touch();
bot_check_close();
// check_for_ledge();
check_for_water();
if (!visible(self.enemy))
{
self.think = self.th_walk;
return;
}
bot_face();
//POX - Added so bots won't try to back up if already far enough away
r = range (self.enemy);
//POX - If a Bot is low on Health or is out-armed during combat, he'll look for any good stuff near by and try to grab it
if (self.health < self.botpref_health || better_weapon() == TRUE)
{
bot_search_for_needed();
bot_grab_items();
if (self.goalentity.flags & FL_ITEM)
{
movetogoal(20);
//POX - Make a footstep sound
bot_footstep();
}
//If no needed items are near by, back up until some are
else //if (walkmove (self.angles_y - 180, 20) == FALSE)
bot_ai_back();//bot_run_slide();
}
//POX- If the bot got here, he's got a lot of health and a good weapon so choose an attack type
// stepping backwards for a long distance shot
//POX - even AS_RUSH bots will back up for a rocket attack
else if ( (self.weapon == IT_ROCKET_LAUNCHER) && (r != RANGE_MID || r != RANGE_FAR) )
{
if (walkmove (self.angles_y - 180, 20) == FALSE)
bot_run_slide();
}
//POX - Check for SMART, RETREAT or RUSH Attack
//POX - TRUE = retreat
else if (better_weapon())
{
//if (walkmove (self.angles_y - 180, 20) == FALSE)
bot_ai_back();//bot_run_slide();
}
// chasing the player here
else if (!(better_weapon()))
{
movetogoal(20);
//POX - Make a footstep sound
bot_footstep();
}
else
bot_run_slide();
};
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void() bot_run =
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
{
// his fighting thoughts. after a short while, he'll give up
// on his enemy, but if he can see him, he'll always attack
//self.nextthink = time + 0.05;
BotCheckPowerups (self);
if (!(self.flags & FL_ONGROUND))
return;
//POX - A hacky fix to make sure these guys have a real target
if (self.goalentity.classname != "bot" || self.goalentity.classname != "player" || !(self.goalentity.flags & FL_ITEM) || self.goalentity.model == string_null)
{
self.goalentity = world;
self.enemy = world;
bot_look_for_bots();
bot_look_for_players();
}
if (visible(self.enemy))
self.search_time = time + 0.4;
if (time > self.search_time || self.enemy.health <= 0)
{
self.goalentity = world;
self.enemy = world;
self.pausetime = time + 4;
if (random() > 0.7)
self.think = self.th_stand;
else
self.think = self.th_walk;
return;
}
bot_strafe();
//POX - Added extra checks to make sure they only target other bots or players only.
if (visible(self.enemy) && time > self.attack_finished && self.enemy != self && (self.enemy.classname == "bot" || self.enemy.classname == "player") )
self.th_missile();
};
/*
==========================================================================
==========================================================================
==========================================================================
Section 2: Weapons
This section is the simplest, basically dull stuff. It checks for
his best weapon and sets the relevant ammo. It gives him a free
weapon. And it does the actual firing of the weapons. The key
difference between a player weapon routine and a bot weapon
routine is the aiming. In player routines, you'll see a line
like this:
dir = aim (self, 100000);
If you want a bot to share that subroutine, basically all you need
to do is change it to this:
if (self.classname == "player")
dir = aim (self, 100000);
else dir = normalize(self.enemy.origin - self.origin);
This allows the bot to aim directly at his enemy.
==========================================================================
==========================================================================
==========================================================================
*/
void() bot_run1;
//POX - Nolonger needed - bots will always drop a pack unless gibbed (same for players)
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//void() bot_drop_pack =
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//{
// if (random () < 0.9)
// DropBackpack();
//};
//POX- Changed for new Paroxysm Weapons
//POX - Minimum ammo now reflects second triggers as well
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
float() bot_bestweapon =
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
{
local float it;
it = self.items;
//POX - Added weapon Preference
if (self.ammo_rockets >= 1 && (it & IT_ROCKET_LAUNCHER) && (self.botpref_weapon == IT_ROCKET_LAUNCHER) )
return IT_ROCKET_LAUNCHER;
else if (self.ammo_cells >= 9 && (it & IT_PLASMAGUN) && (self.botpref_weapon == IT_PLASMAGUN) )
return IT_PLASMAGUN;
else if (self.ammo_rockets >= 1 && (it & IT_ROCKET_LAUNCHER) )
return IT_ROCKET_LAUNCHER;
else if (self.ammo_cells >= 9 && (it & IT_PLASMAGUN) )
return IT_PLASMAGUN;
else if (self.ammo_shells >= 2 && (it & IT_COMBOGUN) )
return IT_COMBOGUN;
else if (self.ammo_rockets >= 1 && (it & IT_GRENADE_LAUNCHER) )
return IT_GRENADE_LAUNCHER;
else if (self.ammo_nails >= 6 && (it & IT_SUPER_NAILGUN) )
return IT_SUPER_NAILGUN;
return IT_TSHOT;
};
// --------------------------------
void() bot_set_currentammo =
// --------------------------------
{
self.items = self.items - ( self.items & (IT_SHELLS | IT_NAILS | IT_ROCKETS | IT_CELLS) );
if (self.weapon == IT_TSHOT)
{
self.currentammo = self.ammo_shells;
self.items = self.items | IT_SHELLS;
}
else if (self.weapon == IT_COMBOGUN)
{
self.currentammo = self.ammo_shells;
self.items = self.items | IT_SHELLS;
}
else if (self.weapon == IT_PLASMAGUN)
{
self.currentammo = self.ammo_cells;
self.items = self.items | IT_CELLS;
}
else if (self.weapon == IT_SUPER_NAILGUN)
{
self.currentammo = self.ammo_nails;
self.items = self.items | IT_NAILS;
}
else if (self.weapon == IT_GRENADE_LAUNCHER)
{
self.currentammo = self.ammo_rockets;
self.items = self.items | IT_ROCKETS;
}
else if (self.weapon == IT_ROCKET_LAUNCHER)
{
self.currentammo = self.ammo_rockets;
self.items = self.items | IT_ROCKETS;
}
else
{
self.currentammo = 0;
self.weaponmodel = "";
self.weaponframe = 0;
}
};
// -------------------------
void() bot_check_ammo =
// -------------------------
{
//if (self.currentammo > 0)
// return;
//if (self.weapon == IT_BONESAW)
// return;
self.weapon = bot_bestweapon();
bot_set_currentammo();
//if (!self.invis_check)
// getmodel(self.weapon, self);
};
// -------------------------------------
vector() bot_aim_at_enemy =
// -------------------------------------
{
local vector dir;
local float chance;
//Predator Mode / invisible enemy
if (self.enemy.items & IT_INVISIBILITY)
chance = floor(random() * 10);
else
chance = 0;
if (chance < 2)
{
//POX- bot aim is affected by botpref_aim (higher values are worse)
if (range(self.enemy) != RANGE_MELEE)//POX - be accurate at close range
{
dir = self.enemy.origin - self.enemy.velocity*self.botpref_aim;
return normalize (dir - self.origin);
}
else
return normalize(self.enemy.origin - self.origin);
}
//Bad aim for Pred mode
else if (chance == 2)
return normalize((self.enemy.origin - '50 0 0') - self.origin);
else if (chance == 9)
return normalize((self.enemy.origin - '75 0 0') - self.origin);
else
return normalize((self.enemy.origin + '20 0 0') - self.origin);
};
//FIXED?
//POX - Bot missiles sometimes Hang (?)
//I couldn't find the cause so I added these new thinks for projectiles
/*POX v1.11
void() bot_missile_think =
{
self.nextthink = time + 5;
if(!self.velocity)
self.nextthink = time + 0.01;
self.think = SUB_Remove;
};
*/
//POX- new function - PlasmaGun Second Trigger
void() bot_fire_MPlasma =
// -----------------------------
{
//Had to double check this (somehow this happens once and a while)
if (self.ammo_cells < 9 || self.ammo_cells > 201)
{
self.ammo_cells = 0;
return;
}
self.currentammo = self.ammo_cells = self.ammo_cells - 9;
sound (self, CHAN_WEAPON, "weapons/mplasma.wav", 1, ATTN_NORM);
bot_face();
newmis = spawn ();
newmis.voided = 0;
newmis.owner = self;
newmis.movetype = MOVETYPE_FLY;
newmis.solid = SOLID_BBOX;
newmis.classname = "megaplasma";
newmis.effects = newmis.effects | EF_BRIGHTFIELD | EF_BRIGHTLIGHT;
newmis.velocity = bot_aim_at_enemy() * 1200;
newmis.angles = vectoangles(newmis.velocity);
newmis.touch = T_MplasmaTouch;
//POX v1.11
//newmis.nextthink = time + 0.05;
//newmis.think = bot_missile_think;
newmis.nextthink = time + 5;
newmis.think = SUB_Remove;
setmodel (newmis, "progs/plasma.mdl");
setsize (newmis, '-1 -1 -1', '1 1 1');
setorigin (newmis, self.origin + v_forward*8 + '0 0 16');
};
//POX- new function - ComboGun Second Trigger
//FIXME - obituary calls this buckshot (?ammo changes too fast?)
.float pball_finish; //second trigger timeout
void() bot_fire_PBall =
// -----------------------------
{
self.currentammo = self.ammo_rockets = self.ammo_rockets - 1;
sound (self, CHAN_WEAPON, "weapons/ssgrnde.wav", 1, ATTN_NORM);
bot_face();
newmis = spawn ();
newmis.voided = 0;
newmis.owner = self;
newmis.movetype = MOVETYPE_TOSS;
newmis.solid = SOLID_BBOX;
newmis.classname = "impactgrenade";
//------------------------------------
makevectors (self.v_angle);
newmis.velocity = bot_aim_at_enemy() * 700;
newmis.velocity_z = newmis.velocity_z + 200;
newmis.angles = vectoangles(newmis.velocity);
//------------------------------------
newmis.touch = T_PballTouch;
//POX v1.11
//newmis.nextthink = time + 0.05;
//newmis.think = bot_missile_think;
newmis.nextthink = time + 3;
newmis.think = SUB_Remove;
setmodel (newmis, "progs/grenade.mdl");
setsize (newmis, '-1 -1 -1', '1 1 1');
setorigin (newmis, self.origin + v_forward*8 + '0 0 16');
};
/* Replaced by the BoneSaw (Bots don't use the BoneSaw)
// -------------------------------------
void() bot_fire_pulsegun =
// -------------------------------------
{
local entity missile;
sound (self, CHAN_WEAPON, "weapons/pulse.wav", 1, ATTN_NORM);
bot_face();
missile = spawn ();
missile.owner = self;
missile.movetype = MOVETYPE_FLYMISSILE;
missile.solid = SOLID_BBOX;
missile.velocity = bot_aim_at_enemy() * 1100;
missile.angles = vectoangles(missile.velocity);
missile.touch = Pulse_Touch;
missile.nextthink = time + 0.05;
missile.think = bot_missile_think;
setmodel (missile, "progs/p_spike.mdl");
setsize (missile, '-1 -1 -1', '1 1 1');
setorigin (missile, self.origin + v_forward*8 + '0 0 16');
};
*/
// -------------------------------------
void() bot_fire_supershotgun =
// -------------------------------------
{
local vector dir;
self.currentammo = self.ammo_shells = self.ammo_shells - 2;
bot_face();
sound (self ,CHAN_WEAPON, "weapons/shotgn2.wav", 1, ATTN_NORM);
self.effects = self.effects | EF_MUZZLEFLASH;
dir = bot_aim_at_enemy();
FireBullets (14, dir, '0.14 0.1 0');
};
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void() bot_fire_shotgun =
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
{
local vector dir;
if (self.weapon == IT_COMBOGUN)
{
bot_fire_supershotgun();
return;
}
//POX - This is a hack since Bots don't use the BoneSaw
if (self.ammo_shells < 1)
self.ammo_shells = 10;
/*
if (self.ammo_shells < 1)
{
bot_fire_pulsegun();
return;
}
*/
self.currentammo = self.ammo_shells = self.ammo_shells - 1;
bot_face();
sound (self, CHAN_WEAPON, "weapons/tsfire.wav", 1, ATTN_NORM);
self.effects = self.effects | EF_MUZZLEFLASH;
dir = bot_aim_at_enemy();
FireBullets (6, dir, '0.04 0.04 0');
};
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void() bot_fire_supernailgun =
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
{
local vector dir;
self.currentammo = self.ammo_nails = self.ammo_nails - 1;
sound (self, CHAN_WEAPON, "weapons/nailgun.wav", 1, ATTN_NORM);
bot_face();
dir = bot_aim_at_enemy();
launch_spike (self.origin + '0 6 16', dir);
newmis.touch = spike_touch;
setmodel (newmis, "progs/s_spike.mdl");
setsize (newmis, VEC_ORIGIN, VEC_ORIGIN);
};
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void() bot_fire_nailgun = //POX - Actually, this is the PlasmaGun
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
{
local vector dir;
if (self.weapon == IT_SUPER_NAILGUN)
{
bot_fire_supernailgun();
return;
}
self.currentammo = self.ammo_cells = self.ammo_cells - 1;
sound (self, CHAN_WEAPON, "weapons/plasma.wav", 1, ATTN_NORM);
bot_face();
dir = bot_aim_at_enemy();
launch_plasma (self.origin + '0 6 16', dir);
newmis.touch = plasma_touch;
setmodel (newmis, "progs/laser.mdl");
setsize (newmis, VEC_ORIGIN, VEC_ORIGIN);
};
// """"""""""""""""""""""""""""
void() bot_fire_grenade =
// ----------------------------
{
//POX - PlasmaGun Second Trigger (shared animation)
if (self.weapon == IT_PLASMAGUN)
{
bot_fire_MPlasma();
return;
}
//POX - ComboGun Second Trigger (shared animation)
if (self.weapon == IT_COMBOGUN)
{
bot_fire_PBall();
return;
}
self.currentammo = self.ammo_rockets = self.ammo_rockets - 1;
self.effects = self.effects | EF_MUZZLEFLASH;
sound (self, CHAN_WEAPON, "weapons/ssgrnde.wav", 1, ATTN_NORM);
bot_face();
newmis = spawn ();
newmis.voided = 0;
newmis.owner = self;
newmis.movetype = MOVETYPE_BOUNCE;
newmis.solid = SOLID_BBOX;
// set missile speed
makevectors (self.v_angle);
//POX - slightly better vertical aim
newmis.velocity = bot_aim_at_enemy() * 580;
newmis.velocity_z = newmis.velocity_z + 200;
//newmis.velocity = newmis.velocity * 580;
//newmis.velocity_z = 300;
newmis.avelocity = '300 300 300';
newmis.angles = vectoangles(newmis.velocity);
newmis.touch = GrenadeTouch;
// set missile duration
newmis.nextthink = time + 2.5;
newmis.think = GrenadeExplode;
setmodel (newmis, "progs/grenade.mdl");
setsize (newmis, '0 0 0', '0 0 0');
setorigin (newmis, self.origin);
};
// =============================
void() bot_fire_rocket =
// -----------------------------
{
local vector spot1, spot2;
local float dist;
if (self.weapon == IT_GRENADE_LAUNCHER)
{
bot_fire_grenade();
return;
}
self.currentammo = self.ammo_rockets = self.ammo_rockets - 0.5;
bot_face();
//POX - For better aiming of toss projectiles
spot1 = self.origin + self.view_ofs;
spot2 = self.enemy.origin + self.enemy.view_ofs;
dist = vlen (spot1 - spot2);
newmis = spawn ();
newmis.voided = 0;
newmis.owner = self;
newmis.movetype = MOVETYPE_TOSS;
newmis.solid = SOLID_BBOX;
newmis.classname = "rocket";
//=---------------------------------------------
makevectors (self.v_angle);
//POX - Much better verical aim
newmis.velocity = bot_aim_at_enemy() * 1100;
if (dist > 500)
newmis.velocity_z = newmis.velocity_z + (dist*0.3) + (random()*8);
else
newmis.velocity_z = newmis.velocity_z + 180 + (random()*8);
//bprint(ftos(dist*0.3));
//bprint("\n");
newmis.angles = vectoangles(newmis.velocity);
//=---------------------------------------------
newmis.touch = T_MissileTouch;
//POX v1.11
//newmis.nextthink = time + 0.05;
//newmis.think = bot_missile_think;
newmis.nextthink = time + 5;
newmis.think = SUB_Remove;
setmodel (newmis, "progs/grenade.mdl");
setsize (newmis, '-1 -1 -1', '1 1 1');
setorigin (newmis, self.origin + v_forward*8 + '0 0 16');
};
/* POX - not needed
// ==============================
void() bot_lightning =
// ------------------------------
{
local vector org, dir;
self.effects = self.effects | EF_MUZZLEFLASH;
bot_face();
makevectors(self.angles);
org = self.origin + '0 0 8' + (v_forward * 8);
dir = bot_aim_at_enemy();
traceline (org, self.origin + dir*600, TRUE, self);
WriteByte (MSG_BROADCAST, SVC_TEMPENTITY);
WriteByte (MSG_BROADCAST, TE_LIGHTNING1);
WriteEntity (MSG_BROADCAST, self);
WriteCoord (MSG_BROADCAST, org_x);
WriteCoord (MSG_BROADCAST, org_y);
WriteCoord (MSG_BROADCAST, org_z);
WriteCoord (MSG_BROADCAST, trace_endpos_x);
WriteCoord (MSG_BROADCAST, trace_endpos_y);
WriteCoord (MSG_BROADCAST, trace_endpos_z);
LightningDamage (org, trace_endpos, self, 10);
sound (self, CHAN_WEAPON, "weapons/lhit.wav", 1, ATTN_NORM);
self.currentammo = self.ammo_cells = self.ammo_cells - 1;
};
/*
==========================================================================
==========================================================================
==========================================================================
Section 3: Animation
This is a bunch of ugly stuff, and you don't really need to
understand all of it. It merely defines his animation frames and
sequences. After that you have his pain, death, and attack routines.
Lastly, we have his spawn/respawn subroutine.
==========================================================================
==========================================================================
==========================================================================
*/
/*POX - frames are referenced by absolute numbers for reuse in different visibleweapon models
// Frame macros
$cd /raid/quake/id1/models/player_4
$origin 0 -6 24
$base base
$skin skin
$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 axataxattd6
*/
// movement animation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//POX - added for Armour Regen Station Support
void() bot_regen1 =[ 6, bot_regen2 ] {bot_regen();};
void() bot_regen2 =[7, bot_regen3 ] {bot_regen();};
void() bot_regen3 =[ 8, bot_regen4 ] {bot_regen();};
void() bot_regen4 =[ 9, bot_regen1 ] {bot_regen();};
void() bot_stand1 =[ 6, bot_stand2 ] {bot_stand();};
void() bot_stand2 =[ 7, bot_stand3 ] {bot_stand();};
void() bot_stand3 =[ 8, bot_stand4 ] {bot_stand();};
void() bot_stand4 =[ 9, bot_stand1 ] {bot_stand();};
void() bot_walk1 =[ 0, bot_walk2 ] {bot_walk();};
void() bot_walk2 =[ 1, bot_walk3 ] {bot_walk();};
void() bot_walk3 =[ 2, bot_walk4 ] {bot_walk();};
void() bot_walk4 =[ 3, bot_walk5 ] {bot_walk();};
void() bot_walk5 =[ 4, bot_walk6 ] {bot_walk();};
void() bot_walk6 =[ 5, bot_walk1 ] {bot_walk();};
void() bot_run1 =[ 0, bot_run2 ] {bot_run();};
void() bot_run2 =[ 1, bot_run3 ] {bot_run();};
void() bot_run3 =[ 2, bot_run4 ] {bot_run();};
void() bot_run4 =[ 3, bot_run5 ] {bot_run();};
void() bot_run5 =[ 4, bot_run6 ] {bot_run();};
void() bot_run6 =[ 5, bot_run1 ] {bot_run();};
void() bot_jump1 =[ 0, bot_jump2 ] {jump_forward();};
void() bot_jump2 =[ 0, bot_jump3 ] {};
void() bot_jump3 =[ 0, bot_jump4 ] {};
void() bot_jump4 =[ 0, bot_jump5 ] {};
void() bot_jump5 =[0, bot_walk1 ] {};
// attack animation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//POX - Added a few things here, SuperDamage sound is checked, and the weapon fire calls support POX weapons
void() bot_rock1 =[ 14, bot_rock2 ] {bot_strafe(); self.effects = self.effects | EF_MUZZLEFLASH;
sound (self, CHAN_WEAPON, "weapons/rhino.wav", 1, ATTN_NORM); bot_fire_rocket(); SuperDamageSound();self.nextthink = time + 0.1;};
void() bot_rock2 =[ 15, bot_rock3 ] {bot_strafe(); bot_fire_rocket();};
void() bot_rock3 =[ 16, bot_rock4 ] {bot_strafe();};
void() bot_rock4 =[ 16, bot_run1 ] {bot_strafe();};
void() bot_gren1 =[ 15, bot_gren2 ] {bot_strafe(); self.effects = self.effects | EF_MUZZLEFLASH; bot_fire_grenade(); SuperDamageSound();};
void() bot_gren2 =[ 16, bot_gren3 ] {bot_strafe();};
void() bot_gren3 =[ 17, bot_gren4 ] {bot_strafe();};
void() bot_gren4 =[ 18, bot_gren5 ] {bot_strafe();};
void() bot_gren5 =[ 19, bot_run1 ] {bot_strafe();};
void() bot_shot1 =[ 15, bot_shot2 ] {bot_strafe(); self.effects = self.effects | EF_MUZZLEFLASH; bot_fire_shotgun(); SuperDamageSound();};
void() bot_shot2 =[ 16, bot_shot3 ] {bot_strafe();};
void() bot_shot3 =[ 17, bot_shot4 ] {bot_strafe();};
void() bot_shot4 =[ 18, bot_shot5 ] {bot_strafe();};
void() bot_shot5 =[ 19, bot_run1 ] {bot_strafe();};
//POX - 102b - Made this simpler, and fixed attack_finished check
void() bot_nail1 =[ 15, bot_nail2 ] {bot_strafe(); bot_fire_nailgun(); self.effects = self.effects | EF_MUZZLEFLASH; SuperDamageSound();};
void() bot_nail2 =[ 16, bot_run1 ] {bot_strafe();
if (visible(self.enemy) && self.ammo_nails > 0 && self.enemy.health > 0)
bot_attack();
else
bot_run1(); };
void() th_respawn =
{
self.think = respawn_bot;
self.nextthink = time + 1;
};
void() bot_pain1 =[ 10, bot_pain2 ] {};
void() bot_pain2 =[ 11, bot_pain3 ] {};
void() bot_pain3 =[ 12, bot_pain4 ] {};
void() bot_pain4 =[ 13, bot_pain5 ] {};
void() bot_pain5 =[ 14, bot_run1 ] {};
void() bot_painb1 =[ 10, bot_painb2 ] {};
void() bot_painb2 =[ 11, bot_painb3 ] {};
void() bot_painb3 =[ 12, bot_painb4 ] {};
void() bot_painb4 =[ 13, bot_run1 ] {};
void() bot_die1 =[ 33, bot_die2 ] {};
void() bot_die2 =[ 34, bot_die3 ] {};
void() bot_die3 =[ 35, bot_die4 ] {self.solid = SOLID_NOT; DropBackpack(); };
void() bot_die4 =[ 36, bot_die5 ] {};
void() bot_die5 =[ 37, bot_die6 ] {};
void() bot_die6 =[ 38, bot_die7 ] {};
void() bot_die7 =[ 39, bot_die8 ] {};
void() bot_die8 =[ 40, bot_die9 ] {};
void() bot_die9 =[ 41, bot_die10 ] {};
void() bot_die10 =[ 42, bot_die11 ] {};
void() bot_die11 =[ 43, th_respawn ] {};
void() bot_dieb1 =[ 44, bot_dieb2 ] {};
void() bot_dieb2 =[ 45, bot_dieb3 ] {};
void() bot_dieb3 =[ 46, bot_dieb4 ] {self.solid = SOLID_NOT; DropBackpack(); };
void() bot_dieb4 =[ 47, bot_dieb5 ] {};
void() bot_dieb5 =[ 48, bot_dieb6 ] {};
void() bot_dieb6 =[ 49, bot_dieb7 ] {};
void() bot_dieb7 =[ 50, bot_dieb8 ] {};
void() bot_dieb8 =[ 51, bot_dieb9 ] {};
void() bot_dieb9 =[ 52, th_respawn ] {};
void() bot_diec1 =[ 53, bot_diec2 ] {};
void() bot_diec2 =[ 54, bot_diec3 ] {};
void() bot_diec3 =[ 55, bot_diec4 ] {self.solid = SOLID_NOT; DropBackpack(); };
void() bot_diec4 =[ 56, bot_diec5 ] {};
void() bot_diec5 =[ 57, bot_diec6 ] {};
void() bot_diec6 =[ 58, bot_diec7 ] {};
void() bot_diec7 =[ 59, bot_diec8 ] {};
void() bot_diec8 =[ 60, bot_diec9 ] {};
void() bot_diec9 =[ 61, bot_diec10 ] {};
void() bot_diec10 =[ 62, bot_diec11 ] {};
void() bot_diec11 =[ 63, bot_diec12 ] {};
void() bot_diec12 =[ 64, bot_diec13 ] {};
void() bot_diec13 =[ 65, bot_diec14 ] {};
void() bot_diec14 =[ 66, bot_diec15 ] {};
void() bot_diec15 =[ 67, th_respawn ] {};
void() bot_died1 =[ 68, bot_died2 ] {};
void() bot_died2 =[ 69, bot_died3 ] {};
void() bot_died3 =[ 70, bot_died4 ] {self.solid = SOLID_NOT; DropBackpack(); };
void() bot_died4 =[ 71, bot_died5 ] {};
void() bot_died5 =[ 72, bot_died6 ] {};
void() bot_died6 =[ 73, bot_died7 ] {};
void() bot_died7 =[ 74, bot_died8 ] {};
void() bot_died8 =[ 75, bot_died9 ] {};
void() bot_died9 =[ 76, th_respawn ] {};
void() bot_diee1 =[ 77, bot_diee2 ] {};
void() bot_diee2 =[ 78, bot_diee3 ] {};
void() bot_diee3 =[ 79, bot_diee4 ] {self.solid = SOLID_NOT; DropBackpack(); };
void() bot_diee4 =[ 80, bot_diee5 ] {};
void() bot_diee5 =[81, bot_diee6 ] {};
void() bot_diee6 =[ 82, bot_diee7 ] {};
void() bot_diee7 =[ 83, bot_diee8 ] {};
void() bot_diee8 =[ 84, bot_diee9 ] {};
void() bot_diee9 =[ 85, th_respawn ] {};
void() BotPainSound =
{
local float rs;
if (self.health < 0)
return;
rs = rint((random() * 5) + 1);
self.noise = "";
if (rs == 1)
self.noise = "player/pain1.wav";
else if (rs == 2)
self.noise = "player/pain2.wav";
else if (rs == 3)
self.noise = "player/pain3.wav";
else if (rs == 4)
self.noise = "player/pain4.wav";
else if (rs == 5)
self.noise = "player/pain5.wav";
else
self.noise = "player/pain6.wav";
sound (self, CHAN_VOICE, self.noise, 1, ATTN_NORM);
return;
};
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void(entity attacker, float damage) bot_pain =
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
{
//helps with LAN packet crashes
if (random() < 0.5) //Go into pain frames less often
return;
// POX - get mad at attacker - even other bots (usually)
if (attacker != self.enemy && attacker != world)
{
self.enemy = attacker;
bot_FoundTarget();
}
if (self.pain_finished > time)
return;
if (random() < 0.35)
return;
BotPainSound(); //POX v1.11 - this is only for shooting type pain
self.pain_finished = time + 2;
if (self.weapon == IT_ROCKET_LAUNCHER)
bot_painb1();
else
bot_pain1();
};
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void() gib_bot =
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
{
//POX - make a nice gib in Gib mode
if (deathmatch & DM_GIB)
self.health = self.health - 40;
//POX-changed self to head model for multi skin support
ThrowGib ("progs/gib3.mdl", self.health);
ThrowGib ("progs/gib1.mdl", self.health);
ThrowGib ("progs/gib2.mdl", self.health);
setmodel (self, "progs/h_player.mdl");
setsize (self, '0 0 0', '0 0 0');
self.deadflag = DEAD_DEAD;
self.think = th_respawn;
self.nextthink = time + 1;
if (damage_attacker.classname == "teledeath")
{
sound (self, CHAN_VOICE, "player/teledth1.wav", 1, ATTN_NONE);
return;
}
if (damage_attacker.classname == "teledeath2")
{
sound (self, CHAN_VOICE, "player/teledth1.wav", 1, ATTN_NONE);
return;
}
if (random() < 0.5)
sound (self, CHAN_VOICE, "player/gib.wav", 1, ATTN_NONE);
else
sound (self, CHAN_VOICE, "player/udeath.wav", 1, ATTN_NONE);
};
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void() bot_die =
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
{
local float i;
// we're turning the bot off here and playing his death scene
self.invisible_finished = 0;
self.invisible_time = 0;
self.skin = self.skinsave; //incase died as eyes
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;
//POX - change back to proper skin and get death model (incase he's invisible)
self.modelindex = modelindex_death;
self.frame = 44;
//POX - visible weapon toss
ThrowWeapon(self.weapon);
//POX check for Gib Mode as well as a regular gib
if (self.health < -40 || (deathmatch & DM_GIB))
{
gib_bot();
return;
}
DeathSound();
i = floor(random() * 5);
if (i == 0)
bot_die1();
else if (i == 1)
bot_dieb1();
else if (i == 2)
bot_diec1();
else if (i == 3)
bot_died1();
else
bot_diee1();
};
//POX a few more attacks added for second triggers
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void() bot_attack =
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
{
// this routine decides which animation sequence to play
//self.think = self.th_walk;
//self.nextthink = time + 0.05;
if (time < self.attack_finished)
{
bot_run1 ();
return;
}
bot_check_ammo();
if (self.weapon == IT_TSHOT)
{
self.attack_finished = time + 0.4;
bot_shot1();
}
//POX - Impact grenade (ComboGun Second Trigger)
else if (self.weapon == IT_COMBOGUN && random() > 0.4 && self.ammo_rockets > 1 && self.pball_finish < time && range(self.enemy) == RANGE_NEAR)
{
self.pball_finish = time + 0.9;
self.which_ammo = 1;//POX - needed for proper obituary
bot_gren1();
}
else if (self.weapon == IT_COMBOGUN)
{
self.attack_finished = time + 0.5;
self.which_ammo = 0;//POX - needed for proper obituary
bot_shot1();
}
//POX - MegaPlasma Burst (Plasma Gun Second Trigger)
else if (self.weapon == IT_PLASMAGUN && (range(self.enemy) != RANGE_MELEE) && self.ammo_cells > 9 && random() > 0.6)
{
self.attack_finished = time + 1.9;
bot_gren1();
}
else if (self.weapon == IT_PLASMAGUN)
{
self.attack_finished = time + 0.1;
bot_nail1();
}
else if (self.weapon == IT_SUPER_NAILGUN)
{
self.attack_finished = time + 0.1;
bot_nail1();
}
else if (self.weapon == IT_GRENADE_LAUNCHER)
{
self.attack_finished = time + 0.8;
bot_gren1();
}
else if (self.weapon == IT_ROCKET_LAUNCHER)
{
self.attack_finished = time + 0.7;
bot_rock1();
}
};
//POX - I split the spawning functions from this main file (see botspawn.qc)