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