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