]> icculus.org git repositories - divverent/nexuiz.git/blob - TeamNexuiz/game/gamec/bot_ai.c
bumps for morphed's teleporter model
[divverent/nexuiz.git] / TeamNexuiz / game / gamec / bot_ai.c
1 /***********************************************\r
2 *                                              *\r
3 *            FrikBot General AI                *\r
4 *     "The I'd rather be playing Quake AI"     *\r
5 *                                              *\r
6 ***********************************************/\r
7 \r
8 /*\r
9 \r
10 This program is in the Public Domain. My crack legal\r
11 team would like to add:\r
12 \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
22 \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
29 liability.\r
30 \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
39 \r
40 Any reproduction of this software must contain\r
41 this notice in its entirety.\r
42 \r
43 */\r
44 \r
45 /*\r
46 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\r
47 \r
48 target_onstack\r
49 \r
50 checks to see if an entity is on the bot's stack\r
51 \r
52 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\r
53 */\r
54 \r
55 float(entity scot) target_onstack =\r
56 {\r
57         if (scot == world)\r
58                 return FALSE;\r
59         else if (self.target1 == scot)\r
60                 return 1;\r
61         else if (self.target2 == scot)\r
62                 return 2;\r
63         else if (self.target3 == scot)\r
64                 return 3;\r
65         else if (self.target4 == scot)\r
66                 return 4;\r
67         else\r
68                 return FALSE;\r
69 };\r
70 \r
71 /*\r
72 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\r
73 \r
74 target_add\r
75 \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
78 \r
79 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\r
80 */\r
81 \r
82 void(entity ent) target_add =\r
83 {\r
84         if (ent == world)\r
85                 return;\r
86         if (target_onstack(ent))\r
87                 return;\r
88         self.target4 = self.target3;\r
89         self.target3 = self.target2;\r
90         self.target2 = self.target1;\r
91         self.target1 = ent;\r
92         self.search_time = time + 5;\r
93 };\r
94 \r
95 \r
96 /*\r
97 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\r
98 \r
99 target_drop\r
100 \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
105 is gone too.\r
106 \r
107 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\r
108 */\r
109 \r
110 void(entity ent) target_drop =\r
111 {\r
112         local float tg;\r
113 \r
114         tg = target_onstack(ent);\r
115         if (tg == 1)\r
116         {\r
117                 self.target1 = self.target2;\r
118                 self.target2 = self.target3;\r
119                 self.target3 = self.target4;\r
120                 self.target4 = world;\r
121         }\r
122         else if (tg == 2)\r
123         {\r
124                 self.target1 = self.target3;\r
125                 self.target2 = self.target4;\r
126                 self.target3 = self.target4 = world;\r
127         }\r
128         else if (tg == 3)\r
129         {\r
130                 self.target1 = self.target4;\r
131                 self.target2 = self.target3 = self.target4 = world;\r
132         }\r
133         else if (tg == 4)\r
134                 self.target1 = self.target2 = self.target3 = self.target4 = world;\r
135         self.search_time = time + 5;\r
136 };\r
137 \r
138 /*\r
139 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\r
140 \r
141 bot_lost\r
142 \r
143 Bot has lost its target.\r
144 \r
145 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\r
146 */\r
147 \r
148 void(entity targ, float success) bot_lost =\r
149 {\r
150         if (!targ)\r
151                 return;\r
152 \r
153         target_drop(targ);\r
154         if (targ.classname == "waypoint")\r
155                 targ.b_sound = targ.b_sound - (targ.b_sound & ClientBitFlag(self.b_clientno));\r
156 \r
157         // find a new route\r
158         if (!success)\r
159         {\r
160                 self.target1 = self.target2 = self.target3 = self.target4 = world;\r
161                 self.last_way = FindWayPoint(self.current_way);\r
162                 ClearMyRoute();\r
163                 self.b_aiflags = 0;\r
164         }\r
165         else\r
166         {\r
167                 if (targ.classname == "item_artifact_invisibility")\r
168                         if (self.items & 524288)\r
169                                 bot_start_topic(3);\r
170 \r
171                 if (targ.flags & FL_ITEM)\r
172                 {\r
173                         if (targ.model == string_null)\r
174                                 targ._last = world;\r
175                         else\r
176                                 targ._last = self;\r
177                 }\r
178         }\r
179 \r
180 \r
181         if (targ.classname != "player")\r
182                 targ.search_time = time + 5;\r
183 };\r
184 \r
185 /*\r
186 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\r
187 \r
188 bot_check_lost\r
189 \r
190 decide if my most immediate target should be\r
191 removed.\r
192 \r
193 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\r
194 */\r
195 void(entity targ) bot_check_lost =\r
196 {\r
197         local vector dist;\r
198         dist = realorigin(targ) - self.origin;\r
199         dist_z = 0;\r
200         if (targ == world)\r
201                 return;\r
202 \r
203         // waypoints and items are lost if you get close enough to them\r
204 \r
205         else if (targ.flags & FL_ITEM)\r
206         {\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
211         }\r
212         else if (targ.classname == "waypoint")\r
213         {\r
214                 if (!(self.b_aiflags & (AI_SNIPER | AI_AMBUSH)))\r
215                 {\r
216                         if (self.b_aiflags & AI_RIDE_TRAIN)\r
217                         {\r
218                                 if (vlen(targ.origin - self.origin) < 48)\r
219                                         bot_lost(targ, TRUE);\r
220                         }\r
221                         else if (self.b_aiflags & AI_PRECISION)\r
222                         {\r
223                                 if (vlen(targ.origin - self.origin) < 24)\r
224                                         bot_lost(targ, TRUE);\r
225                         }\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
229                         {\r
230                                 if (vlen(targ.origin - self.origin) < 128)\r
231                                         bot_lost(targ, TRUE);\r
232                         }\r
233                 }\r
234         }\r
235         else if (targ.classname == "temp_waypoint")\r
236         {\r
237                 if (vlen(targ.origin - self.origin) < 32)\r
238                         bot_lost(targ, TRUE);\r
239         }\r
240         else if (targ.classname == "player")\r
241         {\r
242                 if (targ.health <= 0)\r
243                         bot_lost(targ, TRUE);\r
244                 else if ((coop) || (teamplay && targ.team == self.team))\r
245                 {\r
246                         if (targ.target1.classname == "player")\r
247                         {\r
248                                 if (!targ.target1.ishuman)\r
249                                         bot_lost(targ, TRUE);\r
250                         }\r
251                         else if (targ.teleport_time > time)\r
252                         {\r
253                                 // try not to telefrag teammates\r
254                                 self.keys = self.keys & 960;\r
255                         }\r
256                         else if (vlen(targ.origin - self.origin) < 128)\r
257                         {\r
258                                 if (vlen(targ.origin - self.origin) < 48)\r
259                                         frik_walkmove(self.origin - targ.origin);\r
260                                 else\r
261                                 {\r
262                                         self.keys = self.keys & 960;\r
263                                         bot_start_topic(4);\r
264                                 }\r
265                                 self.search_time = time + 5; // never time out\r
266                         }\r
267                         else if (!fisible(targ))\r
268                                 bot_lost(targ, FALSE);\r
269                 }\r
270                 else if (waypoint_mode > WM_LOADED)\r
271                 {\r
272                         if (vlen(targ.origin - self.origin) < 128)\r
273                         {\r
274                                 bot_lost(targ, TRUE);\r
275                         }\r
276                 }\r
277         }\r
278 \r
279         // buttons are lost of their frame changes\r
280         else if (targ.classname == "func_button")\r
281         {\r
282                 if (targ.frame)\r
283                 {\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
289 \r
290                 }\r
291         }\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
294         {\r
295                 if (targ.nextthink >= time)\r
296                 {\r
297                         bot_lost(targ, TRUE);\r
298                         //if (self.target1)\r
299                         //      bot_get_path(self.target1, TRUE);\r
300                 }\r
301         }\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
305         {\r
306                 dist = targ.origin - self.origin;\r
307                 dist_z = 0;\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
312         }\r
313         else if (targ.classname == "train")\r
314         {\r
315                 if (frik_recognize_plat(FALSE))\r
316                         bot_lost(targ, TRUE);\r
317         }\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
321 };\r
322 \r
323 \r
324 /*\r
325 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\r
326 \r
327 bot_handle_ai\r
328 \r
329 This is a 0.10 addition. Handles any action\r
330 based b_aiflags.\r
331 \r
332 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\r
333 */\r
334 \r
335 void() bot_handle_ai =\r
336 {\r
337         local entity newt;\r
338         local vector v;\r
339 \r
340         // handle ai flags -- note, not all aiflags are handled\r
341         // here, just those that perform some sort of action\r
342 \r
343         // wait is used by the ai to stop the bot until his search time expires / or route changes\r
344 \r
345         if (self.b_aiflags & AI_WAIT)\r
346                 self.keys = self.keys & 960;\r
347 \r
348         if (self.b_aiflags & AI_DOORFLAG) // was on a door when spawned\r
349         {\r
350                 b_temp3 = self;\r
351                 self = self.last_way;\r
352                 if (!frik_recognize_plat(FALSE)) // if there is nothing there now\r
353                 {\r
354                         newt = FindThing("door"); // this is likely the door responsible (crossfingers)\r
355                         self = b_temp3;\r
356 \r
357                         if (self.b_aiflags & AI_DOOR_NO_OPEN)\r
358                         {\r
359                                 if (newt.nextthink)\r
360                                         self.keys = self.keys & 960; // wait until it closes\r
361                                 else\r
362                                 {\r
363                                         bot_lost(self.last_way, FALSE);\r
364                                 }\r
365                         }\r
366                         else\r
367                         {\r
368                                 if (newt.targetname)\r
369                                 {\r
370                                         newt = find(world, target, newt.targetname);\r
371                                         if (newt.health > 0)\r
372                                         {\r
373                                                 self.enemy = newt;\r
374                                                 bot_weapon_switch(1);\r
375                                         }\r
376                                         else\r
377                                         {\r
378                                         //      target_drop(self.last_way);\r
379                                                 target_add(newt);\r
380                                         //      bot_get_path(newt, TRUE);\r
381                                         }\r
382                                 }\r
383                                 self.b_aiflags = self.b_aiflags - AI_DOORFLAG;\r
384                         }\r
385                 }\r
386                 else\r
387                         self = b_temp3;\r
388         }\r
389 \r
390         if (self.b_aiflags & AI_JUMP)\r
391         {\r
392                 if (self.flags & FL_ONGROUND)\r
393                 {\r
394                         bot_jump();\r
395                         self.b_aiflags = self.b_aiflags - AI_JUMP;\r
396                 }\r
397         }\r
398         else if (self.b_aiflags & AI_SUPER_JUMP)\r
399         {\r
400                 if (self.weapon != WEP_LASER)\r
401                         self.impulse = 7;\r
402                 else if (self.flags & FL_ONGROUND)\r
403                 {\r
404                         self.b_aiflags = self.b_aiflags - AI_SUPER_JUMP;\r
405                         if (bot_can_rj(self))\r
406                         {\r
407                                 bot_jump();\r
408                                 self.v_angle_x = self.b_angle_x = 80;\r
409                                 self.button0 = TRUE;\r
410                         }\r
411                         else\r
412                                 bot_lost(self.target1, FALSE);\r
413 \r
414                 }\r
415         }\r
416         if (self.b_aiflags & AI_SURFACE)\r
417         {\r
418                 if (self.waterlevel > 2)\r
419                 {\r
420                         self.keys = KEY_MOVEUP;\r
421                         self.button2 = TRUE; // swim!\r
422                 }\r
423                 else\r
424                         self.b_aiflags = self.b_aiflags - AI_SURFACE;\r
425         }\r
426         if (self.b_aiflags & AI_RIDE_TRAIN)\r
427         {\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
431                 b_temp3 = self;\r
432                 self = self.last_way;\r
433 \r
434                 if (!frik_recognize_plat(FALSE)) // if there is nothing there now\r
435                 {\r
436                         self = b_temp3;\r
437                         self.keys = self.keys & 960;\r
438                 }\r
439                 else\r
440                 {\r
441                         self = b_temp3;\r
442                         if (frik_recognize_plat(FALSE))\r
443                         {\r
444                                 v = realorigin(trace_ent) + trace_ent.origin - self.origin;\r
445                                 v_z = 0;\r
446                                 if (vlen(v) < 24)\r
447                                         self.keys = self.keys & 960;\r
448                                 else\r
449                                 {\r
450                                         self.b_aiflags = self.b_aiflags | AI_PRECISION;\r
451                                         self.keys = frik_KeysForDir(v);\r
452                                 }\r
453                         }\r
454                 }\r
455         }\r
456         if (self.b_aiflags & AI_PLAT_BOTTOM)\r
457         {\r
458                 newt = FindThing("plat");\r
459                 if (newt.state != 1)\r
460                 {\r
461                         v =  self.origin - realorigin(newt);\r
462                         v_z = 0;\r
463                         if (vlen(v) > 96)\r
464                                 self.keys = self.keys & 960;\r
465                         else\r
466                                 frik_walkmove(v);\r
467                 }\r
468                 else\r
469                         self.b_aiflags = self.b_aiflags - AI_PLAT_BOTTOM;\r
470         }\r
471         if (self.b_aiflags & AI_DIRECTIONAL)\r
472         {\r
473                 if ((normalize(self.last_way.origin - self.origin) * self.b_dir) > 0.4)\r
474                 {\r
475                         self.b_aiflags = self.b_aiflags - AI_DIRECTIONAL;\r
476                         bot_lost(self.target1, TRUE);\r
477                 }\r
478         }\r
479         if (self.b_aiflags & AI_SNIPER)\r
480         {\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
484         }\r
485         if (self.b_aiflags & AI_AMBUSH)\r
486         {\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
490         }\r
491 \r
492 };\r
493 \r
494 /*\r
495 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\r
496 \r
497 bot_path\r
498 \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
502 confused\r
503 \r
504 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\r
505 */\r
506 \r
507 void() bot_path =\r
508 {\r
509         local entity jj, tele;\r
510 \r
511         bot_check_lost(self.target1);\r
512         if (!self.target1)\r
513         {\r
514                 self.keys=0;\r
515                 return;\r
516         }\r
517         if (target_onstack(self.last_way))\r
518                 return; // old waypoint still being hunted\r
519 \r
520         jj = FindRoute(self.last_way);\r
521         if (!jj)\r
522         {\r
523                 // this is an ugly hack\r
524                 if (self.target1.current_way != self.last_way)\r
525                 {\r
526                         if (self.target1.classname != "temp_waypoint")\r
527                                 if (self.target1.classname != "player")\r
528                                         bot_lost(self.target1, FALSE);\r
529                 }\r
530 \r
531                 return;\r
532         }\r
533 \r
534         // update the bot's special ai features\r
535 \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
538 \r
539         self.b_aiflags = (jj.b_aiflags & AI_READAHEAD_TYPES) | (self.last_way.b_aiflags & AI_POINT_TYPES);\r
540         target_add(jj);\r
541         if (self.last_way)\r
542         {\r
543                 if (CheckLinked(self.last_way, jj) == 2) // waypoints are telelinked\r
544                 {\r
545                         tele = FindThing("trigger_teleport"); // this is probbly the teleport responsible\r
546                         target_add(tele);\r
547                 }\r
548                 traceline(self.last_way.origin, jj.origin, FALSE, self); // check for blockage\r
549                 if (trace_fraction != 1)\r
550                 {\r
551                         if (trace_ent.classname == "door" && !(self.b_aiflags & AI_DOOR_NO_OPEN)) // a door blocks the way\r
552                         {\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
557                                 {\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
561                                 }\r
562                                 else if (trace_ent.targetname)\r
563                                 {\r
564                                         tele = find(world, target, trace_ent.targetname);\r
565                                         if (tele.health > 0)\r
566                                         {\r
567                                                 self.enemy = tele;\r
568                                                 bot_weapon_switch(1);\r
569                                         }\r
570                                         else\r
571                                         {\r
572                                         //      target_drop(jj);\r
573                                                 target_add(tele);\r
574                                         //      bot_get_path(tele, TRUE);\r
575                                                 self.b_aiflags = self.b_aiflags | AI_BLIND; // give a bot a bone\r
576                                                 return;\r
577                                         }\r
578                                 }\r
579                         }\r
580                         else if (trace_ent.classname == "func_wall")\r
581                         {\r
582                                 // give up\r
583                                 bot_lost(self.target1, FALSE);\r
584                                 return;\r
585                         }\r
586                 }\r
587         }\r
588         // this is used for AI_DRIECTIONAL\r
589         self.b_dir = normalize(jj.origin - self.last_way.origin);\r
590 \r
591         self.last_way = jj;\r
592 };\r
593 \r
594 \r
595 /*\r
596 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\r
597 \r
598 Bot Priority Look. What a stupid name. This is where\r
599 the bot finds things it wants to kill/grab.\r
600 \r
601 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\r
602 */\r
603 // priority scale\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
608 \r
609 // *!* Make sure you add code to bot_check_lost to remove the target *!*\r
610 \r
611 float(entity thing) priority_for_thing =\r
612 {\r
613         local float thisp;\r
614         thisp = 0;\r
615         // This is the most executed function in the bot. Careful what you do here.\r
616 \r
617         if (thing.flags & FL_ITEM && thing.model != string_null && thing.search_time < time)\r
618         {\r
619                 // ugly hack\r
620                 if (thing._last != self)\r
621                         thisp = 20;\r
622                 else if (thing.classname == "item_strength") // IT_STRENGTH\r
623                         thisp = 65;\r
624                 else if (thing.classname == "item_invincible") // IT_INVINCIBLE\r
625                         thisp = 65;\r
626                 //else if (thing.classname == "item_speed") // IT_SPEED\r
627                 //      thisp = 65;\r
628                 //else if (thing.classname == "item_slowmo") // IT_SLOWMO\r
629                 //      thisp = 65;\r
630                 else if (thing.classname == "item_health")\r
631                 {\r
632                         if (thing.spawnflags & 2)\r
633                                 thisp = 55;\r
634                         if (self.health < 40)\r
635                                 thisp = thisp + 50;\r
636                 }\r
637                 else if (thing.classname == "item_health1" || thing.classname == "item_health2")\r
638                 {\r
639                         thisp = 20;\r
640                         if (self.health < 40)\r
641                                 thisp = thisp + 50;\r
642                 }\r
643                 else if (thing.classname == "item_health100")\r
644                 {\r
645                         thisp = 55;\r
646                         if (self.health < 40)\r
647                                 thisp = thisp + 50;\r
648                 }\r
649                 else if (thing.classname == "item_armor1")\r
650                 {\r
651                         thisp = 20;\r
652                         if (self.armorvalue < 100)\r
653                                 thisp = thisp + 25;\r
654                 }\r
655                 else if (thing.classname == "item_armor25")\r
656                 {\r
657                         thisp = 60;\r
658                         if (self.armorvalue < 100)\r
659                                 thisp = thisp + 25;\r
660                 }\r
661                 else if (thing.classname == "weapon_shotgun")\r
662                 {\r
663                         if (!(self.items & IT_SHOTGUN))\r
664                                 thisp = 25;\r
665                 }\r
666                 else if (thing.classname == "weapon_uzi")\r
667                 {\r
668                         if (!(self.items & IT_UZI))\r
669                                 thisp = 30;\r
670                 }\r
671                 else if (thing.classname == "weapon_electro")\r
672                 {\r
673                         if (!(self.items & IT_ELECTRO))\r
674                                 thisp = 35;\r
675                 }\r
676                 else if (thing.classname == "weapon_grenadelauncher")\r
677                 {\r
678                         if (!(self.items & IT_GRENADE_LAUNCHER))\r
679                                 thisp = 45;\r
680                 }\r
681                 else if (thing.classname == "weapon_crylink")\r
682                 {\r
683                         if (!(self.items & IT_CRYLINK))\r
684                                 thisp = 60;\r
685                 }\r
686                 else if (thing.classname == "weapon_nex")\r
687                 {\r
688                         if (!(self.items & IT_NEX)) // IT_LIGHTNING\r
689                                 thisp = 50;\r
690                 }\r
691                 else if (thing.classname == "weapon_hagar")\r
692                 {\r
693                         if (!(self.items & IT_HAGAR))\r
694                                 thisp = 45;\r
695                 }\r
696                 else if (thing.classname == "weapon_rocketlauncher")\r
697                 {\r
698                         if (!(self.items & IT_ROCKET_LAUNCHER))\r
699                                 thisp = 50;\r
700                 }\r
701         }\r
702         else if (thing.classname == "player")\r
703         {\r
704                 if (thing.health > 0)\r
705                 {\r
706                         if (thing == self)\r
707                                 return 0;\r
708                         else\r
709                         {\r
710                                 if (coop)\r
711                                 {\r
712                                         thisp = 100;\r
713                                         if (thing.target1.classname == "player")\r
714                                                 if (!thing.target1.ishuman)\r
715                                                         return 0;\r
716                                 }\r
717                                 else if (teamplay && thing.team == self.team)\r
718                                 {\r
719                                         thisp = 100;\r
720                                         if (thing.target1.classname == "player")\r
721                                                 return 0;\r
722                                 }\r
723                                 else thisp = 30;\r
724                         }\r
725                 }\r
726         }\r
727         else if (thing.classname == "waypoint")\r
728         {\r
729                 if (thing.b_aiflags & AI_SNIPER)\r
730                         thisp = 30;\r
731                 else if (thing.b_aiflags & AI_AMBUSH)\r
732                         thisp = 30;\r
733         }\r
734         if (pointcontents(thing.origin) < -3)\r
735                 return 0;\r
736         if (thisp)\r
737         {\r
738                 if (thing.current_way)\r
739                 {\r
740                         // check to see if it's unreachable\r
741                         if (thing.current_way.items == -1)\r
742                                 return 0;\r
743                         else\r
744                                 thisp = thisp + (13000 - thing.current_way.items) * 0.05;\r
745 \r
746                 }\r
747         }\r
748         return thisp;\r
749 };\r
750 \r
751 void(float scope) bot_look_for_crap =\r
752 {\r
753         local entity foe, best;\r
754         local float thatp, bestp, dist;\r
755 \r
756         if (scope == 1)\r
757                 foe = findradius(self.origin, 13000);\r
758         else\r
759                 foe = findradius(self.origin, 500);\r
760 \r
761         bestp = 1;\r
762         while(foe)\r
763         {\r
764                 thatp = priority_for_thing(foe);\r
765                 if (thatp)\r
766                         if (!scope)\r
767                                 if (!sisible(foe))\r
768                                         thatp = 0;\r
769                 if (thatp > bestp)\r
770                 {\r
771                         bestp = thatp;\r
772                         best = foe;\r
773                         dist = vlen(self.origin - foe.origin);\r
774                 }\r
775                 foe = foe.chain;\r
776         }\r
777         if (best == world)\r
778                 return;\r
779         if (!target_onstack(best))\r
780         {\r
781                 target_add(best);\r
782                 if (scope)\r
783                 {\r
784                         bot_get_path(best, FALSE);\r
785                         self.b_aiflags = self.b_aiflags | AI_WAIT;\r
786                 }\r
787         }\r
788 };\r
789 \r
790 \r
791 /*\r
792 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\r
793 \r
794 bot_angle_set\r
795 \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
799 \r
800 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\r
801 */\r
802 \r
803 void() bot_angle_set =\r
804 {\r
805         local float h;\r
806         local vector view;\r
807 \r
808         if (self.enemy)\r
809         {\r
810                 if (self.enemy.items & 524288)\r
811                         if (random() > 0.2)\r
812                                 return;\r
813                 if (self.missile_speed == 0)\r
814                         self.missile_speed = 10000;\r
815                 if (self.enemy.solid == SOLID_BSP)\r
816                 {\r
817                         view = (((self.enemy.absmin + self.enemy.absmax) * 0.5) - self.origin);\r
818                 }\r
819                 else\r
820                 {\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
824                         else\r
825                                 view = (self.enemy.velocity - (sv_gravity * '0 0 1') * h) * h;\r
826                         view = self.enemy.origin + view;\r
827                         // FIXME: ?\r
828                         traceline(self.enemy.origin, view, FALSE, self);\r
829                         view = trace_endpos;\r
830 \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
833 \r
834                         view = normalize(view - self.origin);\r
835                 }\r
836                 view = vectoangles(view);\r
837                 view_x = view_x * -1;\r
838                 self.b_angle = view;\r
839         }\r
840         else if (self.target1)\r
841         {\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
849         }\r
850         else\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
856         {\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
863 \r
864         }\r
865         else if ((self.enemy == world || self.enemy.movetype == MOVETYPE_PUSH) && self.target1.classname != "player")\r
866         {\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
873         }\r
874         else if (self.b_skill < 2) // skill 2 handled in bot_phys\r
875         {\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
879 \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
888         }\r
889 };\r
890 \r
891 /*\r
892 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\r
893 \r
894 BotAI\r
895 \r
896 This is the main ai loop. Though called every\r
897 frame, the ai_time limits it's actual updating\r
898 \r
899 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\r
900 */\r
901 float stagger_think;\r
902 float intermission_running;\r
903 \r
904 void() BotAI =\r
905 {\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
909 \r
910         if (self.health < 1)\r
911         {\r
912                 self.button0 = floor(random() * 2);\r
913                 self.button2 = 0;\r
914                 self.keys = 0;\r
915                 self.b_aiflags = 0;\r
916                 ClearMyRoute();\r
917                 self.target1 = self.target2 = self.target3 = self.target4 = self.enemy = world;\r
918                 self.last_way = world;\r
919                 return;\r
920         }\r
921 \r
922         // stagger the bot's AI out so they all don't think at the same time, causing game\r
923         // 'spikes'\r
924         if (self.b_skill < 2)\r
925         {\r
926                 if (self.ai_time > time)\r
927                         return;\r
928 \r
929                 self.ai_time = time + 0.05;\r
930                 if (bot_count > 0)\r
931                 {\r
932                         if ((time - stagger_think) < (0.1 / bot_count))\r
933                                 self.ai_time = self.ai_time + 0.1 / (2 * bot_count);\r
934                 }\r
935                 else\r
936                         return;\r
937         }\r
938         if (intermission_running)\r
939                 bot_start_topic(7);\r
940         stagger_think = time;\r
941 \r
942         // shut the bot's buttons off, various functions will turn them on by AI end\r
943 \r
944         self.button2 = 0;\r
945         self.button0 = 0;\r
946 \r
947 \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
951         {\r
952                 if (busy_waypoints <= 0)\r
953                 {\r
954                         if (waypoint_mode < WM_EDITOR)\r
955                                 bot_look_for_crap(TRUE);\r
956                 }\r
957                 self.b_aiflags = 0;\r
958                 self.keys = 0;\r
959         }\r
960         else if (self.target1)\r
961         {\r
962                 frik_movetogoal();\r
963                 bot_path();\r
964         }\r
965         else\r
966         {\r
967                 if (waypoint_mode < WM_EDITOR)\r
968                 {\r
969                         if(self.route_failed)\r
970                         {\r
971                                 frik_bot_roam();\r
972                                 self.route_failed = 0;\r
973                         }\r
974                         else if(!begin_route())\r
975                         {\r
976                                 bot_look_for_crap(FALSE);\r
977                         }\r
978                         self.keys = 0;\r
979                 }\r
980                 else\r
981                 {\r
982                         self.b_aiflags = AI_WAIT;\r
983                         self.keys = 0;\r
984                 }\r
985         }\r
986 \r
987         // bot_angle_set points the bot at it's goal (self.enemy or target1)\r
988 \r
989         bot_angle_set();\r
990 \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
993         // FIXME\r
994         if (self.enemy)\r
995                 bot_fight_style();\r
996         else if (random() < 0.2)\r
997                 if (random() < 0.2)\r
998                         bot_weapon_switch(-1);\r
999         bot_dodge_stuff();\r
1000 \r
1001         // checks to see if bot needs to start going up for air\r
1002 /*      if (self.waterlevel > 2)\r
1003         {\r
1004                 if (time > (self.air_finished - 2))\r
1005                 {\r
1006                         traceline (self.origin, self.origin + '0 0 6800', TRUE, self);\r
1007                         if (trace_inopen)\r
1008                         {\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
1012                         }\r
1013                 }\r
1014         }\r
1015 */\r
1016         // b_aiflags handling\r
1017 \r
1018 \r
1019         if (self.b_aiflags)\r
1020                 bot_handle_ai();\r
1021         //else\r
1022         //      bot_chat(); // don't want chat to screw him up if he's rjing or something\r
1023 };\r