]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/server/havocbot_roles.qc
Don't rate enemies if bot_nofire is set
[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 (navigation_bestgoal)
438                         self.bot_cantfindflag = time + 10;
439                 else if (time > self.bot_cantfindflag)
440                 {
441                         // can't navigate to our own flag :(
442                         Damage(self, self, self, 100000, DEATH_KILL, self.origin, '0 0 0');
443                 }
444                 havocbot_goalrating_ctf_carrieritems(1000, self.origin, 1000);
445                 navigation_goalrating_end();
446         }
447 };
448
449 //role offense:
450 //pick up armor and health
451 //if rockets < 25 || health < 100, change role to middle
452 //if carrying flag, change role to flag carrier
453 //if our flag taken, change role to interceptor
454 //(60-90 second timer) change role to middle
455 //go to enemy flag
456 void havocbot_role_ctf_offense()
457 {
458         local entity f;
459         if (self.flagcarried)
460         {
461                 dprint("changing role to carrier\n");
462                 self.havocbot_role = havocbot_role_ctf_carrier;
463                 self.havocbot_role_timeout = 0;
464                 self.bot_cantfindflag = time + 10;
465                 return;
466         }
467         // check our flag
468         f = ctf_worldflaglist;
469         while (f)
470         {
471                 if (self.team == f.team)
472                         break;
473                 f = f.ctf_worldflagnext;
474         }
475         if (f.cnt != FLAG_BASE && canreach(f))
476         {
477                 dprint("changing role to interceptor\n");
478                 self.havocbot_previous_role = self.havocbot_role;
479                 self.havocbot_role = havocbot_role_ctf_interceptor;
480                 self.havocbot_role_timeout = 0;
481                 return;
482         }
483         if (!self.havocbot_role_timeout)
484                 self.havocbot_role_timeout = time + random() * 30 + 60;
485         if (time > self.havocbot_role_timeout)
486         {
487                 dprint("changing role to middle\n");
488                 self.havocbot_role = havocbot_role_ctf_middle;
489                 self.havocbot_role_timeout = 0;
490                 return;
491         }
492         if (self.bot_strategytime < time)
493         {
494                 self.bot_strategytime = time + cvar("bot_ai_strategyinterval");
495                 navigation_goalrating_start();
496                 havocbot_goalrating_ctf_ourstolenflag(50000);
497                 havocbot_goalrating_ctf_enemyflag(30000);
498                 havocbot_goalrating_ctf_enemybase(20000);
499                 havocbot_goalrating_items(10000, self.origin, 10000);
500                 navigation_goalrating_end();
501         }
502 };
503
504 //role interceptor (temporary role):
505 //pick up items
506 //if carrying flag, change role to flag carrier
507 //if our flag is back, change role to previous role
508 //follow our flag
509 //go to least recently visited area
510 void havocbot_role_ctf_interceptor()
511 {
512         local entity f;
513         if (self.flagcarried)
514         {
515                 dprint("changing role to carrier\n");
516                 self.havocbot_role = havocbot_role_ctf_carrier;
517                 self.havocbot_role_timeout = 0;
518                 self.bot_cantfindflag = time + 10;
519                 return;
520         }
521         // check our flag
522         f = ctf_worldflaglist;
523         while (f)
524         {
525                 if (self.team == f.team)
526                         break;
527                 f = f.ctf_worldflagnext;
528         }
529         if (f.cnt == FLAG_BASE)
530         {
531                 dprint("changing role back\n");
532                 self.havocbot_role = self.havocbot_previous_role;
533                 self.havocbot_role_timeout = 0;
534                 return;
535         }
536
537         if (self.bot_strategytime < time)
538         {
539                 self.bot_strategytime = time + cvar("bot_ai_strategyinterval");
540                 navigation_goalrating_start();
541                 havocbot_goalrating_ctf_ourstolenflag(50000);
542                 havocbot_goalrating_ctf_droppedflags(50000);
543                 havocbot_goalrating_items(10000, self.origin, 10000);
544                 navigation_goalrating_end();
545         }
546 };
547
548 //role middle:
549 //pick up items
550 //if carrying flag, change role to flag carrier
551 //if our flag taken, change role to interceptor
552 //if see flag (of either team) follow it (this has many implications)
553 //(10-20 second timer) change role to defense or offense
554 //go to least recently visited area
555 void havocbot_role_ctf_middle()
556 {
557         local entity f;
558         if (self.flagcarried)
559         {
560                 dprint("changing role to carrier\n");
561                 self.havocbot_role = havocbot_role_ctf_carrier;
562                 self.havocbot_role_timeout = 0;
563                 self.bot_cantfindflag = time + 10;
564                 return;
565         }
566         // check our flag
567         f = ctf_worldflaglist;
568         while (f)
569         {
570                 if (self.team == f.team)
571                         break;
572                 f = f.ctf_worldflagnext;
573         }
574         if (f.cnt != FLAG_BASE && canreach(f))
575         {
576                 dprint("changing role to interceptor\n");
577                 self.havocbot_previous_role = self.havocbot_role;
578                 self.havocbot_role = havocbot_role_ctf_interceptor;
579                 self.havocbot_role_timeout = 0;
580                 return;
581         }
582         if (!self.havocbot_role_timeout)
583                 self.havocbot_role_timeout = time + random() * 10 + 10;
584         if (time > self.havocbot_role_timeout)
585         {
586                 if (random() < 0.5)
587                 {
588                         dprint("changing role to offense\n");
589                         self.havocbot_role = havocbot_role_ctf_offense;
590                 }
591                 else
592                 {
593                         dprint("changing role to defense\n");
594                         self.havocbot_role = havocbot_role_ctf_defense;
595                 }
596                 self.havocbot_role_timeout = 0;
597                 return;
598         }
599
600         if (self.bot_strategytime < time)
601         {
602                 self.bot_strategytime = time + cvar("bot_ai_strategyinterval");
603                 navigation_goalrating_start();
604                 havocbot_goalrating_ctf_ourstolenflag(50000);
605                 havocbot_goalrating_ctf_droppedflags(30000);
606                 //havocbot_goalrating_enemyplayers(1000, self.origin, 1000);
607                 havocbot_goalrating_items(10000, self.origin, 10000);
608                 navigation_goalrating_end();
609         }
610 };
611
612 //role defense:
613 //if rockets < 25 || health < 100, change role to middle
614 //if carrying flag, change role to flag carrier
615 //if our flag taken, change role to interceptor
616 //(30-50 second timer) change role to middle
617 //move to nearest unclaimed defense spot
618 void havocbot_role_ctf_defense()
619 {
620         local entity f;
621         if (self.flagcarried)
622         {
623                 dprint("changing role to carrier\n");
624                 self.havocbot_role = havocbot_role_ctf_carrier;
625                 self.havocbot_role_timeout = 0;
626                 self.bot_cantfindflag = time + 10;
627                 return;
628         }
629         // check our flag
630         f = ctf_worldflaglist;
631         while (f)
632         {
633                 if (self.team == f.team)
634                         break;
635                 f = f.ctf_worldflagnext;
636         }
637         if (f.cnt != FLAG_BASE && canreach(f))
638         {
639                 dprint("changing role to interceptor\n");
640                 self.havocbot_previous_role = self.havocbot_role;
641                 self.havocbot_role = havocbot_role_ctf_interceptor;
642                 self.havocbot_role_timeout = 0;
643                 return;
644         }
645         if (!self.havocbot_role_timeout)
646                 self.havocbot_role_timeout = time + random() * 20 + 30;
647         if (time > self.havocbot_role_timeout)
648         {
649                 dprint("changing role to middle\n");
650                 self.havocbot_role = havocbot_role_ctf_middle;
651                 self.havocbot_role_timeout = 0;
652                 return;
653         }
654         if (self.bot_strategytime < time)
655         {
656                 self.bot_strategytime = time + cvar("bot_ai_strategyinterval");
657                 navigation_goalrating_start();
658                 havocbot_goalrating_ctf_ourstolenflag(200000);
659                 havocbot_goalrating_ctf_droppedflags(50000);
660                 havocbot_goalrating_items(10000, f.origin, 10000);
661                 navigation_goalrating_end();
662         }
663         /*
664         // FIXME: place info_ctf_defensepoint entities in CTF maps and use them
665         // change position occasionally
666         if (time > self.bot_strategytime || self.goalentity.classname != "info_ctf_defensepoint")
667         {
668                 self.bot_strategytime = time + random() * 45 + 15;
669                 self.goalentity = world;
670                 head = findchain(classname, "info_ctf_defensepoint");
671                 while (head)
672                 {
673                         if (time > head.count)
674                         {
675                                 self.goalentity = head;
676                                 head.chain = world;
677                         }
678                         head = head.chain;
679                 }
680                 // if there are no defensepoints defined, switch to middle
681                 if (self.goalentity == world)
682                 {
683                         dprint("changing role to middle\n");
684                         self.havocbot_role = havocbot_role_ctf_middle;
685                         self.havocbot_role_timeout = 0;
686                         return;
687                 }
688         }
689         // keep anyone else from taking this spot
690         if (self.goalentity != world)
691                 self.goalentity.count = time + 0.5;
692         */
693 };
694
695 // CTF:
696 // choose a role according to the situation
697 void() havocbot_role_dm;
698 void havocbot_chooserole_ctf()
699 {
700         local float r;
701         dprint("choose CTF role...\n");
702         if (self.team == COLOR_TEAM3 || self.team == COLOR_TEAM4)
703                 self.havocbot_role = havocbot_role_ctf_rogue;
704         else
705         {
706                 r = random() * 3;
707                 if (r < 1)
708                         self.havocbot_role = havocbot_role_ctf_offense;
709                 else if (r < 2)
710                         self.havocbot_role = havocbot_role_ctf_middle;
711                 else
712                         self.havocbot_role = havocbot_role_ctf_defense;
713         }
714 };
715
716 //DOM:
717 //go to best items, or control points you don't own
718 void havocbot_role_dom()
719 {
720         if (self.bot_strategytime < time)
721         {
722                 self.bot_strategytime = time + cvar("bot_ai_strategyinterval");
723                 navigation_goalrating_start();
724                 havocbot_goalrating_controlpoints(10000, self.origin, 15000);
725                 havocbot_goalrating_items(8000, self.origin, 8000);
726                 //havocbot_goalrating_enemyplayers(3000, self.origin, 2000);
727                 //havocbot_goalrating_waypoints(1, self.origin, 1000);
728                 navigation_goalrating_end();
729         }
730 };
731
732 //DM:
733 //go to best items
734 void havocbot_role_dm()
735 {
736         if (self.bot_strategytime < time)
737         {
738                 self.bot_strategytime = time + cvar("bot_ai_strategyinterval");
739                 navigation_goalrating_start();
740                 havocbot_goalrating_items(10000, self.origin, 10000);
741                 havocbot_goalrating_enemyplayers(20000, self.origin, 10000);
742                 //havocbot_goalrating_waypoints(1, self.origin, 1000);
743                 navigation_goalrating_end();
744         }
745 };
746
747 //Race:
748 //go to next checkpoint, and annoy enemies
749 .float race_checkpoint;
750 void havocbot_role_race()
751 {
752         entity e;
753         if (self.bot_strategytime < time)
754         {
755                 self.bot_strategytime = time + cvar("bot_ai_strategyinterval");
756                 navigation_goalrating_start();
757                 /*
758                 havocbot_goalrating_items(100, self.origin, 10000);
759                 havocbot_goalrating_enemyplayers(500, self.origin, 20000);
760                 */
761
762                 for(e = world; (e = find(e, classname, "trigger_race_checkpoint")) != world; )
763                 {
764                         if(e.cnt == self.race_checkpoint)
765                         {
766                                 navigation_routerating(e, 1000000, 5000);
767                         }
768                         else if(self.race_checkpoint == -1)
769                         {
770                                 navigation_routerating(e, 1000000, 5000);
771                         }
772                 }
773
774                 navigation_goalrating_end();
775         }
776 };
777
778 void havocbot_chooserole_dm()
779 {
780         self.havocbot_role = havocbot_role_dm;
781 };
782
783 void havocbot_chooserole_race()
784 {
785         self.havocbot_role = havocbot_role_race;
786 };
787
788 void havocbot_chooserole_dom()
789 {
790         self.havocbot_role = havocbot_role_dom;
791 };
792
793
794
795
796
797
798 entity kh_worldkeylist;
799 .entity kh_worldkeynext;
800 void havocbot_goalrating_kh(float ratingscale_team, float ratingscale_dropped, float ratingscale_enemy)
801 {
802         local entity head;
803         for (head = kh_worldkeylist; head; head = head.kh_worldkeynext)
804         {
805                 if(head.owner == self)
806                         continue;
807                 if(!kh_tracking_enabled)
808                 {
809                         // if it's carried by our team we know about it
810                         // otherwise we have to see it to know about it
811                         if(!head.owner || head.team != self.team)
812                         {
813                                 traceline(self.origin + self.view_ofs, head.origin, MOVE_NOMONSTERS, self);
814                                 if (trace_fraction < 1 && trace_ent != head)
815                                         continue; // skip what I can't see
816                         }
817                 }
818                 if(!head.owner)
819                         navigation_routerating(head, ratingscale_dropped, 100000);
820                 else if(head.team == self.team)
821                         navigation_routerating(head, ratingscale_team, 100000);
822                 else
823                         navigation_routerating(head, ratingscale_enemy, 100000);
824         }
825 };
826
827 void() havocbot_role_kh_carrier;
828 void() havocbot_role_kh_defense;
829 void() havocbot_role_kh_offense;
830 void() havocbot_role_kh_freelancer;
831 void havocbot_role_kh_carrier()
832 {
833         if (!(self.items & IT_KEY1))
834         {
835                 dprint("changing role to freelancer\n");
836                 self.havocbot_role = havocbot_role_kh_freelancer;
837                 self.havocbot_role_timeout = 0;
838                 return;
839         }
840
841         if (self.bot_strategytime < time)
842         {
843                 self.bot_strategytime = time + cvar("bot_ai_strategyinterval");
844                 navigation_goalrating_start();
845
846                 if(kh_Key_AllOwnedByWhichTeam() == self.team)
847                         havocbot_goalrating_kh(100000, 1, 1); // bring home
848                 else
849                         havocbot_goalrating_kh(40000, 40000, 1000); // play defensively
850
851                 havocbot_goalrating_items(10000, self.origin, 10000);
852                 navigation_goalrating_end();
853         }
854 }
855
856 void havocbot_role_kh_defense()
857 {
858         if (self.items & IT_KEY1)
859         {
860                 dprint("changing role to carrier\n");
861                 self.havocbot_role = havocbot_role_kh_carrier;
862                 self.havocbot_role_timeout = 0;
863                 return;
864         }
865
866         if (!self.havocbot_role_timeout)
867                 self.havocbot_role_timeout = time + random() * 10 + 20;
868         if (time > self.havocbot_role_timeout)
869         {
870                 dprint("changing role to freelancer\n");
871                 self.havocbot_role = havocbot_role_kh_freelancer;
872                 self.havocbot_role_timeout = 0;
873                 return;
874         }
875
876         if (self.bot_strategytime < time)
877         {
878                 float key_owner_team;
879                 self.bot_strategytime = time + cvar("bot_ai_strategyinterval");
880                 navigation_goalrating_start();
881
882                 key_owner_team = kh_Key_AllOwnedByWhichTeam();
883                 if(key_owner_team == self.team)
884                         havocbot_goalrating_kh(100000, 1, 1); // defend key carriers
885                 else if(key_owner_team == -1)
886                         havocbot_goalrating_kh(40000, 10000, 1); // play defensively
887                 else
888                         havocbot_goalrating_kh(1, 1, 100000); // ATTACK ANYWAY
889
890                 havocbot_goalrating_items(10000, self.origin, 10000);
891                 navigation_goalrating_end();
892         }
893 };
894
895 void havocbot_role_kh_offense()
896 {
897         if (self.items & IT_KEY1)
898         {
899                 dprint("changing role to carrier\n");
900                 self.havocbot_role = havocbot_role_kh_carrier;
901                 self.havocbot_role_timeout = 0;
902                 return;
903         }
904
905         if (!self.havocbot_role_timeout)
906                 self.havocbot_role_timeout = time + random() * 10 + 20;
907         if (time > self.havocbot_role_timeout)
908         {
909                 dprint("changing role to freelancer\n");
910                 self.havocbot_role = havocbot_role_kh_freelancer;
911                 self.havocbot_role_timeout = 0;
912                 return;
913         }
914
915         if (self.bot_strategytime < time)
916         {
917                 float key_owner_team;
918
919                 self.bot_strategytime = time + cvar("bot_ai_strategyinterval");
920                 navigation_goalrating_start();
921
922                 key_owner_team = kh_Key_AllOwnedByWhichTeam();
923                 if(key_owner_team == self.team)
924                         havocbot_goalrating_kh(100000, 1, 1); // defend anyway
925                 else if(key_owner_team == -1)
926                         havocbot_goalrating_kh(1, 10000, 40000); // play offensively
927                 else
928                         havocbot_goalrating_kh(1, 1, 100000); // ATTACK! EMERGENCY!
929
930                 havocbot_goalrating_items(10000, self.origin, 10000);
931                 navigation_goalrating_end();
932         }
933 };
934
935 void havocbot_role_kh_freelancer()
936 {
937         if (self.items & IT_KEY1)
938         {
939                 dprint("changing role to carrier\n");
940                 self.havocbot_role = havocbot_role_kh_carrier;
941                 self.havocbot_role_timeout = 0;
942                 return;
943         }
944
945         if (!self.havocbot_role_timeout)
946                 self.havocbot_role_timeout = time + random() * 10 + 10;
947         if (time > self.havocbot_role_timeout)
948         {
949                 if (random() < 0.5)
950                 {
951                         dprint("changing role to offense\n");
952                         self.havocbot_role = havocbot_role_kh_offense;
953                 }
954                 else
955                 {
956                         dprint("changing role to defense\n");
957                         self.havocbot_role = havocbot_role_kh_defense;
958                 }
959                 self.havocbot_role_timeout = 0;
960                 return;
961         }
962
963         if (self.bot_strategytime < time)
964         {
965                 float key_owner_team;
966
967                 self.bot_strategytime = time + cvar("bot_ai_strategyinterval");
968                 navigation_goalrating_start();
969
970                 key_owner_team = kh_Key_AllOwnedByWhichTeam();
971                 if(key_owner_team == self.team)
972                         havocbot_goalrating_kh(100000, 1, 1); // defend anyway
973                 else if(key_owner_team == -1)
974                         havocbot_goalrating_kh(10000, 40000, 10000); // prefer dropped keys
975                 else
976                         havocbot_goalrating_kh(1, 1, 100000); // ATTACK ANYWAY
977
978                 havocbot_goalrating_items(10000, self.origin, 10000);
979                 navigation_goalrating_end();
980         }
981 };
982
983
984
985
986
987 void havocbot_chooserole_kh()
988 {
989         local float r;
990         r = random() * 3;
991         if (r < 1)
992                 self.havocbot_role = havocbot_role_kh_offense;
993         else if (r < 2)
994                 self.havocbot_role = havocbot_role_kh_defense;
995         else
996                 self.havocbot_role = havocbot_role_kh_freelancer;
997 };
998
999 void havocbot_chooserole()
1000 {
1001         dprint("choose a role...\n");
1002         navigation_routetogoal(world);
1003         self.bot_strategytime = -1;
1004         if (g_ctf)
1005                 havocbot_chooserole_ctf();
1006         else if (g_domination)
1007                 havocbot_chooserole_dom();
1008         else if (g_keyhunt)
1009                 havocbot_chooserole_kh();
1010         else if (g_race)
1011                 havocbot_chooserole_race();
1012         else // assume anything else is deathmatch
1013                 havocbot_chooserole_dm();
1014 };
1015