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