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