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