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