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