]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/server/havocbot_roles.qc
[11:57:02] <@LordHavoc> div0: or just remove the seeker for me and say "LordHavoc...
[divverent/nexuiz.git] / data / qcsrc / server / havocbot_roles.qc
1
2 .float havocbot_role_timeout;
3 .void() havocbot_previous_role;
4 .void() havocbot_role;
5 float bot_ignore_bots;
6
7 .float max_armorvalue;
8 float havocbot_pickupevalfunc(entity item)
9 {
10         float i, j, rating, base, position, need_shells, need_nails, need_rockets, need_cells;
11         rating = 0;
12
13         base = item.bot_pickupbasevalue;
14
15 #warning do not do this! This SUCKS! Use the weapon info data!
16         need_shells = self.weapons & WEPBIT_SHOTGUN;
17         need_nails = self.weapons & WEPBIT_UZI;
18         need_cells = self.weapons & ( WEPBIT_HOOK | WEPBIT_HLAC | WEPBIT_MINSTANEX | WEPBIT_NEX | WEPBIT_ELECTRO | WEPBIT_CRYLINK );
19         need_rockets = self.weapons & ( WEPBIT_ROCKET_LAUNCHER | WEPBIT_GRENADE_LAUNCHER | WEPBIT_HAGAR );
20
21         // Rate ammo items
22         if (item.ammo_shells)
23         if (self.ammo_shells < g_pickup_shells_max && need_cells )
24                 rating = rating + max(0, 1 - self.ammo_shells / g_pickup_shells_max);
25
26         if (item.ammo_nails)
27         if (self.ammo_nails < g_pickup_nails_max && need_nails )
28                 rating = rating + max(0, 1 - self.ammo_nails / g_pickup_nails_max);
29
30         if (item.ammo_rockets)
31         if (self.ammo_rockets < g_pickup_rockets_max && need_rockets)
32                 rating = rating + max(0, 1 - self.ammo_rockets / g_pickup_rockets_max);
33
34         if (item.ammo_cells)
35         if (self.ammo_cells < g_pickup_cells_max && need_cells)
36                 rating = rating + max(0, 1 - self.ammo_cells / g_pickup_cells_max);
37
38         // Rate health items (aim to grab half the max capacity)
39         if (item.armorvalue)
40         if (self.armorvalue < item.max_armorvalue * 0.5)
41                 rating = rating + max(0, 1 - self.armorvalue / (item.max_armorvalue * 0.5));
42
43         if (item.health)
44         if (self.health < item.max_health * 0.5)
45         {
46                 rating = rating + max(0, 1 - self.health / (item.max_health * 0.5));
47         }
48
49         // Rate weapons
50         if( item.weapons )
51         {
52                 // See if I have it already
53                 if( self.weapons & item.weapons == item.weapons )
54                 {
55                         // If I can pick it up
56                         if(rating)
57                         if not(cvar("g_weapon_stay"))
58                         {
59                                 // Skilled bots will grab more
60                                 local float divisor = 2;
61                                 rating += bound(0, skill / (10*divisor), 1/divisor);
62                         }
63                 }
64                 else
65                         rating += 1;
66
67                 // If custom weapon priorities for bots is enabled rate most wanted weapons higher
68                 if( bot_custom_weapon && rating )
69                 {
70                         for(i = WEP_FIRST; i < WEP_LAST ; ++i)
71                         {
72                                 // Find weapon
73                                 if( (get_weaponinfo(i)).weapons & item.weapons  != item.weapons )
74                                         continue;
75
76                                 // Find the highest position on any range
77                                 position = -1;
78                                 for(j = 0; j < WEP_LAST ; ++j){
79                                         if(
80                                                 bot_weapons_far[j] == i ||
81                                                 bot_weapons_mid[j] == i ||
82                                                 bot_weapons_close[j] == i
83                                         )
84                                         {
85                                                 position = j;
86                                                 break;
87                                         }
88                                 }
89
90                                 // Rate it
91                                 if (position >= 0 )
92                                 {
93                                         position = WEP_LAST - position;
94                                         // item.bot_pickupbasevalue is overwritten here
95                                         base = BOT_PICKUP_RATING_LOW + ( (BOT_PICKUP_RATING_HIGH - BOT_PICKUP_RATING_LOW) * (position / WEP_LAST ));
96                                         break;
97                                 }
98                         }
99                 }
100         }
101
102         // TODO: if the item is not recognized then default to item.bot_pickupevalfunc(self, item);
103         return base * rating;
104 };
105
106 void havocbot_goalrating_items(float ratingscale, vector org, float sradius)
107 {
108         local entity head;
109         local entity player;
110         local float rating, d, discard, distance, friend_distance, enemy_distance;
111         ratingscale = ratingscale * 0.0001; // items are rated around 10000 already
112         head = findchainfloat(bot_pickup, TRUE);
113
114         while (head)
115         {
116                 distance = vlen(head.origin - org);
117                 friend_distance = 10000; enemy_distance = 10000;
118                 rating = 0;
119
120                 if(!head.solid || distance > sradius || (head == self.ignoregoal && time < self.ignoregoaltime) )
121                 {
122                         head = head.chain;
123                         continue;
124                 }
125
126                 // Check if the item can be picked up safely
127                 if(head.classname == "droppedweapon")
128                 {
129                         traceline(head.origin, head.origin + '0 0 -1500', TRUE, world);
130
131                         d = pointcontents(trace_endpos + '0 0 1');
132                         if(d & CONTENT_WATER || d & CONTENT_SLIME || d & CONTENT_LAVA)
133                         {
134                                 head = head.chain;
135                                 continue;
136                         }
137                         if(tracebox_hits_trigger_hurt(head.origin, head.mins, head.maxs, trace_endpos))
138                         {
139                                 head = head.chain;
140                                 continue;
141                         }
142                 }
143                 else
144                 {
145                         // Ignore items under water
146                         traceline(head.origin + head.maxs, head.origin + head.maxs, MOVE_NORMAL, head);
147                         if(trace_dpstartcontents & DPCONTENTS_LIQUIDSMASK)
148                         {
149                                 head = head.chain;
150                                 continue;
151                         }
152                 }
153
154                 if(teams_matter)
155                 {
156                         discard = FALSE;
157
158                         FOR_EACH_PLAYER(player)
159                         {
160
161                                 if ( self == player || player.deadflag )
162                                         continue;
163
164                                 d = vlen(player.origin - head.origin); // distance between player and item
165
166                                 if ( player.team == self.team )
167                                 {
168                                         if ( clienttype(player) != CLIENTTYPE_REAL || discard )
169                                                 continue;
170
171                                         if( d > friend_distance)
172                                                 continue;
173
174                                         friend_distance = d;
175
176                                         discard = TRUE;
177
178                                         if( head.health && player.health > self.health )
179                                                 continue;
180
181                                         if( head.armorvalue && player.armorvalue > self.armorvalue)
182                                                 continue;
183
184                                         if( head.weapons )
185                                         if( (player.weapons & head.weapons) != head.weapons)
186                                                 continue;
187
188                                         if (head.ammo_shells && player.ammo_shells > self.ammo_shells)
189                                                 continue;
190
191                                         if (head.ammo_nails && player.ammo_nails > self.ammo_nails)
192                                                 continue;
193
194                                         if (head.ammo_rockets && player.ammo_rockets > self.ammo_rockets)
195                                                 continue;
196
197                                         if (head.ammo_cells && player.ammo_cells > self.ammo_cells )
198                                                 continue;
199
200                                         discard = FALSE;
201                                 }
202                                 else
203                                 {
204                                         // If enemy only track distances
205                                         // TODO: track only if visible ?
206                                         if( d < enemy_distance )
207                                                 enemy_distance = d;
208                                 }
209                         }
210
211                         // Rate the item only if no one needs it, or if an enemy is closer to it
212                         if (    (enemy_distance < friend_distance && distance < enemy_distance) ||
213                                 (friend_distance > cvar("bot_ai_friends_aware_pickup_radius") ) || !discard )
214                         {
215                         //      rating = head.bot_pickupevalfunc(self, head);
216                                 rating = havocbot_pickupevalfunc(head);
217                         }
218                 }
219                 else
220                 {
221                 //      rating = head.bot_pickupevalfunc(self, head);
222                         rating = havocbot_pickupevalfunc(head);
223                 }
224
225                 if(rating > 0)
226                         navigation_routerating(head, rating * ratingscale, 2000);
227                 head = head.chain;
228         }
229 };
230
231 void havocbot_goalrating_controlpoints(float ratingscale, vector org, float sradius)
232 {
233         local entity head;
234         head = findchain(classname, "dom_controlpoint");
235         while (head)
236         {
237                 if (vlen(head.origin - org) < sradius)
238                 {
239                         if(head.cnt > -1) // this is just being fought for
240                                 navigation_routerating(head, ratingscale, 5000);
241                         else if(head.goalentity.cnt == 0) // unclaimed point
242                                 navigation_routerating(head, ratingscale * 0.5, 5000);
243                         else if(head.goalentity.team != self.team) // other team's point
244                                 navigation_routerating(head, ratingscale * 0.2, 5000);
245                 }
246                 head = head.chain;
247         }
248 };
249
250 /*
251 // LordHavoc: this function was already unused, but for waypoints to be a
252 // useful goal the bots would have to seek out the least-recently-visited
253 // ones, not the closest
254 void havocbot_goalrating_waypoints(float ratingscale, vector org, float sradius)
255 {
256         local entity head;
257         head = findchain(classname, "waypoint");
258         while (head)
259         {
260                 if (vlen(head.origin - org) < sradius && vlen(head.origin - org) > 100)
261                         navigation_routerating(head, ratingscale, 2000);
262                 head = head.chain;
263         }
264 };
265 */
266
267 void havocbot_goalrating_enemyplayers(float ratingscale, vector org, float sradius)
268 {
269         local entity head;
270         local float t, noteam, distance;
271         noteam = ((self.team == 0) || !teams_matter); // fteqcc sucks
272
273         if (cvar("bot_nofire"))
274                 return;
275
276         // don't chase players if we're under water
277         if(self.waterlevel>WATERLEVEL_WETFEET)
278                 return;
279
280         FOR_EACH_PLAYER(head)
281         {
282                 // TODO: Merge this logic with the bot_shouldattack function
283                 if (self != head)
284                 if (head.health > 0)
285                 if ((noteam && (!bot_ignore_bots || clienttype(head) == CLIENTTYPE_REAL)) || head.team != self.team)
286                 {
287                         distance = vlen(head.origin - org);
288                         if (distance < 100 || distance > sradius)
289                                 continue;
290
291                         if(g_minstagib)
292                         if(head.items & IT_STRENGTH)
293                                 continue;
294
295                         // rate only visible enemies
296                         /*
297                         traceline(self.origin + self.view_ofs, head.origin, MOVE_NOMONSTERS, self);
298                         if (trace_fraction < 1 || trace_ent != head)
299                                 continue;
300                         */
301
302                         if(head.flags & FL_INWATER || head.flags & FL_PARTIALGROUND)
303                                 continue;
304
305                         // not falling
306                         if(head.flags & FL_ONGROUND == 0)
307                         {
308                                 traceline(head.origin, head.origin + '0 0 -1500', TRUE, world);
309                                 t = pointcontents(trace_endpos + '0 0 1');
310                                 if( t != CONTENT_SOLID )
311                                 if(t & CONTENT_WATER || t & CONTENT_SLIME || t & CONTENT_LAVA)
312                                         continue;
313                                 if(tracebox_hits_trigger_hurt(head.origin, head.mins, head.maxs, trace_endpos))
314                                         continue;
315                         }
316
317                         t = (self.health + self.armorvalue ) / (head.health + head.armorvalue );
318                         navigation_routerating(head, t * ratingscale, 2000);
319                 }
320         }
321 };
322
323 // choose a role according to the situation
324 void() havocbot_role_dm;
325
326 //DOM:
327 //go to best items, or control points you don't own
328 void havocbot_role_dom()
329 {
330         if(self.deadflag != DEAD_NO)
331                 return;
332
333         if (self.bot_strategytime < time)
334         {
335                 self.bot_strategytime = time + cvar("bot_ai_strategyinterval");
336                 navigation_goalrating_start();
337                 havocbot_goalrating_controlpoints(10000, self.origin, 15000);
338                 havocbot_goalrating_items(8000, self.origin, 8000);
339                 //havocbot_goalrating_enemyplayers(3000, self.origin, 2000);
340                 //havocbot_goalrating_waypoints(1, self.origin, 1000);
341                 navigation_goalrating_end();
342         }
343 };
344
345 //DM:
346 //go to best items
347 void havocbot_role_dm()
348 {
349         if(self.deadflag != DEAD_NO)
350                 return;
351
352         if (self.bot_strategytime < time)
353         {
354                 self.bot_strategytime = time + cvar("bot_ai_strategyinterval");
355                 navigation_goalrating_start();
356                 havocbot_goalrating_items(10000, self.origin, 10000);
357                 havocbot_goalrating_enemyplayers(20000, self.origin, 10000);
358                 //havocbot_goalrating_waypoints(1, self.origin, 1000);
359                 navigation_goalrating_end();
360         }
361 };
362
363 //Race:
364 //go to next checkpoint, and annoy enemies
365 .float race_checkpoint;
366 void havocbot_role_race()
367 {
368         if(self.deadflag != DEAD_NO)
369                 return;
370
371         entity e;
372         if (self.bot_strategytime < time)
373         {
374                 self.bot_strategytime = time + cvar("bot_ai_strategyinterval");
375                 navigation_goalrating_start();
376                 /*
377                 havocbot_goalrating_items(100, self.origin, 10000);
378                 havocbot_goalrating_enemyplayers(500, self.origin, 20000);
379                 */
380
381                 for(e = world; (e = find(e, classname, "trigger_race_checkpoint")) != world; )
382                 {
383                         if(e.cnt == self.race_checkpoint)
384                         {
385                                 navigation_routerating(e, 1000000, 5000);
386                         }
387                         else if(self.race_checkpoint == -1)
388                         {
389                                 navigation_routerating(e, 1000000, 5000);
390                         }
391                 }
392
393                 navigation_goalrating_end();
394         }
395 };
396
397 void havocbot_chooserole_dm()
398 {
399         self.havocbot_role = havocbot_role_dm;
400 };
401
402 void havocbot_chooserole_race()
403 {
404         self.havocbot_role = havocbot_role_race;
405 };
406
407 void havocbot_chooserole_dom()
408 {
409         self.havocbot_role = havocbot_role_dom;
410 };
411
412
413
414
415
416
417 entity kh_worldkeylist;
418 .entity kh_worldkeynext;
419 void havocbot_goalrating_kh(float ratingscale_team, float ratingscale_dropped, float ratingscale_enemy)
420 {
421         local entity head;
422         for (head = kh_worldkeylist; head; head = head.kh_worldkeynext)
423         {
424                 if(head.owner == self)
425                         continue;
426                 if(!kh_tracking_enabled)
427                 {
428                         // if it's carried by our team we know about it
429                         // otherwise we have to see it to know about it
430                         if(!head.owner || head.team != self.team)
431                         {
432                                 traceline(self.origin + self.view_ofs, head.origin, MOVE_NOMONSTERS, self);
433                                 if (trace_fraction < 1 && trace_ent != head)
434                                         continue; // skip what I can't see
435                         }
436                 }
437                 if(!head.owner)
438                         navigation_routerating(head, ratingscale_dropped, 100000);
439                 else if(head.team == self.team)
440                         navigation_routerating(head, ratingscale_team, 100000);
441                 else
442                         navigation_routerating(head, ratingscale_enemy, 100000);
443         }
444 };
445
446 void() havocbot_role_kh_carrier;
447 void() havocbot_role_kh_defense;
448 void() havocbot_role_kh_offense;
449 void() havocbot_role_kh_freelancer;
450 void havocbot_role_kh_carrier()
451 {
452         if(self.deadflag != DEAD_NO)
453                 return;
454
455         if (!(self.items & IT_KEY1))
456         {
457                 dprint("changing role to freelancer\n");
458                 self.havocbot_role = havocbot_role_kh_freelancer;
459                 self.havocbot_role_timeout = 0;
460                 return;
461         }
462
463         if (self.bot_strategytime < time)
464         {
465                 self.bot_strategytime = time + cvar("bot_ai_strategyinterval");
466                 navigation_goalrating_start();
467
468                 if(kh_Key_AllOwnedByWhichTeam() == self.team)
469                         havocbot_goalrating_kh(100000, 1, 1); // bring home
470                 else
471                         havocbot_goalrating_kh(40000, 40000, 1000); // play defensively
472
473                 havocbot_goalrating_items(10000, self.origin, 10000);
474                 navigation_goalrating_end();
475         }
476 }
477
478 void havocbot_role_kh_defense()
479 {
480         if(self.deadflag != DEAD_NO)
481                 return;
482
483         if (self.items & IT_KEY1)
484         {
485                 dprint("changing role to carrier\n");
486                 self.havocbot_role = havocbot_role_kh_carrier;
487                 self.havocbot_role_timeout = 0;
488                 return;
489         }
490
491         if (!self.havocbot_role_timeout)
492                 self.havocbot_role_timeout = time + random() * 10 + 20;
493         if (time > self.havocbot_role_timeout)
494         {
495                 dprint("changing role to freelancer\n");
496                 self.havocbot_role = havocbot_role_kh_freelancer;
497                 self.havocbot_role_timeout = 0;
498                 return;
499         }
500
501         if (self.bot_strategytime < time)
502         {
503                 float key_owner_team;
504                 self.bot_strategytime = time + cvar("bot_ai_strategyinterval");
505                 navigation_goalrating_start();
506
507                 key_owner_team = kh_Key_AllOwnedByWhichTeam();
508                 if(key_owner_team == self.team)
509                         havocbot_goalrating_kh(100000, 1, 1); // defend key carriers
510                 else if(key_owner_team == -1)
511                         havocbot_goalrating_kh(40000, 10000, 1); // play defensively
512                 else
513                         havocbot_goalrating_kh(1, 1, 100000); // ATTACK ANYWAY
514
515                 havocbot_goalrating_items(10000, self.origin, 10000);
516                 navigation_goalrating_end();
517         }
518 };
519
520 void havocbot_role_kh_offense()
521 {
522         if(self.deadflag != DEAD_NO)
523                 return;
524
525         if (self.items & IT_KEY1)
526         {
527                 dprint("changing role to carrier\n");
528                 self.havocbot_role = havocbot_role_kh_carrier;
529                 self.havocbot_role_timeout = 0;
530                 return;
531         }
532
533         if (!self.havocbot_role_timeout)
534                 self.havocbot_role_timeout = time + random() * 10 + 20;
535         if (time > self.havocbot_role_timeout)
536         {
537                 dprint("changing role to freelancer\n");
538                 self.havocbot_role = havocbot_role_kh_freelancer;
539                 self.havocbot_role_timeout = 0;
540                 return;
541         }
542
543         if (self.bot_strategytime < time)
544         {
545                 float key_owner_team;
546
547                 self.bot_strategytime = time + cvar("bot_ai_strategyinterval");
548                 navigation_goalrating_start();
549
550                 key_owner_team = kh_Key_AllOwnedByWhichTeam();
551                 if(key_owner_team == self.team)
552                         havocbot_goalrating_kh(100000, 1, 1); // defend anyway
553                 else if(key_owner_team == -1)
554                         havocbot_goalrating_kh(1, 10000, 40000); // play offensively
555                 else
556                         havocbot_goalrating_kh(1, 1, 100000); // ATTACK! EMERGENCY!
557
558                 havocbot_goalrating_items(10000, self.origin, 10000);
559                 navigation_goalrating_end();
560         }
561 };
562
563 void havocbot_role_kh_freelancer()
564 {
565         if(self.deadflag != DEAD_NO)
566                 return;
567
568         if (self.items & IT_KEY1)
569         {
570                 dprint("changing role to carrier\n");
571                 self.havocbot_role = havocbot_role_kh_carrier;
572                 self.havocbot_role_timeout = 0;
573                 return;
574         }
575
576         if (!self.havocbot_role_timeout)
577                 self.havocbot_role_timeout = time + random() * 10 + 10;
578         if (time > self.havocbot_role_timeout)
579         {
580                 if (random() < 0.5)
581                 {
582                         dprint("changing role to offense\n");
583                         self.havocbot_role = havocbot_role_kh_offense;
584                 }
585                 else
586                 {
587                         dprint("changing role to defense\n");
588                         self.havocbot_role = havocbot_role_kh_defense;
589                 }
590                 self.havocbot_role_timeout = 0;
591                 return;
592         }
593
594         if (self.bot_strategytime < time)
595         {
596                 float key_owner_team;
597
598                 self.bot_strategytime = time + cvar("bot_ai_strategyinterval");
599                 navigation_goalrating_start();
600
601                 key_owner_team = kh_Key_AllOwnedByWhichTeam();
602                 if(key_owner_team == self.team)
603                         havocbot_goalrating_kh(100000, 1, 1); // defend anyway
604                 else if(key_owner_team == -1)
605                         havocbot_goalrating_kh(10000, 40000, 10000); // prefer dropped keys
606                 else
607                         havocbot_goalrating_kh(1, 1, 100000); // ATTACK ANYWAY
608
609                 havocbot_goalrating_items(10000, self.origin, 10000);
610                 navigation_goalrating_end();
611         }
612 };
613
614 void havocbot_chooserole_kh()
615 {
616         local float r;
617
618         if(self.deadflag != DEAD_NO)
619                 return;
620
621         r = random() * 3;
622         if (r < 1)
623                 self.havocbot_role = havocbot_role_kh_offense;
624         else if (r < 2)
625                 self.havocbot_role = havocbot_role_kh_defense;
626         else
627                 self.havocbot_role = havocbot_role_kh_freelancer;
628 };
629
630 void havocbot_chooserole()
631 {
632         dprint("choosing a role...\n");
633         navigation_clearroute();
634         self.bot_strategytime = 0;
635         if (g_ctf)
636                 havocbot_chooserole_ctf();
637         else if (g_domination)
638                 havocbot_chooserole_dom();
639         else if (g_keyhunt)
640                 havocbot_chooserole_kh();
641         else if (g_race || g_cts)
642                 havocbot_chooserole_race();
643         else if (g_onslaught)
644                 havocbot_chooserole_ons();
645         else // assume anything else is deathmatch
646                 havocbot_chooserole_dm();
647 };
648