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