]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/server/havocbot_roles.qc
Bot improvements:
[divverent/nexuiz.git] / data / qcsrc / server / havocbot_roles.qc
1
2 .float havocbot_role_timeout;
3 .void() havocbot_previous_role;
4 .void() havocbot_role;
5 float bot_ignore_bots;
6
7 float canreach(entity e)
8 {
9         return vlen(self.origin - e.origin) < 1500;
10 }
11
12 .float max_armorvalue;
13 float havocbot_pickupevalfunc(entity item)
14 {
15         float i, j, rating, base, position, need_shells, need_nails, need_rockets, need_cells;
16         rating = 0;
17
18         base = item.bot_pickupbasevalue;
19
20         need_shells = self.weapons & WEPBIT_SHOTGUN;
21         need_nails = self.weapons & WEPBIT_UZI;
22         need_cells = self.weapons & ( WEPBIT_HOOK | WEPBIT_HLAC | WEPBIT_MINSTANEX | WEPBIT_NEX | WEPBIT_ELECTRO | WEPBIT_CRYLINK );
23         need_rockets = self.weapons & ( WEPBIT_ROCKET_LAUNCHER | WEPBIT_GRENADE_LAUNCHER | WEPBIT_HAGAR | WEPBIT_SEEKER );
24
25         if( item.weapons )
26         {
27                 if( self.weapons & item.weapons == item.weapons )
28                         rating = 0.5 + bound(0, skill / 20, 0.5);
29                 else
30                         rating = 1;
31                 
32                 if( bot_custom_weapon )
33                 {
34                         for(i = WEP_FIRST; i < WEP_LAST ; ++i){         
35                                 if( power2of(i-1) & item.weapons  != item.weapons )
36                                         continue;
37
38                                 position = -1;
39                                 for(j = 0; j < WEP_LAST ; ++j){
40                                         if(
41                                                 bot_weapons_far[j] == i ||
42                                                 bot_weapons_mid[j] == i ||
43                                                 bot_weapons_close[j] == i
44                                         )
45                                         {
46                                                 position = j;
47                                                 break;
48                                         }
49                                 }
50
51                                 if (position >= 0 ) 
52                                 {
53                                         position = WEP_LAST - position;
54                                         // item.bot_pickupbasevalue is overwritten here
55                                         base = BOT_PICKUP_RATING_LOW + ( (BOT_PICKUP_RATING_HIGH - BOT_PICKUP_RATING_LOW) * (position / WEP_LAST ));
56                                         break;
57                                 }
58                         }
59                 }
60         }
61
62         if (item.ammo_shells)
63         if (self.ammo_shells < g_pickup_shells_max && need_cells )
64                 rating = rating + max(0, 1 - self.ammo_shells / g_pickup_shells_max);
65
66         if (item.ammo_nails)
67         if (self.ammo_nails < g_pickup_nails_max && need_nails )
68                 rating = rating + max(0, 1 - self.ammo_nails / g_pickup_nails_max);
69
70         if (item.ammo_rockets)
71         if (self.ammo_rockets < g_pickup_rockets_max && need_rockets)
72                 rating = rating + max(0, 1 - self.ammo_rockets / g_pickup_rockets_max);
73
74         if (item.ammo_cells)
75         if (self.ammo_cells < g_pickup_cells_max && need_cells)
76                 rating = rating + max(0, 1 - self.ammo_cells / g_pickup_cells_max);
77
78         if (item.armorvalue)
79         if (self.armorvalue < item.max_armorvalue)
80                 rating = rating + max(0, 1 - self.armorvalue / item.max_armorvalue);
81
82         if (item.health)
83         if (self.health < item.max_health)
84                 rating = rating + max(0, 1 - self.health / item.max_health);
85
86         // TODO: if the item is not recognized then default to item.bot_pickupevalfunc(self, item);
87                 
88         return base * rating;
89 };
90
91 void havocbot_goalrating_items(float ratingscale, vector org, float sradius)
92 {
93         local entity head;
94         local entity player;    
95         local float rating, d, discard, distance, friend_distance, enemy_distance;
96         ratingscale = ratingscale * 0.0001; // items are rated around 10000 already
97         head = findchainfloat(bot_pickup, TRUE);
98         
99         while (head)
100         {
101                 distance = vlen(head.origin - org);
102                 friend_distance = 10000; enemy_distance = 10000;
103                 rating = 0;
104         
105                 if(!head.solid || distance > sradius ){
106                         head = head.chain;
107                         continue;               
108                 }
109
110                 if(teamplay)
111                 {
112                         discard = FALSE;
113                         
114                         FOR_EACH_PLAYER(player)
115                         {
116
117                                 if ( self == player || player.deadflag != DEAD_NO)
118                                         continue;
119
120                                 d = vlen(player.origin - head.origin); // distance between player and item
121
122                                 if ( player.team == self.team )
123                                 {
124                                         if ( clienttype(player) != CLIENTTYPE_REAL || discard )
125                                                 continue;
126
127                                         if( d > friend_distance)
128                                                 continue;
129                                         
130                                         friend_distance = d;
131
132                                         discard = TRUE;
133                                         
134                                         if( head.health && player.health > self.health )
135                                                 continue;
136
137                                         if( head.armorvalue && player.armorvalue > self.armorvalue)
138                                                 continue;
139                                                                                         
140                                         if( head.weapons )
141                                         if( (player.weapons & head.weapons) != head.weapons)
142                                                 continue;
143
144                                         if (head.ammo_shells && player.ammo_shells > self.ammo_shells)
145                                                 continue;
146
147                                         if (head.ammo_nails && player.ammo_nails > self.ammo_nails)
148                                                 continue;
149
150                                         if (head.ammo_rockets && player.ammo_rockets > self.ammo_rockets)
151                                                 continue;
152
153                                         if (head.ammo_cells && player.ammo_cells > self.ammo_cells )
154                                                 continue;
155
156                                         discard = FALSE;
157                                 }
158                                 else
159                                 {
160                                         // If enemy only track distances
161                                         // TODO: track only if visible ?
162                                         if( d < enemy_distance )
163                                                 enemy_distance = d;
164                                 }
165                         }
166
167                         // Rate the item only if no one needs it, or if an enemy is closer to it
168                         if (    (enemy_distance < friend_distance && distance < enemy_distance) ||
169                                 (friend_distance > cvar("bot_ai_friends_aware_pickup_radius") ) || !discard )
170                         {
171                         //      rating = head.bot_pickupevalfunc(self, head);
172                                 rating = havocbot_pickupevalfunc(head);
173                         }                               
174                 }
175                 else
176                 {
177                 //      rating = head.bot_pickupevalfunc(self, head);
178                         rating = havocbot_pickupevalfunc(head);
179                 }
180
181                 if(rating > 0)
182                         navigation_routerating(head, rating * ratingscale, 2000);
183                         
184                 head = head.chain;
185         }
186 };
187
188 void havocbot_goalrating_controlpoints(float ratingscale, vector org, float sradius)
189 {
190         local entity head;
191         head = findchain(classname, "dom_controlpoint");
192         while (head)
193         {
194                 if (vlen(head.origin - org) < sradius)
195                 {
196                         if(head.cnt > -1) // this is just being fought for
197                                 navigation_routerating(head, ratingscale, 5000);
198                         else if(head.goalentity.cnt == 0) // unclaimed point
199                                 navigation_routerating(head, ratingscale * 0.5, 5000);
200                         else if(head.goalentity.team != self.team) // other team's point
201                                 navigation_routerating(head, ratingscale * 0.2, 5000);
202                 }
203                 head = head.chain;
204         }
205 };
206
207 /*
208 // LordHavoc: this function was already unused, but for waypoints to be a
209 // useful goal the bots would have to seek out the least-recently-visited
210 // ones, not the closest
211 void havocbot_goalrating_waypoints(float ratingscale, vector org, float sradius)
212 {
213         local entity head;
214         head = findchain(classname, "waypoint");
215         while (head)
216         {
217                 if (vlen(head.origin - org) < sradius && vlen(head.origin - org) > 100)
218                         navigation_routerating(head, ratingscale, 2000);
219                 head = head.chain;
220         }
221 };
222 */
223
224 void havocbot_goalrating_enemyplayers(float ratingscale, vector org, float sradius)
225 {
226         local entity head;
227         local float t, noteam;
228         noteam = ((self.team == 0) || (teamplay == 0)); // fteqcc sucks
229         //dprint("teamplay is "); dprint(ftos(teamplay)); dprint(", own team is ");
230         //dprint(ftos(self.team)); dprint(" -> noteam is "); dprint(ftos(noteam));
231         //dprint("\n");
232
233         FOR_EACH_PLAYER(head)
234         {
235                 if (self != head)
236                 if (head.health > 0)
237                 if ((noteam && (!bot_ignore_bots || clienttype(head) == CLIENTTYPE_REAL)) || head.team != self.team)
238                 {
239                         if (vlen(head.origin - org) > sradius)
240                                 continue;
241                                 
242                         // rate only visible enemies
243                         /*
244                         traceline(self.origin + self.view_ofs, head.origin, MOVE_NOMONSTERS, self);
245                         if (trace_fraction < 1 || trace_ent != head)
246                                 continue;
247                         */
248                         
249                         // not falling
250                         if(head.flags & FL_ONGROUND == 0)
251                         {
252                                 traceline(head.origin, head.origin + '0 0 -1500', TRUE, world);
253                                 t = pointcontents(trace_endpos + '0 0 1');
254                                 if (t != CONTENT_SOLID)
255                                         continue;
256                                 // TODO: check for trigger_hurt here
257                         }
258                         
259                         t = (self.health + self.armorvalue ) / (head.health + head.armorvalue );
260                         navigation_routerating(head, t * ratingscale, 2000);
261                 }
262         }
263 };
264
265
266 void() havocbot_role_ctf_middle;
267 void() havocbot_role_ctf_defense;
268 void() havocbot_role_ctf_offense;
269 void() havocbot_role_ctf_interceptor;
270
271 void havocbot_goalrating_ctf_carrieritems(float ratingscale, vector org, float sradius)
272 {
273         local entity head;
274         local float t;
275         head = findchainfloat(bot_pickup, TRUE);
276         while (head)
277         {
278                 // look for health and armor only
279                 if (head.solid) // must be possible to pick up (respawning items don't count)
280                 if (head.health || head.armorvalue)
281                 if (vlen(head.origin - org) < sradius)
282                 {
283                         // debugging
284                         //if (!head.bot_pickupevalfunc || head.model == "")
285                         //      eprint(head);
286                         // get the value of the item
287                         t = head.bot_pickupevalfunc(self, head) * 0.0001;
288                         if (t > 0)
289                                 navigation_routerating(head, t * ratingscale, 500);
290                 }
291                 head = head.chain;
292         }
293 };
294
295 entity ctf_worldflaglist;
296 .entity ctf_worldflagnext;
297 void havocbot_goalrating_ctf_ourflag(float ratingscale)
298 {
299         local entity head;
300         head = ctf_worldflaglist;
301         while (head)
302         {
303                 if (self.team == head.team)
304                         break;
305                 head = head.ctf_worldflagnext;
306         }
307         if (head)
308                 navigation_routerating(head, ratingscale, 10000);
309 };
310
311 void havocbot_goalrating_ctf_enemyflag(float ratingscale)
312 {
313         local entity head;
314         head = ctf_worldflaglist;
315         while (head)
316         {
317                 if (self.team != head.team)
318                         break;
319                 head = head.ctf_worldflagnext;
320         }
321         if (head)
322                 navigation_routerating(head, ratingscale, 10000);
323 };
324
325 void havocbot_goalrating_ctf_enemybase(float ratingscale)
326 {
327         // div0: needs a change in the CTF code
328 };
329
330 void havocbot_goalrating_ctf_ourstolenflag(float ratingscale)
331 {
332         local entity head;
333         head = ctf_worldflaglist;
334         while (head)
335         {
336                 if (self.team == head.team)
337                         break;
338                 head = head.ctf_worldflagnext;
339         }
340         if (head)
341         if (head.cnt != FLAG_BASE)
342                 navigation_routerating(head, ratingscale, 10000);
343 };
344
345 void havocbot_goalrating_ctf_droppedflags(float ratingscale)
346 {
347         local entity head;
348         head = ctf_worldflaglist;
349         while (head)
350         {
351                 if (head.cnt != FLAG_BASE) // flag is carried or out in the field
352                         navigation_routerating(head, ratingscale, 10000);
353                 head = head.ctf_worldflagnext;
354         }
355 };
356
357 // CTF: (always teamplay)
358
359 //role rogue: (is this used?)
360 //pick up items and dropped flags (with big rating boost to dropped flags)
361 void havocbot_role_ctf_rogue()
362 {
363         if (self.bot_strategytime < time)
364         {
365                 self.bot_strategytime = time + cvar("bot_ai_strategyinterval");
366                 navigation_goalrating_start();
367                 havocbot_goalrating_ctf_droppedflags(5000);
368                 //havocbot_goalrating_enemyplayers(3000, self.origin, 3000);
369                 havocbot_goalrating_items(10000, self.origin, 10000);
370                 navigation_goalrating_end();
371         }
372 }
373
374 //role flag carrier:
375 //pick up armor and health
376 //go to our flag spot
377 .float bot_cantfindflag;
378 void havocbot_role_ctf_carrier()
379 {
380         if (self.flagcarried == world)
381         {
382                 dprint("changing role to middle\n");
383                 self.havocbot_role = havocbot_role_ctf_middle;
384                 self.havocbot_role_timeout = 0;
385                 return;
386         }
387         if (self.bot_strategytime < time)
388         {
389                 self.bot_strategytime = time + cvar("bot_ai_strategyinterval");
390                 navigation_goalrating_start();
391                 havocbot_goalrating_ctf_ourflag(50000);
392                 if (navigation_bestgoal)
393                         self.bot_cantfindflag = time + 10;
394                 else if (time > self.bot_cantfindflag)
395                 {
396                         // can't navigate to our own flag :(
397                         Damage(self, self, self, 100000, DEATH_KILL, self.origin, '0 0 0');
398                 }
399                 havocbot_goalrating_ctf_carrieritems(1000, self.origin, 1000);
400                 navigation_goalrating_end();
401         }
402 };
403
404 //role offense:
405 //pick up armor and health
406 //if rockets < 25 || health < 100, change role to middle
407 //if carrying flag, change role to flag carrier
408 //if our flag taken, change role to interceptor
409 //(60-90 second timer) change role to middle
410 //go to enemy flag
411 void havocbot_role_ctf_offense()
412 {
413         local entity f;
414         if (self.flagcarried)
415         {
416                 dprint("changing role to carrier\n");
417                 self.havocbot_role = havocbot_role_ctf_carrier;
418                 self.havocbot_role_timeout = 0;
419                 self.bot_cantfindflag = time + 10;
420                 return;
421         }
422         // check our flag
423         f = ctf_worldflaglist;
424         while (f)
425         {
426                 if (self.team == f.team)
427                         break;
428                 f = f.ctf_worldflagnext;
429         }
430         if (f.cnt != FLAG_BASE && canreach(f))
431         {
432                 dprint("changing role to interceptor\n");
433                 self.havocbot_previous_role = self.havocbot_role;
434                 self.havocbot_role = havocbot_role_ctf_interceptor;
435                 self.havocbot_role_timeout = 0;
436                 return;
437         }
438         if (!self.havocbot_role_timeout)
439                 self.havocbot_role_timeout = time + random() * 30 + 60;
440         if (time > self.havocbot_role_timeout)
441         {
442                 dprint("changing role to middle\n");
443                 self.havocbot_role = havocbot_role_ctf_middle;
444                 self.havocbot_role_timeout = 0;
445                 return;
446         }
447         if (self.bot_strategytime < time)
448         {
449                 self.bot_strategytime = time + cvar("bot_ai_strategyinterval");
450                 navigation_goalrating_start();
451                 havocbot_goalrating_ctf_ourstolenflag(50000);
452                 havocbot_goalrating_ctf_enemyflag(30000);
453                 havocbot_goalrating_ctf_enemybase(20000);
454                 havocbot_goalrating_items(10000, self.origin, 10000);
455                 navigation_goalrating_end();
456         }
457 };
458
459 //role interceptor (temporary role):
460 //pick up items
461 //if carrying flag, change role to flag carrier
462 //if our flag is back, change role to previous role
463 //follow our flag
464 //go to least recently visited area
465 void havocbot_role_ctf_interceptor()
466 {
467         local entity f;
468         if (self.flagcarried)
469         {
470                 dprint("changing role to carrier\n");
471                 self.havocbot_role = havocbot_role_ctf_carrier;
472                 self.havocbot_role_timeout = 0;
473                 self.bot_cantfindflag = time + 10;
474                 return;
475         }
476         // check our flag
477         f = ctf_worldflaglist;
478         while (f)
479         {
480                 if (self.team == f.team)
481                         break;
482                 f = f.ctf_worldflagnext;
483         }
484         if (f.cnt == FLAG_BASE)
485         {
486                 dprint("changing role back\n");
487                 self.havocbot_role = self.havocbot_previous_role;
488                 self.havocbot_role_timeout = 0;
489                 return;
490         }
491
492         if (self.bot_strategytime < time)
493         {
494                 self.bot_strategytime = time + cvar("bot_ai_strategyinterval");
495                 navigation_goalrating_start();
496                 havocbot_goalrating_ctf_ourstolenflag(50000);
497                 havocbot_goalrating_ctf_droppedflags(50000);
498                 havocbot_goalrating_items(10000, self.origin, 10000);
499                 navigation_goalrating_end();
500         }
501 };
502
503 //role middle:
504 //pick up items
505 //if carrying flag, change role to flag carrier
506 //if our flag taken, change role to interceptor
507 //if see flag (of either team) follow it (this has many implications)
508 //(10-20 second timer) change role to defense or offense
509 //go to least recently visited area
510 void havocbot_role_ctf_middle()
511 {
512         local entity f;
513         if (self.flagcarried)
514         {
515                 dprint("changing role to carrier\n");
516                 self.havocbot_role = havocbot_role_ctf_carrier;
517                 self.havocbot_role_timeout = 0;
518                 self.bot_cantfindflag = time + 10;
519                 return;
520         }
521         // check our flag
522         f = ctf_worldflaglist;
523         while (f)
524         {
525                 if (self.team == f.team)
526                         break;
527                 f = f.ctf_worldflagnext;
528         }
529         if (f.cnt != FLAG_BASE && canreach(f))
530         {
531                 dprint("changing role to interceptor\n");
532                 self.havocbot_previous_role = self.havocbot_role;
533                 self.havocbot_role = havocbot_role_ctf_interceptor;
534                 self.havocbot_role_timeout = 0;
535                 return;
536         }
537         if (!self.havocbot_role_timeout)
538                 self.havocbot_role_timeout = time + random() * 10 + 10;
539         if (time > self.havocbot_role_timeout)
540         {
541                 if (random() < 0.5)
542                 {
543                         dprint("changing role to offense\n");
544                         self.havocbot_role = havocbot_role_ctf_offense;
545                 }
546                 else
547                 {
548                         dprint("changing role to defense\n");
549                         self.havocbot_role = havocbot_role_ctf_defense;
550                 }
551                 self.havocbot_role_timeout = 0;
552                 return;
553         }
554
555         if (self.bot_strategytime < time)
556         {
557                 self.bot_strategytime = time + cvar("bot_ai_strategyinterval");
558                 navigation_goalrating_start();
559                 havocbot_goalrating_ctf_ourstolenflag(50000);
560                 havocbot_goalrating_ctf_droppedflags(30000);
561                 //havocbot_goalrating_enemyplayers(1000, self.origin, 1000);
562                 havocbot_goalrating_items(10000, self.origin, 10000);
563                 navigation_goalrating_end();
564         }
565 };
566
567 //role defense:
568 //if rockets < 25 || health < 100, change role to middle
569 //if carrying flag, change role to flag carrier
570 //if our flag taken, change role to interceptor
571 //(30-50 second timer) change role to middle
572 //move to nearest unclaimed defense spot
573 void havocbot_role_ctf_defense()
574 {
575         local entity f;
576         if (self.flagcarried)
577         {
578                 dprint("changing role to carrier\n");
579                 self.havocbot_role = havocbot_role_ctf_carrier;
580                 self.havocbot_role_timeout = 0;
581                 self.bot_cantfindflag = time + 10;
582                 return;
583         }
584         // check our flag
585         f = ctf_worldflaglist;
586         while (f)
587         {
588                 if (self.team == f.team)
589                         break;
590                 f = f.ctf_worldflagnext;
591         }
592         if (f.cnt != FLAG_BASE && canreach(f))
593         {
594                 dprint("changing role to interceptor\n");
595                 self.havocbot_previous_role = self.havocbot_role;
596                 self.havocbot_role = havocbot_role_ctf_interceptor;
597                 self.havocbot_role_timeout = 0;
598                 return;
599         }
600         if (!self.havocbot_role_timeout)
601                 self.havocbot_role_timeout = time + random() * 20 + 30;
602         if (time > self.havocbot_role_timeout)
603         {
604                 dprint("changing role to middle\n");
605                 self.havocbot_role = havocbot_role_ctf_middle;
606                 self.havocbot_role_timeout = 0;
607                 return;
608         }
609         if (self.bot_strategytime < time)
610         {
611                 self.bot_strategytime = time + cvar("bot_ai_strategyinterval");
612                 navigation_goalrating_start();
613                 havocbot_goalrating_ctf_ourstolenflag(200000);
614                 havocbot_goalrating_ctf_droppedflags(50000);
615                 havocbot_goalrating_items(10000, f.origin, 10000);
616                 navigation_goalrating_end();
617         }
618         /*
619         // FIXME: place info_ctf_defensepoint entities in CTF maps and use them
620         // change position occasionally
621         if (time > self.bot_strategytime || self.goalentity.classname != "info_ctf_defensepoint")
622         {
623                 self.bot_strategytime = time + random() * 45 + 15;
624                 self.goalentity = world;
625                 head = findchain(classname, "info_ctf_defensepoint");
626                 while (head)
627                 {
628                         if (time > head.count)
629                         {
630                                 self.goalentity = head;
631                                 head.chain = world;
632                         }
633                         head = head.chain;
634                 }
635                 // if there are no defensepoints defined, switch to middle
636                 if (self.goalentity == world)
637                 {
638                         dprint("changing role to middle\n");
639                         self.havocbot_role = havocbot_role_ctf_middle;
640                         self.havocbot_role_timeout = 0;
641                         return;
642                 }
643         }
644         // keep anyone else from taking this spot
645         if (self.goalentity != world)
646                 self.goalentity.count = time + 0.5;
647         */
648 };
649
650 // CTF:
651 // choose a role according to the situation
652 void() havocbot_role_dm;
653 void havocbot_chooserole_ctf()
654 {
655         local float r;
656         dprint("choose CTF role...\n");
657         if (self.team == COLOR_TEAM3 || self.team == COLOR_TEAM4)
658                 self.havocbot_role = havocbot_role_ctf_rogue;
659         else
660         {
661                 r = random() * 3;
662                 if (r < 1)
663                         self.havocbot_role = havocbot_role_ctf_offense;
664                 else if (r < 2)
665                         self.havocbot_role = havocbot_role_ctf_middle;
666                 else
667                         self.havocbot_role = havocbot_role_ctf_defense;
668         }
669 };
670
671 //DOM:
672 //go to best items, or control points you don't own
673 void havocbot_role_dom()
674 {
675         if (self.bot_strategytime < time)
676         {
677                 self.bot_strategytime = time + cvar("bot_ai_strategyinterval");
678                 navigation_goalrating_start();
679                 havocbot_goalrating_controlpoints(10000, self.origin, 15000);
680                 havocbot_goalrating_items(8000, self.origin, 8000);
681                 //havocbot_goalrating_enemyplayers(3000, self.origin, 2000);
682                 //havocbot_goalrating_waypoints(1, self.origin, 1000);
683                 navigation_goalrating_end();
684         }
685 };
686
687 //DM:
688 //go to best items
689 void havocbot_role_dm()
690 {
691         if (self.bot_strategytime < time)
692         {
693                 self.bot_strategytime = time + cvar("bot_ai_strategyinterval");
694                 navigation_goalrating_start();
695                 havocbot_goalrating_items(10000, self.origin, 10000);
696                 havocbot_goalrating_enemyplayers(10000, self.origin, 20000);
697                 //havocbot_goalrating_waypoints(1, self.origin, 1000);
698                 navigation_goalrating_end();
699         }
700 };
701
702 //Race:
703 //go to next checkpoint, and annoy enemies
704 .float race_checkpoint;
705 void havocbot_role_race()
706 {
707         entity e;
708         if (self.bot_strategytime < time)
709         {
710                 self.bot_strategytime = time + cvar("bot_ai_strategyinterval");
711                 navigation_goalrating_start();
712                 /*
713                 havocbot_goalrating_items(100, self.origin, 10000);
714                 havocbot_goalrating_enemyplayers(500, self.origin, 20000);
715                 */
716
717                 for(e = world; (e = find(e, classname, "trigger_race_checkpoint")) != world; )
718                 {
719                         if(e.cnt == self.race_checkpoint)
720                         {
721                                 navigation_routerating(e, 1000000, 5000);
722                         }
723                         else if(self.race_checkpoint == -1)
724                         {
725                                 navigation_routerating(e, 1000000, 5000);
726                         }
727                 }
728                 
729                 navigation_goalrating_end();
730         }
731 };
732
733 void havocbot_chooserole_dm()
734 {
735         self.havocbot_role = havocbot_role_dm;
736 };
737
738 void havocbot_chooserole_race()
739 {
740         self.havocbot_role = havocbot_role_race;
741 };
742
743 void havocbot_chooserole_dom()
744 {
745         self.havocbot_role = havocbot_role_dom;
746 };
747
748
749
750
751
752
753 entity kh_worldkeylist;
754 .entity kh_worldkeynext;
755 void havocbot_goalrating_kh(float ratingscale_team, float ratingscale_dropped, float ratingscale_enemy)
756 {
757         local entity head;
758         for (head = kh_worldkeylist; head; head = head.kh_worldkeynext)
759         {
760                 if(head.owner == self)
761                         continue;
762                 if(!kh_tracking_enabled)
763                 {
764                         // if it's carried by our team we know about it
765                         // otherwise we have to see it to know about it
766                         if(!head.owner || head.team != self.team)
767                         {
768                                 traceline(self.origin + self.view_ofs, head.origin, MOVE_NOMONSTERS, self);
769                                 if (trace_fraction < 1 && trace_ent != head)
770                                         continue; // skip what I can't see
771                         }
772                 }
773                 if(!head.owner)
774                         navigation_routerating(head, ratingscale_dropped, 100000);
775                 else if(head.team == self.team)
776                         navigation_routerating(head, ratingscale_team, 100000);
777                 else
778                         navigation_routerating(head, ratingscale_enemy, 100000);
779         }
780 };
781
782 void() havocbot_role_kh_carrier;
783 void() havocbot_role_kh_defense;
784 void() havocbot_role_kh_offense;
785 void() havocbot_role_kh_freelancer;
786 void havocbot_role_kh_carrier()
787 {
788         if (!(self.items & IT_KEY1))
789         {
790                 dprint("changing role to freelancer\n");
791                 self.havocbot_role = havocbot_role_kh_freelancer;
792                 self.havocbot_role_timeout = 0;
793                 return;
794         }
795
796         if (self.bot_strategytime < time)
797         {
798                 self.bot_strategytime = time + cvar("bot_ai_strategyinterval");
799                 navigation_goalrating_start();
800
801                 if(kh_Key_AllOwnedByWhichTeam() == self.team)
802                         havocbot_goalrating_kh(100000, 1, 1); // bring home
803                 else
804                         havocbot_goalrating_kh(40000, 40000, 1000); // play defensively
805
806                 havocbot_goalrating_items(10000, self.origin, 10000);
807                 navigation_goalrating_end();
808         }
809 }
810
811 void havocbot_role_kh_defense()
812 {
813         if (self.items & IT_KEY1)
814         {
815                 dprint("changing role to carrier\n");
816                 self.havocbot_role = havocbot_role_kh_carrier;
817                 self.havocbot_role_timeout = 0;
818                 return;
819         }
820
821         if (!self.havocbot_role_timeout)
822                 self.havocbot_role_timeout = time + random() * 10 + 20;
823         if (time > self.havocbot_role_timeout)
824         {
825                 dprint("changing role to freelancer\n");
826                 self.havocbot_role = havocbot_role_kh_freelancer;
827                 self.havocbot_role_timeout = 0;
828                 return;
829         }
830
831         if (self.bot_strategytime < time)
832         {
833                 float key_owner_team;
834                 self.bot_strategytime = time + cvar("bot_ai_strategyinterval");
835                 navigation_goalrating_start();
836
837                 key_owner_team = kh_Key_AllOwnedByWhichTeam();
838                 if(key_owner_team == self.team)
839                         havocbot_goalrating_kh(100000, 1, 1); // defend key carriers
840                 else if(key_owner_team == -1)
841                         havocbot_goalrating_kh(40000, 10000, 1); // play defensively
842                 else
843                         havocbot_goalrating_kh(1, 1, 100000); // ATTACK ANYWAY
844
845                 havocbot_goalrating_items(10000, self.origin, 10000);
846                 navigation_goalrating_end();
847         }
848 };
849
850 void havocbot_role_kh_offense()
851 {
852         if (self.items & IT_KEY1)
853         {
854                 dprint("changing role to carrier\n");
855                 self.havocbot_role = havocbot_role_kh_carrier;
856                 self.havocbot_role_timeout = 0;
857                 return;
858         }
859
860         if (!self.havocbot_role_timeout)
861                 self.havocbot_role_timeout = time + random() * 10 + 20;
862         if (time > self.havocbot_role_timeout)
863         {
864                 dprint("changing role to freelancer\n");
865                 self.havocbot_role = havocbot_role_kh_freelancer;
866                 self.havocbot_role_timeout = 0;
867                 return;
868         }
869
870         if (self.bot_strategytime < time)
871         {
872                 float key_owner_team;
873
874                 self.bot_strategytime = time + cvar("bot_ai_strategyinterval");
875                 navigation_goalrating_start();
876
877                 key_owner_team = kh_Key_AllOwnedByWhichTeam();
878                 if(key_owner_team == self.team)
879                         havocbot_goalrating_kh(100000, 1, 1); // defend anyway
880                 else if(key_owner_team == -1)
881                         havocbot_goalrating_kh(1, 10000, 40000); // play offensively
882                 else
883                         havocbot_goalrating_kh(1, 1, 100000); // ATTACK! EMERGENCY!
884
885                 havocbot_goalrating_items(10000, self.origin, 10000);
886                 navigation_goalrating_end();
887         }
888 };
889
890 void havocbot_role_kh_freelancer()
891 {
892         if (self.items & IT_KEY1)
893         {
894                 dprint("changing role to carrier\n");
895                 self.havocbot_role = havocbot_role_kh_carrier;
896                 self.havocbot_role_timeout = 0;
897                 return;
898         }
899
900         if (!self.havocbot_role_timeout)
901                 self.havocbot_role_timeout = time + random() * 10 + 10;
902         if (time > self.havocbot_role_timeout)
903         {
904                 if (random() < 0.5)
905                 {
906                         dprint("changing role to offense\n");
907                         self.havocbot_role = havocbot_role_kh_offense;
908                 }
909                 else
910                 {
911                         dprint("changing role to defense\n");
912                         self.havocbot_role = havocbot_role_kh_defense;
913                 }
914                 self.havocbot_role_timeout = 0;
915                 return;
916         }
917
918         if (self.bot_strategytime < time)
919         {
920                 float key_owner_team;
921
922                 self.bot_strategytime = time + cvar("bot_ai_strategyinterval");
923                 navigation_goalrating_start();
924
925                 key_owner_team = kh_Key_AllOwnedByWhichTeam();
926                 if(key_owner_team == self.team)
927                         havocbot_goalrating_kh(100000, 1, 1); // defend anyway
928                 else if(key_owner_team == -1)
929                         havocbot_goalrating_kh(10000, 40000, 10000); // prefer dropped keys
930                 else
931                         havocbot_goalrating_kh(1, 1, 100000); // ATTACK ANYWAY
932
933                 havocbot_goalrating_items(10000, self.origin, 10000);
934                 navigation_goalrating_end();
935         }
936 };
937
938
939
940
941
942 void havocbot_chooserole_kh()
943 {
944         local float r;
945         r = random() * 3;
946         if (r < 1)
947                 self.havocbot_role = havocbot_role_kh_offense;
948         else if (r < 2)
949                 self.havocbot_role = havocbot_role_kh_defense;
950         else
951                 self.havocbot_role = havocbot_role_kh_freelancer;
952 };
953
954 void havocbot_chooserole()
955 {
956         dprint("choose a role...\n");
957         navigation_routetogoal(world);
958         self.bot_strategytime = -1;
959         if (g_ctf)
960                 havocbot_chooserole_ctf();
961         else if (g_domination)
962                 havocbot_chooserole_dom();
963         else if (g_keyhunt)
964                 havocbot_chooserole_kh();
965         else if (g_race)
966                 havocbot_chooserole_race();
967         else // assume anything else is deathmatch
968                 havocbot_chooserole_dm();
969 };
970