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