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