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