#include "../../_all.qh" #include "havocbot.qh" #include "role_keyhunt.qh" #include "../bot.qh" #include "../navigation.qh" .float max_armorvalue; .float havocbot_role_timeout; .void() havocbot_previous_role; .void() havocbot_role; void havocbot_goalrating_items(float ratingscale, vector org, float sradius) {SELFPARAM(); entity head; entity player; float rating, d, discard, distance, friend_distance, enemy_distance; vector o; ratingscale = ratingscale * 0.0001; // items are rated around 10000 already head = findchainfloat(bot_pickup, true); while (head) { o = (head.absmin + head.absmax) * 0.5; distance = vlen(o - org); friend_distance = 10000; enemy_distance = 10000; rating = 0; if(!head.solid || distance > sradius || (head == self.ignoregoal && time < self.ignoregoaltime) ) { head = head.chain; continue; } // Check if the item can be picked up safely if(head.classname == "droppedweapon") { traceline(o, o + '0 0 -1500', true, world); d = pointcontents(trace_endpos + '0 0 1'); if(d & CONTENT_WATER || d & CONTENT_SLIME || d & CONTENT_LAVA) { head = head.chain; continue; } if(tracebox_hits_trigger_hurt(head.origin, head.mins, head.maxs, trace_endpos)) { head = head.chain; continue; } } else { // Ignore items under water traceline(head.origin + head.maxs, head.origin + head.maxs, MOVE_NORMAL, head); if(trace_dpstartcontents & DPCONTENTS_LIQUIDSMASK) { head = head.chain; continue; } } if(teamplay) { discard = false; FOR_EACH_PLAYER(player) { if ( self == player || player.deadflag ) continue; d = vlen(player.origin - o); // distance between player and item if ( player.team == self.team ) { if ( !IS_REAL_CLIENT(player) || discard ) continue; if( d > friend_distance) continue; friend_distance = d; discard = true; if( head.health && player.health > self.health ) continue; if( head.armorvalue && player.armorvalue > self.armorvalue) continue; if( head.weapons ) if( head.weapons & ~player.weapons ) continue; if (head.ammo_shells && player.ammo_shells > self.ammo_shells) continue; if (head.ammo_nails && player.ammo_nails > self.ammo_nails) continue; if (head.ammo_rockets && player.ammo_rockets > self.ammo_rockets) continue; if (head.ammo_cells && player.ammo_cells > self.ammo_cells) continue; if (head.ammo_plasma && player.ammo_plasma > self.ammo_plasma) continue; discard = false; } else { // If enemy only track distances // TODO: track only if visible ? if( d < enemy_distance ) enemy_distance = d; } } // Rate the item only if no one needs it, or if an enemy is closer to it if ( (enemy_distance < friend_distance && distance < enemy_distance) || (friend_distance > autocvar_bot_ai_friends_aware_pickup_radius ) || !discard ) rating = head.bot_pickupevalfunc(self, head); } else rating = head.bot_pickupevalfunc(self, head); if(rating > 0) navigation_routerating(head, rating * ratingscale, 2000); head = head.chain; } } void havocbot_goalrating_controlpoints(float ratingscale, vector org, float sradius) {SELFPARAM(); entity head; head = findchain(classname, "dom_controlpoint"); while (head) { if (vlen(( ( head.absmin + head.absmax ) * 0.5 ) - org) < sradius) { if(head.cnt > -1) // this is just being fought for navigation_routerating(head, ratingscale, 5000); else if(head.goalentity.cnt == 0) // unclaimed point navigation_routerating(head, ratingscale * 0.5, 5000); else if(head.goalentity.team != self.team) // other team's point navigation_routerating(head, ratingscale * 0.2, 5000); } head = head.chain; } } void havocbot_goalrating_enemyplayers(float ratingscale, vector org, float sradius) {SELFPARAM(); entity head; int t; float distance; noref bool noteam = ((self.team == 0) || !teamplay); if (autocvar_bot_nofire) return; // don't chase players if we're under water if(self.waterlevel>WATERLEVEL_WETFEET) return; FOR_EACH_PLAYER(head) { // TODO: Merge this logic with the bot_shouldattack function if(bot_shouldattack(head)) { distance = vlen(head.origin - org); if (distance < 100 || distance > sradius) continue; // rate only visible enemies /* traceline(self.origin + self.view_ofs, head.origin, MOVE_NOMONSTERS, self); if (trace_fraction < 1 || trace_ent != head) continue; */ if((head.flags & FL_INWATER) || (head.flags & FL_PARTIALGROUND)) continue; // not falling if((head.flags & FL_ONGROUND) == 0) { traceline(head.origin, head.origin + '0 0 -1500', true, world); t = pointcontents(trace_endpos + '0 0 1'); if( t != CONTENT_SOLID ) if(t & CONTENT_WATER || t & CONTENT_SLIME || t & CONTENT_LAVA) continue; if(tracebox_hits_trigger_hurt(head.origin, head.mins, head.maxs, trace_endpos)) continue; } // TODO: rate waypoints near the targetted player at that moment, instead of the player itself // adding a player as a goal seems to be quite dangerous, especially on space maps // remove hack in navigation_poptouchedgoals() after performing this change t = (self.health + self.armorvalue ) / (head.health + head.armorvalue ); navigation_routerating(head, t * ratingscale, 2000); } } } // legacy bot role for standard gamemodes // go to best items void havocbot_role_generic() {SELFPARAM(); if(self.deadflag != DEAD_NO) return; if (self.bot_strategytime < time) { self.bot_strategytime = time + autocvar_bot_ai_strategyinterval; navigation_goalrating_start(); havocbot_goalrating_items(10000, self.origin, 10000); havocbot_goalrating_enemyplayers(20000, self.origin, 10000); //havocbot_goalrating_waypoints(1, self.origin, 1000); navigation_goalrating_end(); } } void havocbot_chooserole_generic() {SELFPARAM(); self.havocbot_role = havocbot_role_generic; } void havocbot_chooserole() {SELFPARAM(); LOG_TRACE("choosing a role...\n"); self.bot_strategytime = 0; if (MUTATOR_CALLHOOK(HavocBot_ChooseRole, self)) return; else if (g_keyhunt) havocbot_chooserole_kh(); else havocbot_chooserole_generic(); }