]> icculus.org git repositories - divverent/nexuiz.git/blob - qcsrc/gamec/bot_ai.c
commented out speed/slowmo related stuff
[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
510         local entity jj, tele;
511         local vector org;
512
513         bot_check_lost(self.target1);
514         if (!self.target1)
515         {
516                 self.keys=0;
517                 return;
518         }
519         if (target_onstack(self.last_way))
520                 return; // old waypoint still being hunted
521
522         jj = FindRoute(self.last_way);
523         if (!jj)
524         {
525                 // this is an ugly hack
526                 if (self.target1.current_way != self.last_way)
527                 {
528                         if (self.target1.classname != "temp_waypoint")
529                                 if (self.target1.classname != "player")
530                                         bot_lost(self.target1, FALSE);
531                 }
532
533                 return;
534         }
535
536         // update the bot's special ai features
537
538         // Readahed types are AI conditions to perform while heading to a waypoint
539         // point types are AI flags that should be executed once reaching a waypoint
540
541         self.b_aiflags = (jj.b_aiflags & AI_READAHEAD_TYPES) | (self.last_way.b_aiflags & AI_POINT_TYPES);
542         target_add(jj);
543         if (self.last_way)
544         {
545                 if (CheckLinked(self.last_way, jj) == 2) // waypoints are telelinked
546                 {
547                         tele = FindThing("trigger_teleport"); // this is probbly the teleport responsible
548                         target_add(tele);
549                 }
550                 traceline(self.last_way.origin, jj.origin, FALSE, self); // check for blockage
551                 if (trace_fraction != 1)
552                 {
553                         if (trace_ent.classname == "door" && !(self.b_aiflags & AI_DOOR_NO_OPEN)) // a door blocks the way
554                         {
555                                 // linked doors fix
556                                 if (trace_ent.owner)
557                                         trace_ent = trace_ent.owner;
558                                 if ((trace_ent.health > 0) && (self.enemy == world))
559                                 {
560                                         self.enemy = trace_ent;
561                                         bot_weapon_switch(1);
562                                         self.b_aiflags = self.b_aiflags | AI_BLIND; // nick knack paddy hack
563                                 }
564                                 else if (trace_ent.targetname)
565                                 {
566                                         tele = find(world, target, trace_ent.targetname);
567                                         if (tele.health > 0)
568                                         {
569                                                 self.enemy = tele;
570                                                 bot_weapon_switch(1);
571                                         }
572                                         else
573                                         {
574                                         //      target_drop(jj);
575                                                 target_add(tele);
576                                         //      bot_get_path(tele, TRUE);
577                                                 self.b_aiflags = self.b_aiflags | AI_BLIND; // give a bot a bone
578                                                 return;
579                                         }
580                                 }
581                         }
582                         else if (trace_ent.classname == "func_wall")
583                         {
584                                 // give up
585                                 bot_lost(self.target1, FALSE);
586                                 return;
587                         }
588                 }
589         }
590         // this is used for AI_DRIECTIONAL
591         self.b_dir = normalize(jj.origin - self.last_way.origin);
592
593         self.last_way = jj;
594 };
595
596
597 /*
598 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
599
600 Bot Priority Look. What a stupid name. This is where
601 the bot finds things it wants to kill/grab.
602
603 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
604 */
605 // priority scale
606 // 0 - 10 virtually ignore
607 // 10 - 30 normal item range
608 // 30 - 50 bot will consider this a target worth changing course for
609 // 50 - 90 bot will hunt these as vital items
610
611 // *!* Make sure you add code to bot_check_lost to remove the target *!*
612
613 float(entity thing) priority_for_thing =
614 {
615         local float thisp;
616         thisp = 0;
617         // This is the most executed function in the bot. Careful what you do here.
618
619         if (thing.flags & FL_ITEM && thing.model != string_null && thing.search_time < time)
620         {
621                 // ugly hack
622                 if (thing._last != self)
623                         thisp = 20;
624                 else if (thing.classname == "item_strength") // IT_STRENGTH
625                         thisp = 65;
626                 else if (thing.classname == "item_invincible") // IT_INVINCIBLE
627                         thisp = 65;
628                 //else if (thing.classname == "item_speed") // IT_SPEED
629                 //      thisp = 65;
630                 //else if (thing.classname == "item_slowmo") // IT_SLOWMO
631                 //      thisp = 65;
632                 else if (thing.classname == "item_health")
633                 {
634                         if (thing.spawnflags & 2)
635                                 thisp = 55;
636                         if (self.health < 40)
637                                 thisp = thisp + 50;
638                 }
639                 else if (thing.classname == "item_health1" || thing.classname == "item_health2")
640                 {
641                         thisp = 20;
642                         if (self.health < 40)
643                                 thisp = thisp + 50;
644                 }
645                 else if (thing.classname == "item_health100")
646                 {
647                         thisp = 55;
648                         if (self.health < 40)
649                                 thisp = thisp + 50;
650                 }
651                 else if (thing.classname == "item_armor1")
652                 {
653                         thisp = 20;
654                         if (self.armorvalue < 100)
655                                 thisp = thisp + 25;
656                 }
657                 else if (thing.classname == "item_armor25")
658                 {
659                         thisp = 60;
660                         if (self.armorvalue < 100)
661                                 thisp = thisp + 25;
662                 }
663                 else if (thing.model == "progs/armor.mdl")
664                 {
665                         if (self.armorvalue < 200)
666                         {
667                                 if (thing.skin == 2)
668                                         thisp = 60;
669                                 else if (self.armorvalue < 100)
670                                         thisp = thisp + 25;
671                         }
672                 }
673                 else if (thing.classname == "weapon_shotgun")
674                 {
675                         if (!(self.items & IT_SHOTGUN))
676                                 thisp = 25;
677                 }
678                 else if (thing.classname == "weapon_uzi")
679                 {
680                         if (!(self.items & IT_UZI))
681                                 thisp = 30;
682                 }
683                 else if (thing.classname == "weapon_electro")
684                 {
685                         if (!(self.items & IT_ELECTRO))
686                                 thisp = 35;
687                 }
688                 else if (thing.classname == "weapon_grenadelauncher")
689                 {
690                         if (!(self.items & IT_GRENADE_LAUNCHER))
691                                 thisp = 45;
692                 }
693                 else if (thing.classname == "weapon_crylink")
694                 {
695                         if (!(self.items & IT_CRYLINK))
696                                 thisp = 60;
697                 }
698                 else if (thing.classname == "weapon_nex")
699                 {
700                         if (!(self.items & IT_NEX)) // IT_LIGHTNING
701                                 thisp = 50;
702                 }
703                 else if (thing.classname == "weapon_hagar")
704                 {
705                         if (!(self.items & IT_HAGAR))
706                                 thisp = 45;
707                 }
708                 else if (thing.classname == "weapon_rocketlauncher")
709                 {
710                         if (!(self.items & IT_ROCKET_LAUNCHER))
711                                 thisp = 50;
712                 }
713         }
714         else if (thing.classname == "player")
715         {
716                 if (thing.health > 0)
717                 {
718                         if (thing == self)
719                                 return 0;
720                         else
721                         {
722                                 if (coop)
723                                 {
724                                         thisp = 100;
725                                         if (thing.target1.classname == "player")
726                                                 if (!thing.target1.ishuman)
727                                                         return 0;
728                                 }
729                                 else if (teamplay && thing.team == self.team)
730                                 {
731                                         thisp = 100;
732                                         if (thing.target1.classname == "player")
733                                                 return 0;
734                                 }
735                                 else thisp = 30;
736                         }
737                 }
738         }
739         else if (thing.classname == "waypoint")
740         {
741                 if (thing.b_aiflags & AI_SNIPER)
742                         thisp = 30;
743                 else if (thing.b_aiflags & AI_AMBUSH)
744                         thisp = 30;
745         }
746         if (pointcontents(thing.origin) < -3)
747                 return 0;
748         if (thisp)
749         {
750                 if (thing.current_way)
751                 {
752                         // check to see if it's unreachable
753                         if (thing.current_way.items == -1)
754                                 return 0;
755                         else
756                                 thisp = thisp + (13000 - thing.current_way.items) * 0.05;
757
758                 }
759         }
760         return thisp;
761 };
762
763 void(float scope) bot_look_for_crap =
764 {
765         local entity foe, best;
766         local float thatp, bestp, dist;
767
768         if (scope == 1)
769                 foe = findradius(self.origin, 13000);
770         else
771                 foe = findradius(self.origin, 500);
772
773         bestp = 1;
774         while(foe)
775         {
776                 thatp = priority_for_thing(foe);
777                 if (thatp)
778                         if (!scope)
779                                 if (!sisible(foe))
780                                         thatp = 0;
781                 if (thatp > bestp)
782                 {
783                         bestp = thatp;
784                         best = foe;
785                         dist = vlen(self.origin - foe.origin);
786                 }
787                 foe = foe.chain;
788         }
789         if (best == world)
790                 return;
791         if (!target_onstack(best))
792         {
793                 target_add(best);
794                 if (scope)
795                 {
796                         bot_get_path(best, FALSE);
797                         self.b_aiflags = self.b_aiflags | AI_WAIT;
798                 }
799         }
800 };
801
802
803 /*
804 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
805
806 bot_angle_set
807
808 Sets the bots look keys & b_angle to point at
809 the target - used for fighting and just
810 generally making the bot look good.
811
812 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
813 */
814
815 void() bot_angle_set =
816 {
817         local float h;
818         local vector view;
819
820         if (self.enemy)
821         {
822                 if (self.enemy.items & 524288)
823                         if (random() > 0.2)
824                                 return;
825                 if (self.missile_speed == 0)
826                         self.missile_speed = 10000;
827                 if (self.enemy.solid == SOLID_BSP)
828                 {
829                         view = (((self.enemy.absmin + self.enemy.absmax) * 0.5) - self.origin);
830                 }
831                 else
832                 {
833                         h = vlen(self.enemy.origin - self.origin) / self.missile_speed;
834                         if (self.enemy.flags & FL_ONGROUND)
835                                 view = self.enemy.velocity * h;
836                         else
837                                 view = (self.enemy.velocity - (sv_gravity * '0 0 1') * h) * h;
838                         view = self.enemy.origin + view;
839                         // FIXME: ?
840                         traceline(self.enemy.origin, view, FALSE, self);
841                         view = trace_endpos;
842
843                         if (self.weapon == WEP_GRENADE_LAUNCHER || self.weapon == WEP_ELECTRO || self.weapon == WEP_HAGAR || self.weapon == WEP_ROCKET_LAUNCHER)
844                                 view = view - '0 0 22';
845
846                         view = normalize(view - self.origin);
847                 }
848                 view = vectoangles(view);
849                 view_x = view_x * -1;
850                 self.b_angle = view;
851         }
852         else if (self.target1)
853         {
854                 view = realorigin(self.target1);
855                 if (self.target1.flags & FL_ITEM)
856                         view = view + '0 0 48';
857                 view = view - (self.origin + self.view_ofs);
858                 view = vectoangles(view);
859                 view_x = view_x * -1;
860                 self.b_angle = view;
861         }
862         else
863                 self.b_angle_x = 0;
864         // HACK HACK HACK HACK
865         // The bot falls off ledges a lot because of "turning around"
866         // so let the bot use instant turn around when not hunting a player
867         if (self.b_skill == 3)
868         {
869                 self.keys = self.keys & 63;
870                 self.v_angle = self.b_angle;
871                 while (self.v_angle_x < -180)
872                         self.v_angle_x = self.v_angle_x + 360;
873                 while (self.v_angle_x > 180)
874                         self.v_angle_x = self.v_angle_x - 360;
875
876         }
877         else if ((self.enemy == world || self.enemy.movetype == MOVETYPE_PUSH) && self.target1.classname != "player")
878         {
879                 self.keys = self.keys & 63;
880                 self.v_angle = self.b_angle;
881                 while (self.v_angle_x < -180)
882                         self.v_angle_x = self.v_angle_x + 360;
883                 while (self.v_angle_x > 180)
884                         self.v_angle_x = self.v_angle_x - 360;
885         }
886         else if (self.b_skill < 2) // skill 2 handled in bot_phys
887         {
888                 if (self.b_angle_x > 180)
889                         self.b_angle_x = self.b_angle_x - 360;
890                 self.keys = self.keys & 63;
891
892                 if (angcomp(self.b_angle_y, self.v_angle_y) > 10)
893                         self.keys = self.keys | KEY_LOOKLEFT;
894                 else if (angcomp(self.b_angle_y, self.v_angle_y) < -10)
895                         self.keys = self.keys | KEY_LOOKRIGHT;
896                 if (angcomp(self.b_angle_x, self.v_angle_x) < -10)
897                         self.keys = self.keys | KEY_LOOKUP;
898                 else if (angcomp(self.b_angle_x, self.v_angle_x) > 10)
899                         self.keys = self.keys | KEY_LOOKDOWN;
900         }
901 };
902
903 /*
904 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
905
906 BotAI
907
908 This is the main ai loop. Though called every
909 frame, the ai_time limits it's actual updating
910
911 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
912 */
913 float stagger_think;
914 float intermission_running;
915
916 void() BotAI =
917 {
918         // am I dead? Fire randomly until I respawn
919         // health < 1 is used because fractional healths show up as 0 on normal player
920         // status bars, and the mod probably already compensated for that
921
922         if (self.health < 1)
923         {
924                 self.button0 = floor(random() * 2);
925                 self.button2 = 0;
926                 self.keys = 0;
927                 self.b_aiflags = 0;
928                 ClearMyRoute();
929                 self.target1 = self.target2 = self.target3 = self.target4 = self.enemy = world;
930                 self.last_way = world;
931                 return;
932         }
933
934         // stagger the bot's AI out so they all don't think at the same time, causing game
935         // 'spikes'
936         if (self.b_skill < 2)
937         {
938                 if (self.ai_time > time)
939                         return;
940
941                 self.ai_time = time + 0.05;
942                 if (bot_count > 0)
943                 {
944                         if ((time - stagger_think) < (0.1 / bot_count))
945                                 self.ai_time = self.ai_time + 0.1 / (2 * bot_count);
946                 }
947                 else
948                         return;
949         }
950         if (intermission_running)
951                 bot_start_topic(7);
952         stagger_think = time;
953
954         // shut the bot's buttons off, various functions will turn them on by AI end
955
956         self.button2 = 0;
957         self.button0 = 0;
958
959
960         // target1 is like goalentity in normal Quake monster AI.
961         // it's the bot's most immediate target
962         if (route_table == self)
963         {
964                 if (busy_waypoints <= 0)
965                 {
966                         if (waypoint_mode < WM_EDITOR)
967                                 bot_look_for_crap(TRUE);
968                 }
969                 self.b_aiflags = 0;
970                 self.keys = 0;
971         }
972         else if (self.target1)
973         {
974                 frik_movetogoal();
975                 bot_path();
976         }
977         else
978         {
979                 if (waypoint_mode < WM_EDITOR)
980                 {
981                         if(self.route_failed)
982                         {
983                                 frik_bot_roam();
984                                 self.route_failed = 0;
985                         }
986                         else if(!begin_route())
987                         {
988                                 bot_look_for_crap(FALSE);
989                         }
990                         self.keys = 0;
991                 }
992                 else
993                 {
994                         self.b_aiflags = AI_WAIT;
995                         self.keys = 0;
996                 }
997         }
998
999         // bot_angle_set points the bot at it's goal (self.enemy or target1)
1000
1001         bot_angle_set();
1002
1003         // fight my enemy. Enemy is probably a field QC coders will most likely use a lot
1004         // for their own needs, since it's unused on a normal player
1005         // FIXME
1006         if (self.enemy)
1007                 bot_fight_style();
1008         else if (random() < 0.2)
1009                 if (random() < 0.2)
1010                         bot_weapon_switch(-1);
1011         bot_dodge_stuff();
1012
1013         // checks to see if bot needs to start going up for air
1014 /*      if (self.waterlevel > 2)
1015         {
1016                 if (time > (self.air_finished - 2))
1017                 {
1018                         traceline (self.origin, self.origin + '0 0 6800', TRUE, self);
1019                         if (trace_inopen)
1020                         {
1021                                 self.keys = KEY_MOVEUP;
1022                                 self.button2 = TRUE; // swim!
1023                                 return; // skip ai flags for now - this is life or death
1024                         }
1025                 }
1026         }
1027 */
1028         // b_aiflags handling
1029
1030
1031         if (self.b_aiflags)
1032                 bot_handle_ai();
1033         else
1034                 bot_chat(); // don't want chat to screw him up if he's rjing or something
1035 };