]> icculus.org git repositories - divverent/nexuiz.git/blob - qcsrc/gamec/bot_ai.c
disabled the 'has taken the lead' till it works
[divverent/nexuiz.git] / qcsrc / gamec / bot_ai.c
1 /***********************************************
2 *                                              *
3 *            FrikBot General AI                *
4 *     "The I'd rather be playing Quake AI"     *
5 *                                              *
6 ***********************************************/
7
8 /*
9
10 This program is in the Public Domain. My crack legal
11 team would like to add:
12
13 RYAN "FRIKAC" SMITH IS PROVIDING THIS SOFTWARE "AS IS"
14 AND MAKES NO WARRANTY, EXPRESS OR IMPLIED, AS TO THE
15 ACCURACY, CAPABILITY, EFFICIENCY, MERCHANTABILITY, OR
16 FUNCTIONING OF THIS SOFTWARE AND/OR DOCUMENTATION. IN
17 NO EVENT WILL RYAN "FRIKAC" SMITH BE LIABLE FOR ANY
18 GENERAL, CONSEQUENTIAL, INDIRECT, INCIDENTAL,
19 EXEMPLARY, OR SPECIAL DAMAGES, EVEN IF RYAN "FRIKAC"
20 SMITH HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
21 DAMAGES, IRRESPECTIVE OF THE CAUSE OF SUCH DAMAGES.
22
23 You accept this software on the condition that you
24 indemnify and hold harmless Ryan "FrikaC" Smith from
25 any and all liability or damages to third parties,
26 including attorney fees, court costs, and other
27 related costs and expenses, arising out of your use
28 of this software irrespective of the cause of said
29 liability.
30
31 The export from the United States or the subsequent
32 reexport of this software is subject to compliance
33 with United States export control and munitions
34 control restrictions. You agree that in the event you
35 seek to export this software, you assume full
36 responsibility for obtaining all necessary export
37 licenses and approvals and for assuring compliance
38 with applicable reexport restrictions.
39
40 Any reproduction of this software must contain
41 this notice in its entirety.
42
43 */
44
45 /*
46 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
47
48 target_onstack
49
50 checks to see if an entity is on the bot's stack
51
52 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
53 */
54
55 float(entity scot) target_onstack =
56 {
57         if (scot == world)
58                 return FALSE;
59         else if (self.target1 == scot)
60                 return 1;
61         else if (self.target2 == scot)
62                 return 2;
63         else if (self.target3 == scot)
64                 return 3;
65         else if (self.target4 == scot)
66                 return 4;
67         else
68                 return FALSE;
69 };
70
71 /*
72 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
73
74 target_add
75
76 adds a new entity to the stack, since it's a
77 LIFO stack, this will be the bot's new target1
78
79 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
80 */
81
82 void(entity ent) target_add =
83 {
84         if (ent == world)
85                 return;
86         if (target_onstack(ent))
87                 return;
88         self.target4 = self.target3;
89         self.target3 = self.target2;
90         self.target2 = self.target1;
91         self.target1 = ent;
92         self.search_time = time + 5;
93 };
94
95
96 /*
97 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
98
99 target_drop
100
101 Removes an entity from the bot's target stack.
102 The stack will empty everything up to the object
103 So if you have target2 item_health, target1
104 waypoint, and you drop the health, the waypoint
105 is gone too.
106
107 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
108 */
109
110 void(entity ent) target_drop =
111 {
112         local float tg;
113
114         tg = target_onstack(ent);
115         if (tg == 1)
116         {
117                 self.target1 = self.target2;
118                 self.target2 = self.target3;
119                 self.target3 = self.target4;
120                 self.target4 = world;
121         }
122         else if (tg == 2)
123         {
124                 self.target1 = self.target3;
125                 self.target2 = self.target4;
126                 self.target3 = self.target4 = world;
127         }
128         else if (tg == 3)
129         {
130                 self.target1 = self.target4;
131                 self.target2 = self.target3 = self.target4 = world;
132         }
133         else if (tg == 4)
134                 self.target1 = self.target2 = self.target3 = self.target4 = world;
135         self.search_time = time + 5;
136 };
137
138 /*
139 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
140
141 bot_lost
142
143 Bot has lost its target.
144
145 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
146 */
147
148 void(entity targ, float success) bot_lost =
149 {
150         if (!targ)
151                 return;
152
153         target_drop(targ);
154         if (targ.classname == "waypoint")
155                 targ.b_sound = targ.b_sound - (targ.b_sound & ClientBitFlag(self.b_clientno));
156
157         // find a new route
158         if (!success)
159         {
160                 self.target1 = self.target2 = self.target3 = self.target4 = world;
161                 self.last_way = FindWayPoint(self.current_way);
162                 ClearMyRoute();
163                 self.b_aiflags = 0;
164         }
165         else
166         {
167                 if (targ.classname == "item_artifact_invisibility")
168                         if (self.items & 524288)
169                                 bot_start_topic(3);
170
171                 if (targ.flags & FL_ITEM)
172                 {
173                         if (targ.model == string_null)
174                                 targ._last = world;
175                         else
176                                 targ._last = self;
177                 }
178         }
179
180
181         if (targ.classname != "player")
182                 targ.search_time = time + 5;
183 };
184
185 /*
186 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
187
188 bot_check_lost
189
190 decide if my most immediate target should be
191 removed.
192
193 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
194 */
195 void(entity targ) bot_check_lost =
196 {
197         local vector dist;
198         dist = realorigin(targ) - self.origin;
199         dist_z = 0;
200         if (targ == world)
201                 return;
202
203         // waypoints and items are lost if you get close enough to them
204
205         else if (targ.flags & FL_ITEM)
206         {
207                 if (vlen(targ.origin - self.origin) < 32)
208                         bot_lost(targ, TRUE);
209                 else if (targ.model == string_null)
210                         bot_lost(targ, TRUE);
211         }
212         else if (targ.classname == "waypoint")
213         {
214                 if (!(self.b_aiflags & (AI_SNIPER | AI_AMBUSH)))
215                 {
216                         if (self.b_aiflags & AI_RIDE_TRAIN)
217                         {
218                                 if (vlen(targ.origin - self.origin) < 48)
219                                         bot_lost(targ, TRUE);
220                         }
221                         else if (self.b_aiflags & AI_PRECISION)
222                         {
223                                 if (vlen(targ.origin - self.origin) < 24)
224                                         bot_lost(targ, TRUE);
225                         }
226                         else if (vlen(targ.origin - self.origin) < 32)
227                                 bot_lost(targ, TRUE);
228                         else if (self.b_aiflags & AI_CARELESS) // Electro - better for jumppads
229                         {
230                                 if (vlen(targ.origin - self.origin) < 128)
231                                         bot_lost(targ, TRUE);
232                         }
233                 }
234         }
235         else if (targ.classname == "temp_waypoint")
236         {
237                 if (vlen(targ.origin - self.origin) < 32)
238                         bot_lost(targ, TRUE);
239         }
240         else if (targ.classname == "player")
241         {
242                 if (targ.health <= 0)
243                         bot_lost(targ, TRUE);
244                 else if ((coop) || (teamplay && targ.team == self.team))
245                 {
246                         if (targ.target1.classname == "player")
247                         {
248                                 if (!targ.target1.ishuman)
249                                         bot_lost(targ, TRUE);
250                         }
251                         else if (targ.teleport_time > time)
252                         {
253                                 // try not to telefrag teammates
254                                 self.keys = self.keys & 960;
255                         }
256                         else if (vlen(targ.origin - self.origin) < 128)
257                         {
258                                 if (vlen(targ.origin - self.origin) < 48)
259                                         frik_walkmove(self.origin - targ.origin);
260                                 else
261                                 {
262                                         self.keys = self.keys & 960;
263                                         bot_start_topic(4);
264                                 }
265                                 self.search_time = time + 5; // never time out
266                         }
267                         else if (!fisible(targ))
268                                 bot_lost(targ, FALSE);
269                 }
270                 else if (waypoint_mode > WM_LOADED)
271                 {
272                         if (vlen(targ.origin - self.origin) < 128)
273                         {
274                                 bot_lost(targ, TRUE);
275                         }
276                 }
277         }
278
279         // buttons are lost of their frame changes
280         else if (targ.classname == "func_button")
281         {
282                 if (targ.frame)
283                 {
284                         bot_lost(targ, TRUE);
285                         if (self.enemy == targ)
286                                 self.enemy = world;
287                         //if (self.target1)
288                         //      bot_get_path(self.target1, TRUE);
289
290                 }
291         }
292         // trigger_multiple style triggers are lost if their thinktime changes
293         else if ((targ.movetype == MOVETYPE_NONE) && (targ.solid == SOLID_TRIGGER))
294         {
295                 if (targ.nextthink >= time)
296                 {
297                         bot_lost(targ, TRUE);
298                         //if (self.target1)
299                         //      bot_get_path(self.target1, TRUE);
300                 }
301         }
302         // lose any target way above the bot's head
303         // FIXME: if the bot can fly in your mod..
304         if ((targ.origin_z - self.origin_z) > 64)
305         {
306                 dist = targ.origin - self.origin;
307                 dist_z = 0;
308                 if (vlen(dist) < 32)
309                         if (self.flags & FL_ONGROUND)
310                                 if(!frik_recognize_plat(FALSE))
311                                         bot_lost(targ, FALSE);
312         }
313         else if (targ.classname == "train")
314         {
315                 if (frik_recognize_plat(FALSE))
316                         bot_lost(targ, TRUE);
317         }
318         // targets are lost if the bot's search time has expired
319         if (time > self.search_time)
320                 bot_lost(targ, FALSE);
321 };
322
323
324 /*
325 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
326
327 bot_handle_ai
328
329 This is a 0.10 addition. Handles any action
330 based b_aiflags.
331
332 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
333 */
334
335 void() bot_handle_ai =
336 {
337         local entity newt;
338         local vector v;
339
340         // handle ai flags -- note, not all aiflags are handled
341         // here, just those that perform some sort of action
342
343         // wait is used by the ai to stop the bot until his search time expires / or route changes
344
345         if (self.b_aiflags & AI_WAIT)
346                 self.keys = self.keys & 960;
347
348         if (self.b_aiflags & AI_DOORFLAG) // was on a door when spawned
349         {
350                 b_temp3 = self;
351                 self = self.last_way;
352                 if (!frik_recognize_plat(FALSE)) // if there is nothing there now
353                 {
354                         newt = FindThing("door"); // this is likely the door responsible (crossfingers)
355                         self = b_temp3;
356
357                         if (self.b_aiflags & AI_DOOR_NO_OPEN)
358                         {
359                                 if (newt.nextthink)
360                                         self.keys = self.keys & 960; // wait until it closes
361                                 else
362                                 {
363                                         bot_lost(self.last_way, FALSE);
364                                 }
365                         }
366                         else
367                         {
368                                 if (newt.targetname)
369                                 {
370                                         newt = find(world, target, newt.targetname);
371                                         if (newt.health > 0)
372                                         {
373                                                 self.enemy = newt;
374                                                 bot_weapon_switch(1);
375                                         }
376                                         else
377                                         {
378                                         //      target_drop(self.last_way);
379                                                 target_add(newt);
380                                         //      bot_get_path(newt, TRUE);
381                                         }
382                                 }
383                                 self.b_aiflags = self.b_aiflags - AI_DOORFLAG;
384                         }
385                 }
386                 else
387                         self = b_temp3;
388         }
389
390         if (self.b_aiflags & AI_JUMP)
391         {
392                 if (self.flags & FL_ONGROUND)
393                 {
394                         bot_jump();
395                         self.b_aiflags = self.b_aiflags - AI_JUMP;
396                 }
397         }
398         else if (self.b_aiflags & AI_SUPER_JUMP)
399         {
400                 if (self.weapon != WEP_LASER)
401                         self.impulse = 7;
402                 else if (self.flags & FL_ONGROUND)
403                 {
404                         self.b_aiflags = self.b_aiflags - AI_SUPER_JUMP;
405                         if (bot_can_rj(self))
406                         {
407                                 bot_jump();
408                                 self.v_angle_x = self.b_angle_x = 80;
409                                 self.button0 = TRUE;
410                         }
411                         else
412                                 bot_lost(self.target1, FALSE);
413
414                 }
415         }
416         if (self.b_aiflags & AI_SURFACE)
417         {
418                 if (self.waterlevel > 2)
419                 {
420                         self.keys = KEY_MOVEUP;
421                         self.button2 = TRUE; // swim!
422                 }
423                 else
424                         self.b_aiflags = self.b_aiflags - AI_SURFACE;
425         }
426         if (self.b_aiflags & AI_RIDE_TRAIN)
427         {
428                 // simple, but effective
429                 // this can probably be used for a lot of different
430                 // things, not just trains (door elevators come to mind)
431                 b_temp3 = self;
432                 self = self.last_way;
433
434                 if (!frik_recognize_plat(FALSE)) // if there is nothing there now
435                 {
436                         self = b_temp3;
437                         self.keys = self.keys & 960;
438                 }
439                 else
440                 {
441                         self = b_temp3;
442                         if (frik_recognize_plat(FALSE))
443                         {
444                                 v = realorigin(trace_ent) + trace_ent.origin - self.origin;
445                                 v_z = 0;
446                                 if (vlen(v) < 24)
447                                         self.keys = self.keys & 960;
448                                 else
449                                 {
450                                         self.b_aiflags = self.b_aiflags | AI_PRECISION;
451                                         self.keys = frik_KeysForDir(v);
452                                 }
453                         }
454                 }
455         }
456         if (self.b_aiflags & AI_PLAT_BOTTOM)
457         {
458                 newt = FindThing("plat");
459                 if (newt.state != 1)
460                 {
461                         v =  self.origin - realorigin(newt);
462                         v_z = 0;
463                         if (vlen(v) > 96)
464                                 self.keys = self.keys & 960;
465                         else
466                                 frik_walkmove(v);
467                 }
468                 else
469                         self.b_aiflags = self.b_aiflags - AI_PLAT_BOTTOM;
470         }
471         if (self.b_aiflags & AI_DIRECTIONAL)
472         {
473                 if ((normalize(self.last_way.origin - self.origin) * self.b_dir) > 0.4)
474                 {
475                         self.b_aiflags = self.b_aiflags - AI_DIRECTIONAL;
476                         bot_lost(self.target1, TRUE);
477                 }
478         }
479         if (self.b_aiflags & AI_SNIPER)
480         {
481                 self.b_aiflags = (self.b_aiflags | AI_WAIT | AI_PRECISION) - AI_SNIPER;
482                 // FIXME: Add a switch to wep command
483                 // FIXME: increase delay?
484         }
485         if (self.b_aiflags & AI_AMBUSH)
486         {
487                 self.b_aiflags = (self.b_aiflags | AI_WAIT) - AI_AMBUSH;
488                 // FIXME: Add a switch to wep command
489                 // FIXME: increase delay?
490         }
491
492 };
493
494 /*
495 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
496
497 bot_path
498
499 Bot will follow a route generated by the
500 begin_route set of functions in bot_way.qc.
501 This code, while it works pretty well, can get
502 confused
503
504 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
505 */
506
507 void() bot_path =
508 {
509         local entity jj, tele;
510
511         bot_check_lost(self.target1);
512         if (!self.target1)
513         {
514                 self.keys=0;
515                 return;
516         }
517         if (target_onstack(self.last_way))
518                 return; // old waypoint still being hunted
519
520         jj = FindRoute(self.last_way);
521         if (!jj)
522         {
523                 // this is an ugly hack
524                 if (self.target1.current_way != self.last_way)
525                 {
526                         if (self.target1.classname != "temp_waypoint")
527                                 if (self.target1.classname != "player")
528                                         bot_lost(self.target1, FALSE);
529                 }
530
531                 return;
532         }
533
534         // update the bot's special ai features
535
536         // Readahed types are AI conditions to perform while heading to a waypoint
537         // point types are AI flags that should be executed once reaching a waypoint
538
539         self.b_aiflags = (jj.b_aiflags & AI_READAHEAD_TYPES) | (self.last_way.b_aiflags & AI_POINT_TYPES);
540         target_add(jj);
541         if (self.last_way)
542         {
543                 if (CheckLinked(self.last_way, jj) == 2) // waypoints are telelinked
544                 {
545                         tele = FindThing("trigger_teleport"); // this is probbly the teleport responsible
546                         target_add(tele);
547                 }
548                 traceline(self.last_way.origin, jj.origin, FALSE, self); // check for blockage
549                 if (trace_fraction != 1)
550                 {
551                         if (trace_ent.classname == "door" && !(self.b_aiflags & AI_DOOR_NO_OPEN)) // a door blocks the way
552                         {
553                                 // linked doors fix
554                                 if (trace_ent.owner)
555                                         trace_ent = trace_ent.owner;
556                                 if ((trace_ent.health > 0) && (self.enemy == world))
557                                 {
558                                         self.enemy = trace_ent;
559                                         bot_weapon_switch(1);
560                                         self.b_aiflags = self.b_aiflags | AI_BLIND; // nick knack paddy hack
561                                 }
562                                 else if (trace_ent.targetname)
563                                 {
564                                         tele = find(world, target, trace_ent.targetname);
565                                         if (tele.health > 0)
566                                         {
567                                                 self.enemy = tele;
568                                                 bot_weapon_switch(1);
569                                         }
570                                         else
571                                         {
572                                         //      target_drop(jj);
573                                                 target_add(tele);
574                                         //      bot_get_path(tele, TRUE);
575                                                 self.b_aiflags = self.b_aiflags | AI_BLIND; // give a bot a bone
576                                                 return;
577                                         }
578                                 }
579                         }
580                         else if (trace_ent.classname == "func_wall")
581                         {
582                                 // give up
583                                 bot_lost(self.target1, FALSE);
584                                 return;
585                         }
586                 }
587         }
588         // this is used for AI_DRIECTIONAL
589         self.b_dir = normalize(jj.origin - self.last_way.origin);
590
591         self.last_way = jj;
592 };
593
594
595 /*
596 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
597
598 Bot Priority Look. What a stupid name. This is where
599 the bot finds things it wants to kill/grab.
600
601 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
602 */
603 // priority scale
604 // 0 - 10 virtually ignore
605 // 10 - 30 normal item range
606 // 30 - 50 bot will consider this a target worth changing course for
607 // 50 - 90 bot will hunt these as vital items
608
609 // *!* Make sure you add code to bot_check_lost to remove the target *!*
610
611 float(entity thing) priority_for_thing =
612 {
613         local float thisp;
614         thisp = 0;
615         // This is the most executed function in the bot. Careful what you do here.
616
617         if (thing.flags & FL_ITEM && thing.model != string_null && thing.search_time < time)
618         {
619                 // ugly hack
620                 if (thing._last != self)
621                         thisp = 20;
622                 else if (thing.classname == "item_strength") // IT_STRENGTH
623                         thisp = 65;
624                 else if (thing.classname == "item_invincible") // IT_INVINCIBLE
625                         thisp = 65;
626                 //else if (thing.classname == "item_speed") // IT_SPEED
627                 //      thisp = 65;
628                 //else if (thing.classname == "item_slowmo") // IT_SLOWMO
629                 //      thisp = 65;
630                 else if (thing.classname == "item_health")
631                 {
632                         if (thing.spawnflags & 2)
633                                 thisp = 55;
634                         if (self.health < 40)
635                                 thisp = thisp + 50;
636                 }
637                 else if (thing.classname == "item_health1" || thing.classname == "item_health2")
638                 {
639                         thisp = 20;
640                         if (self.health < 40)
641                                 thisp = thisp + 50;
642                 }
643                 else if (thing.classname == "item_health100")
644                 {
645                         thisp = 55;
646                         if (self.health < 40)
647                                 thisp = thisp + 50;
648                 }
649                 else if (thing.classname == "item_armor1")
650                 {
651                         thisp = 20;
652                         if (self.armorvalue < 100)
653                                 thisp = thisp + 25;
654                 }
655                 else if (thing.classname == "item_armor25")
656                 {
657                         thisp = 60;
658                         if (self.armorvalue < 100)
659                                 thisp = thisp + 25;
660                 }
661                 else if (thing.model == "progs/armor.mdl")
662                 {
663                         if (self.armorvalue < 200)
664                         {
665                                 if (thing.skin == 2)
666                                         thisp = 60;
667                                 else if (self.armorvalue < 100)
668                                         thisp = thisp + 25;
669                         }
670                 }
671                 else if (thing.classname == "weapon_shotgun")
672                 {
673                         if (!(self.items & IT_SHOTGUN))
674                                 thisp = 25;
675                 }
676                 else if (thing.classname == "weapon_uzi")
677                 {
678                         if (!(self.items & IT_UZI))
679                                 thisp = 30;
680                 }
681                 else if (thing.classname == "weapon_electro")
682                 {
683                         if (!(self.items & IT_ELECTRO))
684                                 thisp = 35;
685                 }
686                 else if (thing.classname == "weapon_grenadelauncher")
687                 {
688                         if (!(self.items & IT_GRENADE_LAUNCHER))
689                                 thisp = 45;
690                 }
691                 else if (thing.classname == "weapon_crylink")
692                 {
693                         if (!(self.items & IT_CRYLINK))
694                                 thisp = 60;
695                 }
696                 else if (thing.classname == "weapon_nex")
697                 {
698                         if (!(self.items & IT_NEX)) // IT_LIGHTNING
699                                 thisp = 50;
700                 }
701                 else if (thing.classname == "weapon_hagar")
702                 {
703                         if (!(self.items & IT_HAGAR))
704                                 thisp = 45;
705                 }
706                 else if (thing.classname == "weapon_rocketlauncher")
707                 {
708                         if (!(self.items & IT_ROCKET_LAUNCHER))
709                                 thisp = 50;
710                 }
711         }
712         else if (thing.classname == "player")
713         {
714                 if (thing.health > 0)
715                 {
716                         if (thing == self)
717                                 return 0;
718                         else
719                         {
720                                 if (coop)
721                                 {
722                                         thisp = 100;
723                                         if (thing.target1.classname == "player")
724                                                 if (!thing.target1.ishuman)
725                                                         return 0;
726                                 }
727                                 else if (teamplay && thing.team == self.team)
728                                 {
729                                         thisp = 100;
730                                         if (thing.target1.classname == "player")
731                                                 return 0;
732                                 }
733                                 else thisp = 30;
734                         }
735                 }
736         }
737         else if (thing.classname == "waypoint")
738         {
739                 if (thing.b_aiflags & AI_SNIPER)
740                         thisp = 30;
741                 else if (thing.b_aiflags & AI_AMBUSH)
742                         thisp = 30;
743         }
744         if (pointcontents(thing.origin) < -3)
745                 return 0;
746         if (thisp)
747         {
748                 if (thing.current_way)
749                 {
750                         // check to see if it's unreachable
751                         if (thing.current_way.items == -1)
752                                 return 0;
753                         else
754                                 thisp = thisp + (13000 - thing.current_way.items) * 0.05;
755
756                 }
757         }
758         return thisp;
759 };
760
761 void(float scope) bot_look_for_crap =
762 {
763         local entity foe, best;
764         local float thatp, bestp, dist;
765
766         if (scope == 1)
767                 foe = findradius(self.origin, 13000);
768         else
769                 foe = findradius(self.origin, 500);
770
771         bestp = 1;
772         while(foe)
773         {
774                 thatp = priority_for_thing(foe);
775                 if (thatp)
776                         if (!scope)
777                                 if (!sisible(foe))
778                                         thatp = 0;
779                 if (thatp > bestp)
780                 {
781                         bestp = thatp;
782                         best = foe;
783                         dist = vlen(self.origin - foe.origin);
784                 }
785                 foe = foe.chain;
786         }
787         if (best == world)
788                 return;
789         if (!target_onstack(best))
790         {
791                 target_add(best);
792                 if (scope)
793                 {
794                         bot_get_path(best, FALSE);
795                         self.b_aiflags = self.b_aiflags | AI_WAIT;
796                 }
797         }
798 };
799
800
801 /*
802 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
803
804 bot_angle_set
805
806 Sets the bots look keys & b_angle to point at
807 the target - used for fighting and just
808 generally making the bot look good.
809
810 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
811 */
812
813 void() bot_angle_set =
814 {
815         local float h;
816         local vector view;
817
818         if (self.enemy)
819         {
820                 if (self.enemy.items & 524288)
821                         if (random() > 0.2)
822                                 return;
823                 if (self.missile_speed == 0)
824                         self.missile_speed = 10000;
825                 if (self.enemy.solid == SOLID_BSP)
826                 {
827                         view = (((self.enemy.absmin + self.enemy.absmax) * 0.5) - self.origin);
828                 }
829                 else
830                 {
831                         h = vlen(self.enemy.origin - self.origin) / self.missile_speed;
832                         if (self.enemy.flags & FL_ONGROUND)
833                                 view = self.enemy.velocity * h;
834                         else
835                                 view = (self.enemy.velocity - (sv_gravity * '0 0 1') * h) * h;
836                         view = self.enemy.origin + view;
837                         // FIXME: ?
838                         traceline(self.enemy.origin, view, FALSE, self);
839                         view = trace_endpos;
840
841                         if (self.weapon == WEP_GRENADE_LAUNCHER || self.weapon == WEP_ELECTRO || self.weapon == WEP_HAGAR || self.weapon == WEP_ROCKET_LAUNCHER)
842                                 view = view - '0 0 22';
843
844                         view = normalize(view - self.origin);
845                 }
846                 view = vectoangles(view);
847                 view_x = view_x * -1;
848                 self.b_angle = view;
849         }
850         else if (self.target1)
851         {
852                 view = realorigin(self.target1);
853                 if (self.target1.flags & FL_ITEM)
854                         view = view + '0 0 48';
855                 view = view - (self.origin + self.view_ofs);
856                 view = vectoangles(view);
857                 view_x = view_x * -1;
858                 self.b_angle = view;
859         }
860         else
861                 self.b_angle_x = 0;
862         // HACK HACK HACK HACK
863         // The bot falls off ledges a lot because of "turning around"
864         // so let the bot use instant turn around when not hunting a player
865         if (self.b_skill == 3)
866         {
867                 self.keys = self.keys & 63;
868                 self.v_angle = self.b_angle;
869                 while (self.v_angle_x < -180)
870                         self.v_angle_x = self.v_angle_x + 360;
871                 while (self.v_angle_x > 180)
872                         self.v_angle_x = self.v_angle_x - 360;
873
874         }
875         else if ((self.enemy == world || self.enemy.movetype == MOVETYPE_PUSH) && self.target1.classname != "player")
876         {
877                 self.keys = self.keys & 63;
878                 self.v_angle = self.b_angle;
879                 while (self.v_angle_x < -180)
880                         self.v_angle_x = self.v_angle_x + 360;
881                 while (self.v_angle_x > 180)
882                         self.v_angle_x = self.v_angle_x - 360;
883         }
884         else if (self.b_skill < 2) // skill 2 handled in bot_phys
885         {
886                 if (self.b_angle_x > 180)
887                         self.b_angle_x = self.b_angle_x - 360;
888                 self.keys = self.keys & 63;
889
890                 if (angcomp(self.b_angle_y, self.v_angle_y) > 10)
891                         self.keys = self.keys | KEY_LOOKLEFT;
892                 else if (angcomp(self.b_angle_y, self.v_angle_y) < -10)
893                         self.keys = self.keys | KEY_LOOKRIGHT;
894                 if (angcomp(self.b_angle_x, self.v_angle_x) < -10)
895                         self.keys = self.keys | KEY_LOOKUP;
896                 else if (angcomp(self.b_angle_x, self.v_angle_x) > 10)
897                         self.keys = self.keys | KEY_LOOKDOWN;
898         }
899 };
900
901 /*
902 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
903
904 BotAI
905
906 This is the main ai loop. Though called every
907 frame, the ai_time limits it's actual updating
908
909 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
910 */
911 float stagger_think;
912 float intermission_running;
913
914 void() BotAI =
915 {
916         // am I dead? Fire randomly until I respawn
917         // health < 1 is used because fractional healths show up as 0 on normal player
918         // status bars, and the mod probably already compensated for that
919
920         if (self.health < 1)
921         {
922                 self.button0 = floor(random() * 2);
923                 self.button2 = 0;
924                 self.keys = 0;
925                 self.b_aiflags = 0;
926                 ClearMyRoute();
927                 self.target1 = self.target2 = self.target3 = self.target4 = self.enemy = world;
928                 self.last_way = world;
929                 return;
930         }
931
932         // stagger the bot's AI out so they all don't think at the same time, causing game
933         // 'spikes'
934         if (self.b_skill < 2)
935         {
936                 if (self.ai_time > time)
937                         return;
938
939                 self.ai_time = time + 0.05;
940                 if (bot_count > 0)
941                 {
942                         if ((time - stagger_think) < (0.1 / bot_count))
943                                 self.ai_time = self.ai_time + 0.1 / (2 * bot_count);
944                 }
945                 else
946                         return;
947         }
948         if (intermission_running)
949                 bot_start_topic(7);
950         stagger_think = time;
951
952         // shut the bot's buttons off, various functions will turn them on by AI end
953
954         self.button2 = 0;
955         self.button0 = 0;
956
957
958         // target1 is like goalentity in normal Quake monster AI.
959         // it's the bot's most immediate target
960         if (route_table == self)
961         {
962                 if (busy_waypoints <= 0)
963                 {
964                         if (waypoint_mode < WM_EDITOR)
965                                 bot_look_for_crap(TRUE);
966                 }
967                 self.b_aiflags = 0;
968                 self.keys = 0;
969         }
970         else if (self.target1)
971         {
972                 frik_movetogoal();
973                 bot_path();
974         }
975         else
976         {
977                 if (waypoint_mode < WM_EDITOR)
978                 {
979                         if(self.route_failed)
980                         {
981                                 frik_bot_roam();
982                                 self.route_failed = 0;
983                         }
984                         else if(!begin_route())
985                         {
986                                 bot_look_for_crap(FALSE);
987                         }
988                         self.keys = 0;
989                 }
990                 else
991                 {
992                         self.b_aiflags = AI_WAIT;
993                         self.keys = 0;
994                 }
995         }
996
997         // bot_angle_set points the bot at it's goal (self.enemy or target1)
998
999         bot_angle_set();
1000
1001         // fight my enemy. Enemy is probably a field QC coders will most likely use a lot
1002         // for their own needs, since it's unused on a normal player
1003         // FIXME
1004         if (self.enemy)
1005                 bot_fight_style();
1006         else if (random() < 0.2)
1007                 if (random() < 0.2)
1008                         bot_weapon_switch(-1);
1009         bot_dodge_stuff();
1010
1011         // checks to see if bot needs to start going up for air
1012 /*      if (self.waterlevel > 2)
1013         {
1014                 if (time > (self.air_finished - 2))
1015                 {
1016                         traceline (self.origin, self.origin + '0 0 6800', TRUE, self);
1017                         if (trace_inopen)
1018                         {
1019                                 self.keys = KEY_MOVEUP;
1020                                 self.button2 = TRUE; // swim!
1021                                 return; // skip ai flags for now - this is life or death
1022                         }
1023                 }
1024         }
1025 */
1026         // b_aiflags handling
1027
1028
1029         if (self.b_aiflags)
1030                 bot_handle_ai();
1031         //else
1032         //      bot_chat(); // don't want chat to screw him up if he's rjing or something
1033 };