/* 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 |= 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 &= ~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 &= ~FL_ONGROUND; self.velocity = self.velocity + v_right * 300; } else { makevectors(self.angles); 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 &= ~FL_ONGROUND; self.velocity = self.velocity + v_right * 300; } else { makevectors(self.angles); 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 &= ~FL_ONGROUND; self.velocity = v_forward*-460; } }; // ----------------------------------------- void() jump_forward = // ----------------------------------------- { // propels him into the air if (!(self.flags & FL_ONGROUND)) return; 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 &= ~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 &= ~(IT_SHELLS | IT_NAILS | IT_ROCKETS | IT_CELLS); if (self.weapon == IT_TSHOT) { self.currentammo = self.ammo_shells; self.items |= IT_SHELLS; } else if (self.weapon == IT_COMBOGUN) { self.currentammo = self.ammo_shells; self.items |= IT_SHELLS; } else if (self.weapon == IT_PLASMAGUN) { self.currentammo = self.ammo_cells; self.items |= IT_CELLS; } else if (self.weapon == IT_SUPER_NAILGUN) { self.currentammo = self.ammo_nails; self.items |= IT_NAILS; } else if (self.weapon == IT_GRENADE_LAUNCHER) { self.currentammo = self.ammo_rockets; self.items |= IT_ROCKETS; } else if (self.weapon == IT_ROCKET_LAUNCHER) { self.currentammo = self.ammo_rockets; 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 |= 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 |= 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 |= 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 |= 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 |= 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 |= 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 |= 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 |= 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 |= 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 &= ~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)