1 /***********************************************
\r
3 * FrikBot General AI *
\r
4 * "The I'd rather be playing Quake AI" *
\r
6 ***********************************************/
\r
10 This program is in the Public Domain. My crack legal
\r
11 team would like to add:
\r
13 RYAN "FRIKAC" SMITH IS PROVIDING THIS SOFTWARE "AS IS"
\r
14 AND MAKES NO WARRANTY, EXPRESS OR IMPLIED, AS TO THE
\r
15 ACCURACY, CAPABILITY, EFFICIENCY, MERCHANTABILITY, OR
\r
16 FUNCTIONING OF THIS SOFTWARE AND/OR DOCUMENTATION. IN
\r
17 NO EVENT WILL RYAN "FRIKAC" SMITH BE LIABLE FOR ANY
\r
18 GENERAL, CONSEQUENTIAL, INDIRECT, INCIDENTAL,
\r
19 EXEMPLARY, OR SPECIAL DAMAGES, EVEN IF RYAN "FRIKAC"
\r
20 SMITH HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
\r
21 DAMAGES, IRRESPECTIVE OF THE CAUSE OF SUCH DAMAGES.
\r
23 You accept this software on the condition that you
\r
24 indemnify and hold harmless Ryan "FrikaC" Smith from
\r
25 any and all liability or damages to third parties,
\r
26 including attorney fees, court costs, and other
\r
27 related costs and expenses, arising out of your use
\r
28 of this software irrespective of the cause of said
\r
31 The export from the United States or the subsequent
\r
32 reexport of this software is subject to compliance
\r
33 with United States export control and munitions
\r
34 control restrictions. You agree that in the event you
\r
35 seek to export this software, you assume full
\r
36 responsibility for obtaining all necessary export
\r
37 licenses and approvals and for assuring compliance
\r
38 with applicable reexport restrictions.
\r
40 Any reproduction of this software must contain
\r
41 this notice in its entirety.
\r
46 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
\r
50 checks to see if an entity is on the bot's stack
\r
52 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
\r
55 float(entity scot) target_onstack =
\r
59 else if (self.target1 == scot)
\r
61 else if (self.target2 == scot)
\r
63 else if (self.target3 == scot)
\r
65 else if (self.target4 == scot)
\r
72 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
\r
76 adds a new entity to the stack, since it's a
\r
77 LIFO stack, this will be the bot's new target1
\r
79 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
\r
82 void(entity ent) target_add =
\r
86 if (target_onstack(ent))
\r
88 self.target4 = self.target3;
\r
89 self.target3 = self.target2;
\r
90 self.target2 = self.target1;
\r
92 self.search_time = time + 5;
\r
97 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
\r
101 Removes an entity from the bot's target stack.
\r
102 The stack will empty everything up to the object
\r
103 So if you have target2 item_health, target1
\r
104 waypoint, and you drop the health, the waypoint
\r
107 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
\r
110 void(entity ent) target_drop =
\r
114 tg = target_onstack(ent);
\r
117 self.target1 = self.target2;
\r
118 self.target2 = self.target3;
\r
119 self.target3 = self.target4;
\r
120 self.target4 = world;
\r
124 self.target1 = self.target3;
\r
125 self.target2 = self.target4;
\r
126 self.target3 = self.target4 = world;
\r
130 self.target1 = self.target4;
\r
131 self.target2 = self.target3 = self.target4 = world;
\r
134 self.target1 = self.target2 = self.target3 = self.target4 = world;
\r
135 self.search_time = time + 5;
\r
139 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
\r
143 Bot has lost its target.
\r
145 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
\r
148 void(entity targ, float success) bot_lost =
\r
154 if (targ.classname == "waypoint")
\r
155 targ.b_sound = targ.b_sound - (targ.b_sound & ClientBitFlag(self.b_clientno));
\r
157 // find a new route
\r
160 self.target1 = self.target2 = self.target3 = self.target4 = world;
\r
161 self.last_way = FindWayPoint(self.current_way);
\r
163 self.b_aiflags = 0;
\r
167 if (targ.classname == "item_artifact_invisibility")
\r
168 if (self.items & 524288)
\r
169 bot_start_topic(3);
\r
171 if (targ.flags & FL_ITEM)
\r
173 if (targ.model == string_null)
\r
174 targ._last = world;
\r
181 if (targ.classname != "player")
\r
182 targ.search_time = time + 5;
\r
186 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
\r
190 decide if my most immediate target should be
\r
193 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
\r
195 void(entity targ) bot_check_lost =
\r
198 dist = realorigin(targ) - self.origin;
\r
203 // waypoints and items are lost if you get close enough to them
\r
205 else if (targ.flags & FL_ITEM)
\r
207 if (vlen(targ.origin - self.origin) < 32)
\r
208 bot_lost(targ, TRUE);
\r
209 else if (targ.model == string_null)
\r
210 bot_lost(targ, TRUE);
\r
212 else if (targ.classname == "waypoint")
\r
214 if (!(self.b_aiflags & (AI_SNIPER | AI_AMBUSH)))
\r
216 if (self.b_aiflags & AI_RIDE_TRAIN)
\r
218 if (vlen(targ.origin - self.origin) < 48)
\r
219 bot_lost(targ, TRUE);
\r
221 else if (self.b_aiflags & AI_PRECISION)
\r
223 if (vlen(targ.origin - self.origin) < 24)
\r
224 bot_lost(targ, TRUE);
\r
226 else if (vlen(targ.origin - self.origin) < 32)
\r
227 bot_lost(targ, TRUE);
\r
228 else if (self.b_aiflags & AI_CARELESS) // Electro - better for jumppads
\r
230 if (vlen(targ.origin - self.origin) < 128)
\r
231 bot_lost(targ, TRUE);
\r
235 else if (targ.classname == "temp_waypoint")
\r
237 if (vlen(targ.origin - self.origin) < 32)
\r
238 bot_lost(targ, TRUE);
\r
240 else if (targ.classname == "player")
\r
242 if (targ.health <= 0)
\r
243 bot_lost(targ, TRUE);
\r
244 else if ((coop) || (teamplay && targ.team == self.team))
\r
246 if (targ.target1.classname == "player")
\r
248 if (!targ.target1.ishuman)
\r
249 bot_lost(targ, TRUE);
\r
251 else if (targ.teleport_time > time)
\r
253 // try not to telefrag teammates
\r
254 self.keys = self.keys & 960;
\r
256 else if (vlen(targ.origin - self.origin) < 128)
\r
258 if (vlen(targ.origin - self.origin) < 48)
\r
259 frik_walkmove(self.origin - targ.origin);
\r
262 self.keys = self.keys & 960;
\r
263 bot_start_topic(4);
\r
265 self.search_time = time + 5; // never time out
\r
267 else if (!fisible(targ))
\r
268 bot_lost(targ, FALSE);
\r
270 else if (waypoint_mode > WM_LOADED)
\r
272 if (vlen(targ.origin - self.origin) < 128)
\r
274 bot_lost(targ, TRUE);
\r
279 // buttons are lost of their frame changes
\r
280 else if (targ.classname == "func_button")
\r
284 bot_lost(targ, TRUE);
\r
285 if (self.enemy == targ)
\r
286 self.enemy = world;
\r
287 //if (self.target1)
\r
288 // bot_get_path(self.target1, TRUE);
\r
292 // trigger_multiple style triggers are lost if their thinktime changes
\r
293 else if ((targ.movetype == MOVETYPE_NONE) && (targ.solid == SOLID_TRIGGER))
\r
295 if (targ.nextthink >= time)
\r
297 bot_lost(targ, TRUE);
\r
298 //if (self.target1)
\r
299 // bot_get_path(self.target1, TRUE);
\r
302 // lose any target way above the bot's head
\r
303 // FIXME: if the bot can fly in your mod..
\r
304 if ((targ.origin_z - self.origin_z) > 64)
\r
306 dist = targ.origin - self.origin;
\r
308 if (vlen(dist) < 32)
\r
309 if (self.flags & FL_ONGROUND)
\r
310 if(!frik_recognize_plat(FALSE))
\r
311 bot_lost(targ, FALSE);
\r
313 else if (targ.classname == "train")
\r
315 if (frik_recognize_plat(FALSE))
\r
316 bot_lost(targ, TRUE);
\r
318 // targets are lost if the bot's search time has expired
\r
319 if (time > self.search_time)
\r
320 bot_lost(targ, FALSE);
\r
325 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
\r
329 This is a 0.10 addition. Handles any action
\r
332 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
\r
335 void() bot_handle_ai =
\r
340 // handle ai flags -- note, not all aiflags are handled
\r
341 // here, just those that perform some sort of action
\r
343 // wait is used by the ai to stop the bot until his search time expires / or route changes
\r
345 if (self.b_aiflags & AI_WAIT)
\r
346 self.keys = self.keys & 960;
\r
348 if (self.b_aiflags & AI_DOORFLAG) // was on a door when spawned
\r
351 self = self.last_way;
\r
352 if (!frik_recognize_plat(FALSE)) // if there is nothing there now
\r
354 newt = FindThing("door"); // this is likely the door responsible (crossfingers)
\r
357 if (self.b_aiflags & AI_DOOR_NO_OPEN)
\r
359 if (newt.nextthink)
\r
360 self.keys = self.keys & 960; // wait until it closes
\r
363 bot_lost(self.last_way, FALSE);
\r
368 if (newt.targetname)
\r
370 newt = find(world, target, newt.targetname);
\r
371 if (newt.health > 0)
\r
374 bot_weapon_switch(1);
\r
378 // target_drop(self.last_way);
\r
380 // bot_get_path(newt, TRUE);
\r
383 self.b_aiflags = self.b_aiflags - AI_DOORFLAG;
\r
390 if (self.b_aiflags & AI_JUMP)
\r
392 if (self.flags & FL_ONGROUND)
\r
395 self.b_aiflags = self.b_aiflags - AI_JUMP;
\r
398 else if (self.b_aiflags & AI_SUPER_JUMP)
\r
400 if (self.weapon != WEP_LASER)
\r
402 else if (self.flags & FL_ONGROUND)
\r
404 self.b_aiflags = self.b_aiflags - AI_SUPER_JUMP;
\r
405 if (bot_can_rj(self))
\r
408 self.v_angle_x = self.b_angle_x = 80;
\r
409 self.button0 = TRUE;
\r
412 bot_lost(self.target1, FALSE);
\r
416 if (self.b_aiflags & AI_SURFACE)
\r
418 if (self.waterlevel > 2)
\r
420 self.keys = KEY_MOVEUP;
\r
421 self.button2 = TRUE; // swim!
\r
424 self.b_aiflags = self.b_aiflags - AI_SURFACE;
\r
426 if (self.b_aiflags & AI_RIDE_TRAIN)
\r
428 // simple, but effective
\r
429 // this can probably be used for a lot of different
\r
430 // things, not just trains (door elevators come to mind)
\r
432 self = self.last_way;
\r
434 if (!frik_recognize_plat(FALSE)) // if there is nothing there now
\r
437 self.keys = self.keys & 960;
\r
442 if (frik_recognize_plat(FALSE))
\r
444 v = realorigin(trace_ent) + trace_ent.origin - self.origin;
\r
447 self.keys = self.keys & 960;
\r
450 self.b_aiflags = self.b_aiflags | AI_PRECISION;
\r
451 self.keys = frik_KeysForDir(v);
\r
456 if (self.b_aiflags & AI_PLAT_BOTTOM)
\r
458 newt = FindThing("plat");
\r
459 if (newt.state != 1)
\r
461 v = self.origin - realorigin(newt);
\r
464 self.keys = self.keys & 960;
\r
469 self.b_aiflags = self.b_aiflags - AI_PLAT_BOTTOM;
\r
471 if (self.b_aiflags & AI_DIRECTIONAL)
\r
473 if ((normalize(self.last_way.origin - self.origin) * self.b_dir) > 0.4)
\r
475 self.b_aiflags = self.b_aiflags - AI_DIRECTIONAL;
\r
476 bot_lost(self.target1, TRUE);
\r
479 if (self.b_aiflags & AI_SNIPER)
\r
481 self.b_aiflags = (self.b_aiflags | AI_WAIT | AI_PRECISION) - AI_SNIPER;
\r
482 // FIXME: Add a switch to wep command
\r
483 // FIXME: increase delay?
\r
485 if (self.b_aiflags & AI_AMBUSH)
\r
487 self.b_aiflags = (self.b_aiflags | AI_WAIT) - AI_AMBUSH;
\r
488 // FIXME: Add a switch to wep command
\r
489 // FIXME: increase delay?
\r
495 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
\r
499 Bot will follow a route generated by the
\r
500 begin_route set of functions in bot_way.qc.
\r
501 This code, while it works pretty well, can get
\r
504 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
\r
509 local entity jj, tele;
\r
511 bot_check_lost(self.target1);
\r
517 if (target_onstack(self.last_way))
\r
518 return; // old waypoint still being hunted
\r
520 jj = FindRoute(self.last_way);
\r
523 // this is an ugly hack
\r
524 if (self.target1.current_way != self.last_way)
\r
526 if (self.target1.classname != "temp_waypoint")
\r
527 if (self.target1.classname != "player")
\r
528 bot_lost(self.target1, FALSE);
\r
534 // update the bot's special ai features
\r
536 // Readahed types are AI conditions to perform while heading to a waypoint
\r
537 // point types are AI flags that should be executed once reaching a waypoint
\r
539 self.b_aiflags = (jj.b_aiflags & AI_READAHEAD_TYPES) | (self.last_way.b_aiflags & AI_POINT_TYPES);
\r
543 if (CheckLinked(self.last_way, jj) == 2) // waypoints are telelinked
\r
545 tele = FindThing("trigger_teleport"); // this is probbly the teleport responsible
\r
548 traceline(self.last_way.origin, jj.origin, FALSE, self); // check for blockage
\r
549 if (trace_fraction != 1)
\r
551 if (trace_ent.classname == "door" && !(self.b_aiflags & AI_DOOR_NO_OPEN)) // a door blocks the way
\r
553 // linked doors fix
\r
554 if (trace_ent.owner)
\r
555 trace_ent = trace_ent.owner;
\r
556 if ((trace_ent.health > 0) && (self.enemy == world))
\r
558 self.enemy = trace_ent;
\r
559 bot_weapon_switch(1);
\r
560 self.b_aiflags = self.b_aiflags | AI_BLIND; // nick knack paddy hack
\r
562 else if (trace_ent.targetname)
\r
564 tele = find(world, target, trace_ent.targetname);
\r
565 if (tele.health > 0)
\r
568 bot_weapon_switch(1);
\r
572 // target_drop(jj);
\r
574 // bot_get_path(tele, TRUE);
\r
575 self.b_aiflags = self.b_aiflags | AI_BLIND; // give a bot a bone
\r
580 else if (trace_ent.classname == "func_wall")
\r
583 bot_lost(self.target1, FALSE);
\r
588 // this is used for AI_DRIECTIONAL
\r
589 self.b_dir = normalize(jj.origin - self.last_way.origin);
\r
591 self.last_way = jj;
\r
596 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
\r
598 Bot Priority Look. What a stupid name. This is where
\r
599 the bot finds things it wants to kill/grab.
\r
601 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
\r
604 // 0 - 10 virtually ignore
\r
605 // 10 - 30 normal item range
\r
606 // 30 - 50 bot will consider this a target worth changing course for
\r
607 // 50 - 90 bot will hunt these as vital items
\r
609 // *!* Make sure you add code to bot_check_lost to remove the target *!*
\r
611 float(entity thing) priority_for_thing =
\r
615 // This is the most executed function in the bot. Careful what you do here.
\r
617 if (thing.flags & FL_ITEM && thing.model != string_null && thing.search_time < time)
\r
620 if (thing._last != self)
\r
622 else if (thing.classname == "item_strength") // IT_STRENGTH
\r
624 else if (thing.classname == "item_invincible") // IT_INVINCIBLE
\r
626 //else if (thing.classname == "item_speed") // IT_SPEED
\r
628 //else if (thing.classname == "item_slowmo") // IT_SLOWMO
\r
630 else if (thing.classname == "item_health")
\r
632 if (thing.spawnflags & 2)
\r
634 if (self.health < 40)
\r
635 thisp = thisp + 50;
\r
637 else if (thing.classname == "item_health1" || thing.classname == "item_health2")
\r
640 if (self.health < 40)
\r
641 thisp = thisp + 50;
\r
643 else if (thing.classname == "item_health100")
\r
646 if (self.health < 40)
\r
647 thisp = thisp + 50;
\r
649 else if (thing.classname == "item_armor1")
\r
652 if (self.armorvalue < 100)
\r
653 thisp = thisp + 25;
\r
655 else if (thing.classname == "item_armor25")
\r
658 if (self.armorvalue < 100)
\r
659 thisp = thisp + 25;
\r
661 else if (thing.classname == "weapon_shotgun")
\r
663 if (!(self.items & IT_SHOTGUN))
\r
666 else if (thing.classname == "weapon_uzi")
\r
668 if (!(self.items & IT_UZI))
\r
671 else if (thing.classname == "weapon_electro")
\r
673 if (!(self.items & IT_ELECTRO))
\r
676 else if (thing.classname == "weapon_grenadelauncher")
\r
678 if (!(self.items & IT_GRENADE_LAUNCHER))
\r
681 else if (thing.classname == "weapon_crylink")
\r
683 if (!(self.items & IT_CRYLINK))
\r
686 else if (thing.classname == "weapon_nex")
\r
688 if (!(self.items & IT_NEX)) // IT_LIGHTNING
\r
691 else if (thing.classname == "weapon_hagar")
\r
693 if (!(self.items & IT_HAGAR))
\r
696 else if (thing.classname == "weapon_rocketlauncher")
\r
698 if (!(self.items & IT_ROCKET_LAUNCHER))
\r
702 else if (thing.classname == "player")
\r
704 if (thing.health > 0)
\r
713 if (thing.target1.classname == "player")
\r
714 if (!thing.target1.ishuman)
\r
717 else if (teamplay && thing.team == self.team)
\r
720 if (thing.target1.classname == "player")
\r
727 else if (thing.classname == "waypoint")
\r
729 if (thing.b_aiflags & AI_SNIPER)
\r
731 else if (thing.b_aiflags & AI_AMBUSH)
\r
734 if (pointcontents(thing.origin) < -3)
\r
738 if (thing.current_way)
\r
740 // check to see if it's unreachable
\r
741 if (thing.current_way.items == -1)
\r
744 thisp = thisp + (13000 - thing.current_way.items) * 0.05;
\r
751 void(float scope) bot_look_for_crap =
\r
753 local entity foe, best;
\r
754 local float thatp, bestp, dist;
\r
757 foe = findradius(self.origin, 13000);
\r
759 foe = findradius(self.origin, 500);
\r
764 thatp = priority_for_thing(foe);
\r
773 dist = vlen(self.origin - foe.origin);
\r
779 if (!target_onstack(best))
\r
784 bot_get_path(best, FALSE);
\r
785 self.b_aiflags = self.b_aiflags | AI_WAIT;
\r
792 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
\r
796 Sets the bots look keys & b_angle to point at
\r
797 the target - used for fighting and just
\r
798 generally making the bot look good.
\r
800 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
\r
803 void() bot_angle_set =
\r
810 if (self.enemy.items & 524288)
\r
811 if (random() > 0.2)
\r
813 if (self.missile_speed == 0)
\r
814 self.missile_speed = 10000;
\r
815 if (self.enemy.solid == SOLID_BSP)
\r
817 view = (((self.enemy.absmin + self.enemy.absmax) * 0.5) - self.origin);
\r
821 h = vlen(self.enemy.origin - self.origin) / self.missile_speed;
\r
822 if (self.enemy.flags & FL_ONGROUND)
\r
823 view = self.enemy.velocity * h;
\r
825 view = (self.enemy.velocity - (sv_gravity * '0 0 1') * h) * h;
\r
826 view = self.enemy.origin + view;
\r
828 traceline(self.enemy.origin, view, FALSE, self);
\r
829 view = trace_endpos;
\r
831 if (self.weapon == WEP_GRENADE_LAUNCHER || self.weapon == WEP_ELECTRO || self.weapon == WEP_HAGAR || self.weapon == WEP_ROCKET_LAUNCHER)
\r
832 view = view - '0 0 22';
\r
834 view = normalize(view - self.origin);
\r
836 view = vectoangles(view);
\r
837 view_x = view_x * -1;
\r
838 self.b_angle = view;
\r
840 else if (self.target1)
\r
842 view = realorigin(self.target1);
\r
843 if (self.target1.flags & FL_ITEM)
\r
844 view = view + '0 0 48';
\r
845 view = view - (self.origin + self.view_ofs);
\r
846 view = vectoangles(view);
\r
847 view_x = view_x * -1;
\r
848 self.b_angle = view;
\r
851 self.b_angle_x = 0;
\r
852 // HACK HACK HACK HACK
\r
853 // The bot falls off ledges a lot because of "turning around"
\r
854 // so let the bot use instant turn around when not hunting a player
\r
855 if (self.b_skill == 3)
\r
857 self.keys = self.keys & 63;
\r
858 self.v_angle = self.b_angle;
\r
859 while (self.v_angle_x < -180)
\r
860 self.v_angle_x = self.v_angle_x + 360;
\r
861 while (self.v_angle_x > 180)
\r
862 self.v_angle_x = self.v_angle_x - 360;
\r
865 else if ((self.enemy == world || self.enemy.movetype == MOVETYPE_PUSH) && self.target1.classname != "player")
\r
867 self.keys = self.keys & 63;
\r
868 self.v_angle = self.b_angle;
\r
869 while (self.v_angle_x < -180)
\r
870 self.v_angle_x = self.v_angle_x + 360;
\r
871 while (self.v_angle_x > 180)
\r
872 self.v_angle_x = self.v_angle_x - 360;
\r
874 else if (self.b_skill < 2) // skill 2 handled in bot_phys
\r
876 if (self.b_angle_x > 180)
\r
877 self.b_angle_x = self.b_angle_x - 360;
\r
878 self.keys = self.keys & 63;
\r
880 if (angcomp(self.b_angle_y, self.v_angle_y) > 10)
\r
881 self.keys = self.keys | KEY_LOOKLEFT;
\r
882 else if (angcomp(self.b_angle_y, self.v_angle_y) < -10)
\r
883 self.keys = self.keys | KEY_LOOKRIGHT;
\r
884 if (angcomp(self.b_angle_x, self.v_angle_x) < -10)
\r
885 self.keys = self.keys | KEY_LOOKUP;
\r
886 else if (angcomp(self.b_angle_x, self.v_angle_x) > 10)
\r
887 self.keys = self.keys | KEY_LOOKDOWN;
\r
892 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
\r
896 This is the main ai loop. Though called every
\r
897 frame, the ai_time limits it's actual updating
\r
899 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
\r
901 float stagger_think;
\r
902 float intermission_running;
\r
906 // am I dead? Fire randomly until I respawn
\r
907 // health < 1 is used because fractional healths show up as 0 on normal player
\r
908 // status bars, and the mod probably already compensated for that
\r
910 if (self.health < 1)
\r
912 self.button0 = floor(random() * 2);
\r
915 self.b_aiflags = 0;
\r
917 self.target1 = self.target2 = self.target3 = self.target4 = self.enemy = world;
\r
918 self.last_way = world;
\r
922 // stagger the bot's AI out so they all don't think at the same time, causing game
\r
924 if (self.b_skill < 2)
\r
926 if (self.ai_time > time)
\r
929 self.ai_time = time + 0.05;
\r
932 if ((time - stagger_think) < (0.1 / bot_count))
\r
933 self.ai_time = self.ai_time + 0.1 / (2 * bot_count);
\r
938 if (intermission_running)
\r
939 bot_start_topic(7);
\r
940 stagger_think = time;
\r
942 // shut the bot's buttons off, various functions will turn them on by AI end
\r
948 // target1 is like goalentity in normal Quake monster AI.
\r
949 // it's the bot's most immediate target
\r
950 if (route_table == self)
\r
952 if (busy_waypoints <= 0)
\r
954 if (waypoint_mode < WM_EDITOR)
\r
955 bot_look_for_crap(TRUE);
\r
957 self.b_aiflags = 0;
\r
960 else if (self.target1)
\r
967 if (waypoint_mode < WM_EDITOR)
\r
969 if(self.route_failed)
\r
972 self.route_failed = 0;
\r
974 else if(!begin_route())
\r
976 bot_look_for_crap(FALSE);
\r
982 self.b_aiflags = AI_WAIT;
\r
987 // bot_angle_set points the bot at it's goal (self.enemy or target1)
\r
991 // fight my enemy. Enemy is probably a field QC coders will most likely use a lot
\r
992 // for their own needs, since it's unused on a normal player
\r
996 else if (random() < 0.2)
\r
997 if (random() < 0.2)
\r
998 bot_weapon_switch(-1);
\r
1001 // checks to see if bot needs to start going up for air
\r
1002 /* if (self.waterlevel > 2)
\r
1004 if (time > (self.air_finished - 2))
\r
1006 traceline (self.origin, self.origin + '0 0 6800', TRUE, self);
\r
1009 self.keys = KEY_MOVEUP;
\r
1010 self.button2 = TRUE; // swim!
\r
1011 return; // skip ai flags for now - this is life or death
\r
1016 // b_aiflags handling
\r
1019 if (self.b_aiflags)
\r
1022 // bot_chat(); // don't want chat to screw him up if he's rjing or something
\r