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