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