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