2 .float havocbot_role_timeout;
3 .void() havocbot_previous_role;
8 float havocbot_pickupevalfunc(entity item)
10 float i, j, rating, base, position, need_shells, need_nails, need_rockets, need_cells;
13 base = item.bot_pickupbasevalue;
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 );
23 if (self.ammo_shells < g_pickup_shells_max && need_cells )
24 rating = rating + max(0, 1 - self.ammo_shells / g_pickup_shells_max);
27 if (self.ammo_nails < g_pickup_nails_max && need_nails )
28 rating = rating + max(0, 1 - self.ammo_nails / g_pickup_nails_max);
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);
35 if (self.ammo_cells < g_pickup_cells_max && need_cells)
36 rating = rating + max(0, 1 - self.ammo_cells / g_pickup_cells_max);
38 // Rate health items (aim to grab half the max capacity)
40 if (self.armorvalue < item.max_armorvalue * 0.5)
41 rating = rating + max(0, 1 - self.armorvalue / (item.max_armorvalue * 0.5));
44 if (self.health < item.max_health * 0.5)
46 rating = rating + max(0, 1 - self.health / (item.max_health * 0.5));
52 // See if I have it already
53 if( self.weapons & item.weapons == item.weapons )
55 // If I can pick it up
57 if not(cvar("g_weapon_stay"))
59 // Skilled bots will grab more
60 local float divisor = 2;
61 rating += bound(0, skill / (10*divisor), 1/divisor);
67 // If custom weapon priorities for bots is enabled rate most wanted weapons higher
68 if( bot_custom_weapon && rating )
70 for(i = WEP_FIRST; i < WEP_LAST ; ++i)
73 if( (get_weaponinfo(i)).weapons & item.weapons != item.weapons )
76 // Find the highest position on any range
78 for(j = 0; j < WEP_LAST ; ++j){
80 bot_weapons_far[j] == i ||
81 bot_weapons_mid[j] == i ||
82 bot_weapons_close[j] == i
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 ));
102 // TODO: if the item is not recognized then default to item.bot_pickupevalfunc(self, item);
103 return base * rating;
106 void havocbot_goalrating_items(float ratingscale, vector org, float sradius)
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);
116 distance = vlen(head.origin - org);
117 friend_distance = 10000; enemy_distance = 10000;
120 if(!head.solid || distance > sradius || (head == self.ignoregoal && time < self.ignoregoaltime) )
126 // Check if the item can be picked up safely
127 if(head.classname == "droppedweapon")
129 traceline(head.origin, head.origin + '0 0 -1500', TRUE, world);
131 d = pointcontents(trace_endpos + '0 0 1');
132 if(d & CONTENT_WATER || d & CONTENT_SLIME || d & CONTENT_LAVA)
137 if(tracebox_hits_trigger_hurt(head.origin, head.mins, head.maxs, trace_endpos))
145 // Ignore items under water
146 traceline(head.origin + head.maxs, head.origin + head.maxs, MOVE_NORMAL, head);
147 if(trace_dpstartcontents & DPCONTENTS_LIQUIDSMASK)
158 FOR_EACH_PLAYER(player)
161 if ( self == player || player.deadflag )
164 d = vlen(player.origin - head.origin); // distance between player and item
166 if ( player.team == self.team )
168 if ( clienttype(player) != CLIENTTYPE_REAL || discard )
171 if( d > friend_distance)
178 if( head.health && player.health > self.health )
181 if( head.armorvalue && player.armorvalue > self.armorvalue)
185 if( (player.weapons & head.weapons) != head.weapons)
188 if (head.ammo_shells && player.ammo_shells > self.ammo_shells)
191 if (head.ammo_nails && player.ammo_nails > self.ammo_nails)
194 if (head.ammo_rockets && player.ammo_rockets > self.ammo_rockets)
197 if (head.ammo_cells && player.ammo_cells > self.ammo_cells )
204 // If enemy only track distances
205 // TODO: track only if visible ?
206 if( d < enemy_distance )
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 )
215 // rating = head.bot_pickupevalfunc(self, head);
216 rating = havocbot_pickupevalfunc(head);
221 // rating = head.bot_pickupevalfunc(self, head);
222 rating = havocbot_pickupevalfunc(head);
226 navigation_routerating(head, rating * ratingscale, 2000);
231 void havocbot_goalrating_controlpoints(float ratingscale, vector org, float sradius)
234 head = findchain(classname, "dom_controlpoint");
237 if (vlen(head.origin - org) < sradius)
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);
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)
257 head = findchain(classname, "waypoint");
260 if (vlen(head.origin - org) < sradius && vlen(head.origin - org) > 100)
261 navigation_routerating(head, ratingscale, 2000);
267 void havocbot_goalrating_enemyplayers(float ratingscale, vector org, float sradius)
270 local float t, noteam, distance;
271 noteam = ((self.team == 0) || !teams_matter); // fteqcc sucks
273 if (cvar("bot_nofire"))
276 // don't chase players if we're under water
277 if(self.waterlevel>WATERLEVEL_WETFEET)
280 FOR_EACH_PLAYER(head)
282 // TODO: Merge this logic with the bot_shouldattack function
285 if ((noteam && (!bot_ignore_bots || clienttype(head) == CLIENTTYPE_REAL)) || head.team != self.team)
287 distance = vlen(head.origin - org);
288 if (distance < 100 || distance > sradius)
292 if(head.items & IT_STRENGTH)
295 // rate only visible enemies
297 traceline(self.origin + self.view_ofs, head.origin, MOVE_NOMONSTERS, self);
298 if (trace_fraction < 1 || trace_ent != head)
302 if(head.flags & FL_INWATER || head.flags & FL_PARTIALGROUND)
306 if(head.flags & FL_ONGROUND == 0)
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)
313 if(tracebox_hits_trigger_hurt(head.origin, head.mins, head.maxs, trace_endpos))
317 t = (self.health + self.armorvalue ) / (head.health + head.armorvalue );
318 navigation_routerating(head, t * ratingscale, 2000);
323 // choose a role according to the situation
324 void() havocbot_role_dm;
327 //go to best items, or control points you don't own
328 void havocbot_role_dom()
330 if(self.deadflag != DEAD_NO)
333 if (self.bot_strategytime < time)
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();
347 void havocbot_role_dm()
349 if(self.deadflag != DEAD_NO)
352 if (self.bot_strategytime < time)
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();
364 //go to next checkpoint, and annoy enemies
365 .float race_checkpoint;
366 void havocbot_role_race()
368 if(self.deadflag != DEAD_NO)
372 if (self.bot_strategytime < time)
374 self.bot_strategytime = time + cvar("bot_ai_strategyinterval");
375 navigation_goalrating_start();
377 havocbot_goalrating_items(100, self.origin, 10000);
378 havocbot_goalrating_enemyplayers(500, self.origin, 20000);
381 for(e = world; (e = find(e, classname, "trigger_race_checkpoint")) != world; )
383 if(e.cnt == self.race_checkpoint)
385 navigation_routerating(e, 1000000, 5000);
387 else if(self.race_checkpoint == -1)
389 navigation_routerating(e, 1000000, 5000);
393 navigation_goalrating_end();
397 void havocbot_chooserole_dm()
399 self.havocbot_role = havocbot_role_dm;
402 void havocbot_chooserole_race()
404 self.havocbot_role = havocbot_role_race;
407 void havocbot_chooserole_dom()
409 self.havocbot_role = havocbot_role_dom;
417 entity kh_worldkeylist;
418 .entity kh_worldkeynext;
419 void havocbot_goalrating_kh(float ratingscale_team, float ratingscale_dropped, float ratingscale_enemy)
422 for (head = kh_worldkeylist; head; head = head.kh_worldkeynext)
424 if(head.owner == self)
426 if(!kh_tracking_enabled)
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)
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
438 navigation_routerating(head, ratingscale_dropped, 100000);
439 else if(head.team == self.team)
440 navigation_routerating(head, ratingscale_team, 100000);
442 navigation_routerating(head, ratingscale_enemy, 100000);
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()
452 if(self.deadflag != DEAD_NO)
455 if (!(self.items & IT_KEY1))
457 dprint("changing role to freelancer\n");
458 self.havocbot_role = havocbot_role_kh_freelancer;
459 self.havocbot_role_timeout = 0;
463 if (self.bot_strategytime < time)
465 self.bot_strategytime = time + cvar("bot_ai_strategyinterval");
466 navigation_goalrating_start();
468 if(kh_Key_AllOwnedByWhichTeam() == self.team)
469 havocbot_goalrating_kh(100000, 1, 1); // bring home
471 havocbot_goalrating_kh(40000, 40000, 1000); // play defensively
473 havocbot_goalrating_items(10000, self.origin, 10000);
474 navigation_goalrating_end();
478 void havocbot_role_kh_defense()
480 if(self.deadflag != DEAD_NO)
483 if (self.items & IT_KEY1)
485 dprint("changing role to carrier\n");
486 self.havocbot_role = havocbot_role_kh_carrier;
487 self.havocbot_role_timeout = 0;
491 if (!self.havocbot_role_timeout)
492 self.havocbot_role_timeout = time + random() * 10 + 20;
493 if (time > self.havocbot_role_timeout)
495 dprint("changing role to freelancer\n");
496 self.havocbot_role = havocbot_role_kh_freelancer;
497 self.havocbot_role_timeout = 0;
501 if (self.bot_strategytime < time)
503 float key_owner_team;
504 self.bot_strategytime = time + cvar("bot_ai_strategyinterval");
505 navigation_goalrating_start();
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
513 havocbot_goalrating_kh(1, 1, 100000); // ATTACK ANYWAY
515 havocbot_goalrating_items(10000, self.origin, 10000);
516 navigation_goalrating_end();
520 void havocbot_role_kh_offense()
522 if(self.deadflag != DEAD_NO)
525 if (self.items & IT_KEY1)
527 dprint("changing role to carrier\n");
528 self.havocbot_role = havocbot_role_kh_carrier;
529 self.havocbot_role_timeout = 0;
533 if (!self.havocbot_role_timeout)
534 self.havocbot_role_timeout = time + random() * 10 + 20;
535 if (time > self.havocbot_role_timeout)
537 dprint("changing role to freelancer\n");
538 self.havocbot_role = havocbot_role_kh_freelancer;
539 self.havocbot_role_timeout = 0;
543 if (self.bot_strategytime < time)
545 float key_owner_team;
547 self.bot_strategytime = time + cvar("bot_ai_strategyinterval");
548 navigation_goalrating_start();
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
556 havocbot_goalrating_kh(1, 1, 100000); // ATTACK! EMERGENCY!
558 havocbot_goalrating_items(10000, self.origin, 10000);
559 navigation_goalrating_end();
563 void havocbot_role_kh_freelancer()
565 if(self.deadflag != DEAD_NO)
568 if (self.items & IT_KEY1)
570 dprint("changing role to carrier\n");
571 self.havocbot_role = havocbot_role_kh_carrier;
572 self.havocbot_role_timeout = 0;
576 if (!self.havocbot_role_timeout)
577 self.havocbot_role_timeout = time + random() * 10 + 10;
578 if (time > self.havocbot_role_timeout)
582 dprint("changing role to offense\n");
583 self.havocbot_role = havocbot_role_kh_offense;
587 dprint("changing role to defense\n");
588 self.havocbot_role = havocbot_role_kh_defense;
590 self.havocbot_role_timeout = 0;
594 if (self.bot_strategytime < time)
596 float key_owner_team;
598 self.bot_strategytime = time + cvar("bot_ai_strategyinterval");
599 navigation_goalrating_start();
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
607 havocbot_goalrating_kh(1, 1, 100000); // ATTACK ANYWAY
609 havocbot_goalrating_items(10000, self.origin, 10000);
610 navigation_goalrating_end();
614 void havocbot_chooserole_kh()
618 if(self.deadflag != DEAD_NO)
623 self.havocbot_role = havocbot_role_kh_offense;
625 self.havocbot_role = havocbot_role_kh_defense;
627 self.havocbot_role = havocbot_role_kh_freelancer;
630 void havocbot_chooserole()
632 dprint("choosing a role...\n");
633 navigation_clearroute();
634 self.bot_strategytime = 0;
636 havocbot_chooserole_ctf();
637 else if (g_domination)
638 havocbot_chooserole_dom();
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();