}
}
+.float ladder_time;
+
void PM_check_hitground()
{SELFPARAM();
#ifdef SVQC
PM_ClientMovement_Move();
}
+.entity ladder_entity;
+
void PM_ladder(float maxspd_mod)
{SELFPARAM();
// on a spawnfunc_func_ladder or swimming in spawnfunc_func_water
#include "../../../server/g_subs.qh"
#include "../../../server/g_damage.qh"
-#include "../../../server/bot/bot.qh"
#include "../../../common/csqcmodel_settings.qh"
#include "../../../csqcmodellib/sv_model.qh"
#include "../../../server/weapons/common.qh"
#include "../util.qh"
-#ifdef SVQC
-#include "../../server/bot/aim.qh"
-#endif
-
REGISTRY(Weapons, 72) // Increase as needed. Can be up to 72.
REGISTER_REGISTRY(RegisterWeapons)
entity get_weaponinfo(int id);
int autocvar__campaign_index;
string autocvar__campaign_name;
bool autocvar__sv_init;
-float autocvar_bot_ai_aimskill_blendrate;
-float autocvar_bot_ai_aimskill_firetolerance_distdegrees;
-float autocvar_bot_ai_aimskill_firetolerance_maxdegrees;
-float autocvar_bot_ai_aimskill_firetolerance_mindegrees;
-float autocvar_bot_ai_aimskill_fixedrate;
-float autocvar_bot_ai_aimskill_mouse;
-float autocvar_bot_ai_aimskill_offset;
-float autocvar_bot_ai_aimskill_order_filter_1st;
-float autocvar_bot_ai_aimskill_order_filter_2nd;
-float autocvar_bot_ai_aimskill_order_filter_3th;
-float autocvar_bot_ai_aimskill_order_filter_4th;
-float autocvar_bot_ai_aimskill_order_filter_5th;
-float autocvar_bot_ai_aimskill_order_mix_1st;
-float autocvar_bot_ai_aimskill_order_mix_2nd;
-float autocvar_bot_ai_aimskill_order_mix_3th;
-float autocvar_bot_ai_aimskill_order_mix_4th;
-float autocvar_bot_ai_aimskill_order_mix_5th;
-float autocvar_bot_ai_aimskill_think;
-float autocvar_bot_ai_bunnyhop_firstjumpdelay;
-float autocvar_bot_ai_bunnyhop_skilloffset;
-float autocvar_bot_ai_bunnyhop_startdistance;
-float autocvar_bot_ai_bunnyhop_stopdistance;
-float autocvar_bot_ai_chooseweaponinterval;
-string autocvar_bot_ai_custom_weapon_priority_close;
-string autocvar_bot_ai_custom_weapon_priority_distances;
-string autocvar_bot_ai_custom_weapon_priority_far;
-string autocvar_bot_ai_custom_weapon_priority_mid;
-float autocvar_bot_ai_dangerdetectioninterval;
-float autocvar_bot_ai_dangerdetectionupdates;
-float autocvar_bot_ai_enemydetectioninterval;
-float autocvar_bot_ai_enemydetectionradius;
-float autocvar_bot_ai_friends_aware_pickup_radius;
-float autocvar_bot_ai_ignoregoal_timeout;
-float autocvar_bot_ai_keyboard_distance;
-float autocvar_bot_ai_keyboard_threshold;
-float autocvar_bot_ai_navigation_jetpack;
-float autocvar_bot_ai_navigation_jetpack_mindistance;
-float autocvar_bot_ai_strategyinterval;
-float autocvar_bot_ai_thinkinterval;
-bool autocvar_bot_ai_weapon_combo;
-float autocvar_bot_ai_weapon_combo_threshold;
-string autocvar_bot_config_file;
-bool autocvar_bot_god;
-bool autocvar_bot_ignore_bots;
-bool autocvar_bot_join_empty;
-bool autocvar_bot_navigation_ignoreplayers;
-bool autocvar_bot_nofire;
-#define autocvar_bot_number cvar("bot_number")
-#define autocvar_bot_prefix cvar_string("bot_prefix")
-bool autocvar_bot_sound_monopoly;
-#define autocvar_bot_suffix cvar_string("bot_suffix")
-bool autocvar_bot_usemodelnames;
-int autocvar_bot_vs_human;
-int autocvar_bot_debug;
-bool autocvar_bot_debug_tracewalk;
-bool autocvar_bot_debug_goalstack;
-bool autocvar_bot_wander_enable;
int autocvar_captureleadlimit_override;
#define autocvar_capturelimit_override cvar("capturelimit_override")
#define autocvar_developer cvar("developer")
float autocvar_g_ctf_dropped_capture_radius;
float autocvar_g_cts_finish_kill_delay;
bool autocvar_g_cts_selfdamage;
-bool autocvar_g_debug_bot_commands;
int autocvar_g_domination_default_teams;
bool autocvar_g_domination_disable_frags;
int autocvar_g_domination_point_amt;
float autocvar_g_turrets_targetscan_mindelay;
bool autocvar_g_use_ammunition;
bool autocvar_g_waypointeditor;
-int autocvar_g_waypointeditor_auto;
bool autocvar_g_waypoints_for_items;
float autocvar_g_weapon_charge_colormod_blue_full;
float autocvar_g_weapon_charge_colormod_blue_half;
bool autocvar_samelevel;
string autocvar_sessionid;
#define autocvar_skill cvar("skill")
-float autocvar_skill_auto;
#define autocvar_slowmo cvar("slowmo")
float autocvar_snd_soundradius;
int autocvar_spawn_debug;
string autocvar_sv_jumpspeedcap_max;
float autocvar_sv_jumpspeedcap_max_disable_on_ramps;
string autocvar_sv_jumpspeedcap_min;
-float autocvar_sv_jumpvelocity;
bool autocvar_sv_logscores_bots;
bool autocvar_sv_logscores_console;
bool autocvar_sv_logscores_file;
int autocvar_timelimit_overtimes;
float autocvar_timelimit_suddendeath;
#define autocvar_utf8_enable cvar("utf8_enable")
-bool autocvar_waypoint_benchmark;
float autocvar_sv_gameplayfix_gravityunaffectedbyticrate;
bool autocvar_sv_gameplayfix_upwardvelocityclearsongroundflag;
float autocvar_g_trueaim_minrange;
+++ /dev/null
-#include "aim.qh"
-#include "../_all.qh"
-
-#include "bot.qh"
-
-#include "../mutators/mutators_include.qh"
-
-// traces multiple trajectories to find one that will impact the target
-// 'end' vector is the place it aims for,
-// returns true only if it hit targ (don't target non-solid entities)
-
-float findtrajectorywithleading(vector org, vector m1, vector m2, entity targ, float shotspeed, float shotspeedupward, float maxtime, float shotdelay, entity ignore)
-{
- float c, savesolid, shottime;
- vector dir, end, v, o;
- if (shotspeed < 1)
- return false; // could cause division by zero if calculated
- if (targ.solid < SOLID_BBOX) // SOLID_NOT and SOLID_TRIGGER
- return false; // could never hit it
- if (!tracetossent)
- tracetossent = spawn();
- tracetossent.owner = ignore;
- setsize(tracetossent, m1, m2);
- savesolid = targ.solid;
- targ.solid = SOLID_NOT;
- o = (targ.absmin + targ.absmax) * 0.5;
- shottime = ((vlen(o - org) / shotspeed) + shotdelay);
- v = targ.velocity * shottime + o;
- tracebox(o, targ.mins, targ.maxs, v, false, targ);
- v = trace_endpos;
- end = v + (targ.mins + targ.maxs) * 0.5;
- if ((vlen(end - org) / shotspeed + 0.2) > maxtime)
- {
- // out of range
- targ.solid = savesolid;
- return false;
- }
-
- if (!tracetossfaketarget)
- tracetossfaketarget = spawn();
- tracetossfaketarget.solid = savesolid;
- tracetossfaketarget.movetype = targ.movetype;
- _setmodel(tracetossfaketarget, targ.model); // no low precision
- tracetossfaketarget.model = targ.model;
- tracetossfaketarget.modelindex = targ.modelindex;
- setsize(tracetossfaketarget, targ.mins, targ.maxs);
- setorigin(tracetossfaketarget, v);
-
- c = 0;
- dir = normalize(end - org);
- while (c < 10) // 10 traces
- {
- setorigin(tracetossent, org); // reset
- tracetossent.velocity = findtrajectory_velocity = normalize(dir) * shotspeed + shotspeedupward * '0 0 1';
- tracetoss(tracetossent, ignore); // love builtin functions...
- if (trace_ent == tracetossfaketarget) // done
- {
- targ.solid = savesolid;
-
- // make it disappear
- tracetossfaketarget.solid = SOLID_NOT;
- tracetossfaketarget.movetype = MOVETYPE_NONE;
- tracetossfaketarget.model = "";
- tracetossfaketarget.modelindex = 0;
- // relink to remove it from physics considerations
- setorigin(tracetossfaketarget, v);
-
- return true;
- }
- dir.z = dir.z + 0.1; // aim up a little more
- c = c + 1;
- }
- targ.solid = savesolid;
-
- // make it disappear
- tracetossfaketarget.solid = SOLID_NOT;
- tracetossfaketarget.movetype = MOVETYPE_NONE;
- tracetossfaketarget.model = "";
- tracetossfaketarget.modelindex = 0;
- // relink to remove it from physics considerations
- setorigin(tracetossfaketarget, v);
-
- // leave a valid one even if it won't reach
- findtrajectory_velocity = normalize(end - org) * shotspeed + shotspeedupward * '0 0 1';
- return false;
-}
-
-void lag_update()
-{SELFPARAM();
- if (self.lag1_time) if (time > self.lag1_time) {self.lag_func(self.lag1_time, self.lag1_float1, self.lag1_float2, self.lag1_entity1, self.lag1_vec1, self.lag1_vec2, self.lag1_vec3, self.lag1_vec4);self.lag1_time = 0;}
- if (self.lag2_time) if (time > self.lag2_time) {self.lag_func(self.lag2_time, self.lag2_float1, self.lag2_float2, self.lag2_entity1, self.lag2_vec1, self.lag2_vec2, self.lag2_vec3, self.lag2_vec4);self.lag2_time = 0;}
- if (self.lag3_time) if (time > self.lag3_time) {self.lag_func(self.lag3_time, self.lag3_float1, self.lag3_float2, self.lag3_entity1, self.lag3_vec1, self.lag3_vec2, self.lag3_vec3, self.lag3_vec4);self.lag3_time = 0;}
- if (self.lag4_time) if (time > self.lag4_time) {self.lag_func(self.lag4_time, self.lag4_float1, self.lag4_float2, self.lag4_entity1, self.lag4_vec1, self.lag4_vec2, self.lag4_vec3, self.lag4_vec4);self.lag4_time = 0;}
- if (self.lag5_time) if (time > self.lag5_time) {self.lag_func(self.lag5_time, self.lag5_float1, self.lag5_float2, self.lag5_entity1, self.lag5_vec1, self.lag5_vec2, self.lag5_vec3, self.lag5_vec4);self.lag5_time = 0;}
-}
-
-float lag_additem(float t, float f1, float f2, entity e1, vector v1, vector v2, vector v3, vector v4)
-{SELFPARAM();
- if (self.lag1_time == 0) {self.lag1_time = t;self.lag1_float1 = f1;self.lag1_float2 = f2;self.lag1_entity1 = e1;self.lag1_vec1 = v1;self.lag1_vec2 = v2;self.lag1_vec3 = v3;self.lag1_vec4 = v4;return true;}
- if (self.lag2_time == 0) {self.lag2_time = t;self.lag2_float1 = f1;self.lag2_float2 = f2;self.lag2_entity1 = e1;self.lag2_vec1 = v1;self.lag2_vec2 = v2;self.lag2_vec3 = v3;self.lag2_vec4 = v4;return true;}
- if (self.lag3_time == 0) {self.lag3_time = t;self.lag3_float1 = f1;self.lag3_float2 = f2;self.lag3_entity1 = e1;self.lag3_vec1 = v1;self.lag3_vec2 = v2;self.lag3_vec3 = v3;self.lag3_vec4 = v4;return true;}
- if (self.lag4_time == 0) {self.lag4_time = t;self.lag4_float1 = f1;self.lag4_float2 = f2;self.lag4_entity1 = e1;self.lag4_vec1 = v1;self.lag4_vec2 = v2;self.lag4_vec3 = v3;self.lag4_vec4 = v4;return true;}
- if (self.lag5_time == 0) {self.lag5_time = t;self.lag5_float1 = f1;self.lag5_float2 = f2;self.lag5_entity1 = e1;self.lag5_vec1 = v1;self.lag5_vec2 = v2;self.lag5_vec3 = v3;self.lag5_vec4 = v4;return true;}
- // no room for it (what is the best thing to do here??)
- return false;
-}
-
-float bot_shouldattack(entity e)
-{SELFPARAM();
- if (e.team == self.team)
- {
- if (e == self)
- return false;
- if (teamplay)
- if (e.team != 0)
- return false;
- }
-
- if(e.frozen)
- return false;
-
- if(teamplay)
- {
- if(e.team==0)
- return false;
- }
- else if(bot_ignore_bots)
- if(IS_BOT_CLIENT(e))
- return false;
-
- if (!e.takedamage)
- return false;
- if (e.deadflag)
- return false;
- if (e.BUTTON_CHAT)
- return false;
- if(e.flags & FL_NOTARGET)
- return false;
-
- if(MUTATOR_CALLHOOK(BotShouldAttack, e))
- return false;
-
- return true;
-}
-
-void bot_lagfunc(float t, float f1, float f2, entity e1, vector v1, vector v2, vector v3, vector v4)
-{SELFPARAM();
- if(self.flags & FL_INWATER)
- {
- self.bot_aimtarg = world;
- return;
- }
- self.bot_aimtarg = e1;
- self.bot_aimlatency = self.ping; // FIXME? Shouldn't this be in the lag item?
- self.bot_aimselforigin = v1;
- self.bot_aimselfvelocity = v2;
- self.bot_aimtargorigin = v3;
- self.bot_aimtargvelocity = v4;
- if(skill <= 0)
- self.bot_canfire = (random() < 0.8);
- else if(skill <= 1)
- self.bot_canfire = (random() < 0.9);
- else if(skill <= 2)
- self.bot_canfire = (random() < 0.95);
- else
- self.bot_canfire = 1;
-}
-
-float bot_aimdir(vector v, float maxfiredeviation)
-{SELFPARAM();
- float dist, delta_t, blend;
- vector desiredang, diffang;
-
- //dprint("aim ", self.netname, ": old:", vtos(self.v_angle));
- // make sure v_angle is sane first
- self.v_angle_y = self.v_angle.y - floor(self.v_angle.y / 360) * 360;
- self.v_angle_z = 0;
-
- // get the desired angles to aim at
- //dprint(" at:", vtos(v));
- v = normalize(v);
- //te_lightning2(world, self.origin + self.view_ofs, self.origin + self.view_ofs + v * 200);
- if (time >= self.bot_badaimtime)
- {
- self.bot_badaimtime = max(self.bot_badaimtime + 0.3, time);
- self.bot_badaimoffset = randomvec() * bound(0, 5 - 0.5 * (skill+self.bot_offsetskill), 5) * autocvar_bot_ai_aimskill_offset;
- }
- desiredang = vectoangles(v) + self.bot_badaimoffset;
- //dprint(" desired:", vtos(desiredang));
- if (desiredang.x >= 180)
- desiredang.x = desiredang.x - 360;
- desiredang.x = bound(-90, 0 - desiredang.x, 90);
- desiredang.z = self.v_angle.z;
- //dprint(" / ", vtos(desiredang));
-
- //// pain throws off aim
- //if (self.bot_painintensity)
- //{
- // // shake from pain
- // desiredang = desiredang + randomvec() * self.bot_painintensity * 0.2;
- //}
-
- // calculate turn angles
- diffang = (desiredang - self.bot_olddesiredang);
- // wrap yaw turn
- diffang.y = diffang.y - floor(diffang.y / 360) * 360;
- if (diffang.y >= 180)
- diffang.y = diffang.y - 360;
- self.bot_olddesiredang = desiredang;
- //dprint(" diff:", vtos(diffang));
-
- delta_t = time-self.bot_prevaimtime;
- self.bot_prevaimtime = time;
- // Here we will try to anticipate the comming aiming direction
- self.bot_1st_order_aimfilter= self.bot_1st_order_aimfilter
- + (diffang * (1 / delta_t) - self.bot_1st_order_aimfilter) * bound(0, autocvar_bot_ai_aimskill_order_filter_1st,1);
- self.bot_2nd_order_aimfilter= self.bot_2nd_order_aimfilter
- + (self.bot_1st_order_aimfilter - self.bot_2nd_order_aimfilter) * bound(0, autocvar_bot_ai_aimskill_order_filter_2nd,1);
- self.bot_3th_order_aimfilter= self.bot_3th_order_aimfilter
- + (self.bot_2nd_order_aimfilter - self.bot_3th_order_aimfilter) * bound(0, autocvar_bot_ai_aimskill_order_filter_3th,1);
- self.bot_4th_order_aimfilter= self.bot_4th_order_aimfilter
- + (self.bot_3th_order_aimfilter - self.bot_4th_order_aimfilter) * bound(0, autocvar_bot_ai_aimskill_order_filter_4th,1);
- self.bot_5th_order_aimfilter= self.bot_5th_order_aimfilter
- + (self.bot_4th_order_aimfilter - self.bot_5th_order_aimfilter) * bound(0, autocvar_bot_ai_aimskill_order_filter_5th,1);
-
- //blend = (bound(0,skill,10)*0.1)*pow(1-bound(0,skill,10)*0.05,2.5)*5.656854249; //Plot formule before changing !
- blend = bound(0,skill+self.bot_aimskill,10)*0.1;
- desiredang = desiredang + blend *
- (
- self.bot_1st_order_aimfilter * autocvar_bot_ai_aimskill_order_mix_1st
- + self.bot_2nd_order_aimfilter * autocvar_bot_ai_aimskill_order_mix_2nd
- + self.bot_3th_order_aimfilter * autocvar_bot_ai_aimskill_order_mix_3th
- + self.bot_4th_order_aimfilter * autocvar_bot_ai_aimskill_order_mix_4th
- + self.bot_5th_order_aimfilter * autocvar_bot_ai_aimskill_order_mix_5th
- );
-
- // calculate turn angles
- diffang = desiredang - self.bot_mouseaim;
- // wrap yaw turn
- diffang.y = diffang.y - floor(diffang.y / 360) * 360;
- if (diffang.y >= 180)
- diffang.y = diffang.y - 360;
- //dprint(" diff:", vtos(diffang));
-
- if (time >= self.bot_aimthinktime)
- {
- self.bot_aimthinktime = max(self.bot_aimthinktime + 0.5 - 0.05*(skill+self.bot_thinkskill), time);
- self.bot_mouseaim = self.bot_mouseaim + diffang * (1-random()*0.1*bound(1,10-(skill+self.bot_thinkskill),10));
- }
-
- //self.v_angle = self.v_angle + diffang * bound(0, r * frametime * (skill * 0.5 + 2), 1);
-
- diffang = self.bot_mouseaim - desiredang;
- // wrap yaw turn
- diffang.y = diffang.y - floor(diffang.y / 360) * 360;
- if (diffang.y >= 180)
- diffang.y = diffang.y - 360;
- desiredang = desiredang + diffang * bound(0,autocvar_bot_ai_aimskill_think,1);
-
- // calculate turn angles
- diffang = desiredang - self.v_angle;
- // wrap yaw turn
- diffang.y = diffang.y - floor(diffang.y / 360) * 360;
- if (diffang.y >= 180)
- diffang.y = diffang.y - 360;
- //dprint(" diff:", vtos(diffang));
-
- // jitter tracking
- dist = vlen(diffang);
- //diffang = diffang + randomvec() * (dist * 0.05 * (3.5 - bound(0, skill, 3)));
-
- // turn
- float r, fixedrate, blendrate;
- fixedrate = autocvar_bot_ai_aimskill_fixedrate / bound(1,dist,1000);
- blendrate = autocvar_bot_ai_aimskill_blendrate;
- r = max(fixedrate, blendrate);
- //self.v_angle = self.v_angle + diffang * bound(frametime, r * frametime * (2+skill*skill*0.05-random()*0.05*(10-skill)), 1);
- self.v_angle = self.v_angle + diffang * bound(delta_t, r * delta_t * (2+pow(skill+self.bot_mouseskill,3)*0.005-random()), 1);
- self.v_angle = self.v_angle * bound(0,autocvar_bot_ai_aimskill_mouse,1) + desiredang * bound(0,(1-autocvar_bot_ai_aimskill_mouse),1);
- //self.v_angle = self.v_angle + diffang * bound(0, r * frametime * (skill * 0.5 + 2), 1);
- //self.v_angle = self.v_angle + diffang * (1/ blendrate);
- self.v_angle_z = 0;
- self.v_angle_y = self.v_angle.y - floor(self.v_angle.y / 360) * 360;
- //dprint(" turn:", vtos(self.v_angle));
-
- makevectors(self.v_angle);
- shotorg = self.origin + self.view_ofs;
- shotdir = v_forward;
-
- //dprint(" dir:", vtos(v_forward));
- //te_lightning2(world, shotorg, shotorg + shotdir * 100);
-
- // calculate turn angles again
- //diffang = desiredang - self.v_angle;
- //diffang_y = diffang_y - floor(diffang_y / 360) * 360;
- //if (diffang_y >= 180)
- // diffang_y = diffang_y - 360;
-
- //dprint("e ", vtos(diffang), " < ", ftos(maxfiredeviation), "\n");
-
- // decide whether to fire this time
- // note the maxfiredeviation is in degrees so this has to convert to radians first
- //if ((normalize(v) * shotdir) >= cos(maxfiredeviation * (3.14159265358979323846 / 180)))
- if ((normalize(v) * shotdir) >= cos(maxfiredeviation * (3.14159265358979323846 / 180)))
- if (vlen(trace_endpos-shotorg) < 500+500*bound(0, skill+self.bot_aggresskill, 10) || random()*random()>bound(0,(skill+self.bot_aggresskill)*0.05,1))
- self.bot_firetimer = time + bound(0.1, 0.5-(skill+self.bot_aggresskill)*0.05, 0.5);
- //traceline(shotorg,shotorg+shotdir*1000,false,world);
- //dprint(ftos(maxfiredeviation),"\n");
- //dprint(" diff:", vtos(diffang), "\n");
-
- return self.bot_canfire && (time < self.bot_firetimer);
-}
-
-vector bot_shotlead(vector targorigin, vector targvelocity, float shotspeed, float shotdelay)
-{
- // Try to add code here that predicts gravity effect here, no clue HOW to though ... well not yet atleast...
- return targorigin + targvelocity * (shotdelay + vlen(targorigin - shotorg) / shotspeed);
-}
-
-float bot_aim(float shotspeed, float shotspeedupward, float maxshottime, float applygravity)
-{SELFPARAM();
- float f, r, hf, distanceratio;
- vector v;
- /*
- eprint(self);
- dprint("bot_aim(", ftos(shotspeed));
- dprint(", ", ftos(shotspeedupward));
- dprint(", ", ftos(maxshottime));
- dprint(", ", ftos(applygravity));
- dprint(");\n");
- */
-
- hf = self.dphitcontentsmask;
- self.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_CORPSE;
-
- shotspeed *= W_WeaponSpeedFactor();
- shotspeedupward *= W_WeaponSpeedFactor();
- if (!shotspeed)
- {
- LOG_TRACE("bot_aim: WARNING: weapon ", WEP_NAME(self.weapon), " shotspeed is zero!\n");
- shotspeed = 1000000;
- }
- if (!maxshottime)
- {
- LOG_TRACE("bot_aim: WARNING: weapon ", WEP_NAME(self.weapon), " maxshottime is zero!\n");
- maxshottime = 1;
- }
- makevectors(self.v_angle);
- shotorg = self.origin + self.view_ofs;
- shotdir = v_forward;
- v = bot_shotlead(self.bot_aimtargorigin, self.bot_aimtargvelocity, shotspeed, self.bot_aimlatency);
- distanceratio = sqrt(bound(0,skill,10000))*0.3*(vlen(v-shotorg)-100)/autocvar_bot_ai_aimskill_firetolerance_distdegrees;
- distanceratio = bound(0,distanceratio,1);
- r = (autocvar_bot_ai_aimskill_firetolerance_maxdegrees-autocvar_bot_ai_aimskill_firetolerance_mindegrees)
- * (1-distanceratio) + autocvar_bot_ai_aimskill_firetolerance_mindegrees;
- if (applygravity && self.bot_aimtarg)
- {
- if (!findtrajectorywithleading(shotorg, '0 0 0', '0 0 0', self.bot_aimtarg, shotspeed, shotspeedupward, maxshottime, 0, self))
- {
- self.dphitcontentsmask = hf;
- return false;
- }
-
- f = bot_aimdir(findtrajectory_velocity - shotspeedupward * '0 0 1', r);
- }
- else
- {
- f = bot_aimdir(v - shotorg, r);
- //dprint("AIM: ");dprint(vtos(self.bot_aimtargorigin));dprint(" + ");dprint(vtos(self.bot_aimtargvelocity));dprint(" * ");dprint(ftos(self.bot_aimlatency + vlen(self.bot_aimtargorigin - shotorg) / shotspeed));dprint(" = ");dprint(vtos(v));dprint(" : aimdir = ");dprint(vtos(normalize(v - shotorg)));dprint(" : ");dprint(vtos(shotdir));dprint("\n");
- //traceline(shotorg, shotorg + shotdir * 10000, false, self);
- //if (trace_ent.takedamage)
- //if (trace_fraction < 1)
- //if (!bot_shouldattack(trace_ent))
- // return false;
- traceline(shotorg, self.bot_aimtargorigin, false, self);
- if (trace_fraction < 1)
- if (trace_ent != self.enemy)
- if (!bot_shouldattack(trace_ent))
- {
- self.dphitcontentsmask = hf;
- return false;
- }
- }
-
- //if (r > maxshottime * shotspeed)
- // return false;
- self.dphitcontentsmask = hf;
- return true;
-}
+++ /dev/null
-#ifndef AIM_H
-#define AIM_H
-/*
- * Globals and Fields
- */
-
-entity tracetossent;
-entity tracetossfaketarget;
-vector findtrajectory_velocity;
-
-
-
-vector shotorg;
-vector shotdir;
-
-// lag simulation
-// upto 5 queued messages
-.float lag1_time;
-.float lag1_float1;
-.float lag1_float2;
-.entity lag1_entity1;
-.vector lag1_vec1;
-.vector lag1_vec2;
-.vector lag1_vec3;
-.vector lag1_vec4;
-
-.float lag2_time;
-.float lag2_float1;
-.float lag2_float2;
-.entity lag2_entity1;
-.vector lag2_vec1;
-.vector lag2_vec2;
-.vector lag2_vec3;
-.vector lag2_vec4;
-
-.float lag3_time;
-.float lag3_float1;
-.float lag3_float2;
-.entity lag3_entity1;
-.vector lag3_vec1;
-.vector lag3_vec2;
-.vector lag3_vec3;
-.vector lag3_vec4;
-
-.float lag4_time;
-.float lag4_float1;
-.float lag4_float2;
-.entity lag4_entity1;
-.vector lag4_vec1;
-.vector lag4_vec2;
-.vector lag4_vec3;
-.vector lag4_vec4;
-
-.float lag5_time;
-.float lag5_float1;
-.float lag5_float2;
-.entity lag5_entity1;
-.vector lag5_vec1;
-.vector lag5_vec2;
-.vector lag5_vec3;
-.vector lag5_vec4;
-
-.float bot_badaimtime;
-.float bot_aimthinktime;
-.float bot_prevaimtime;
-.float bot_firetimer;
-.float bot_aimlatency;
-
-.vector bot_mouseaim;
-.vector bot_badaimoffset;
-.vector bot_1st_order_aimfilter;
-.vector bot_2nd_order_aimfilter;
-.vector bot_3th_order_aimfilter;
-.vector bot_4th_order_aimfilter;
-.vector bot_5th_order_aimfilter;
-.vector bot_olddesiredang;
-
-.vector bot_aimselforigin;
-.vector bot_aimselfvelocity;
-.vector bot_aimtargorigin;
-.vector bot_aimtargvelocity;
-
-.entity bot_aimtarg;
-
-/*
- * Functions
- */
-
-float lag_additem(float t, float f1, float f2, entity e1, vector v1, vector v2, vector v3, vector v4);
-void lag_update();
-void bot_lagfunc(float t, float f1, float f2, entity e1, vector v1, vector v2, vector v3, vector v4);
-
-float bot_shouldattack(entity e);
-float bot_aimdir(vector v, float maxfiredeviation);
-float bot_aim(float shotspeed, float shotspeedupward, float maxshottime, float applygravity);
-float findtrajectorywithleading(vector org, vector m1, vector m2, entity targ, float shotspeed, float shotspeedupward, float maxtime, float shotdelay, entity ignore);
-
-vector bot_shotlead(vector targorigin, vector targvelocity, float shotspeed, float shotdelay);
-
-.void(float t, float f1, float f2, entity e1, vector v1, vector v2, vector v3, vector v4) lag_func;
-#endif
--- /dev/null
+#ifndef BOT_API_H
+#define BOT_API_H
+
+#include "../defs.qh"
+#include "../autocvars.qh"
+#include "../../common/weapons/all.qh"
+#include "../../common/teams.qh"
+#include "../teamplay.qh"
+#include "../miscfunctions.qh"
+#include "../campaign.qh"
+#include "../cl_client.qh"
+#include "../mutators/mutators_include.qh"
+#include "../constants.qh"
+#include "../weapons/weaponsystem.qh"
+
+const int WAYPOINTFLAG_GENERATED = 0x800000;
+
+int autocvar_bot_vs_human;
+int autocvar_bot_number;
+bool autocvar_bot_sound_monopoly;
+float autocvar_bot_ai_strategyinterval;
+
+float bot_custom_weapon;
+float bot_weapons_close[Weapons_MAX];
+float bot_weapons_far[Weapons_MAX];
+float bot_weapons_mid[Weapons_MAX];
+float currentbots;
+float skill;
+
+.float bot_attack;
+.float bot_dodgerating;
+.float bot_dodge;
+.float bot_forced_team;
+.float bot_moveskill; // moving technique
+.float bot_pickup;
+.float(entity player, entity item) bot_pickupevalfunc;
+.float bot_strategytime;
+.string cleanname;
+.float havocbot_role_timeout;
+.float isbot; // true if this client is actually a bot
+.float lastteleporttime;
+.float navigation_hasgoals;
+.float nearestwaypointtimeout;
+.entity nearestwaypoint;
+.float speed;
+.entity wp00, wp01, wp02, wp03, wp04, wp05, wp06, wp07, wp08, wp09, wp10, wp11, wp12, wp13, wp14, wp15;
+.entity wp16, wp17, wp18, wp19, wp20, wp21, wp22, wp23, wp24, wp25, wp26, wp27, wp28, wp29, wp30, wp31;
+.float wp00mincost, wp01mincost, wp02mincost, wp03mincost, wp04mincost, wp05mincost, wp06mincost, wp07mincost;
+.float wp08mincost, wp09mincost, wp10mincost, wp11mincost, wp12mincost, wp13mincost, wp14mincost, wp15mincost;
+.float wp16mincost, wp17mincost, wp18mincost, wp19mincost, wp20mincost, wp21mincost, wp22mincost, wp23mincost;
+.float wp24mincost, wp25mincost, wp26mincost, wp27mincost, wp28mincost, wp29mincost, wp30mincost, wp31mincost;
+.float wpconsidered;
+.float wpcost;
+.int wpflags;
+
+bool bot_aim(float shotspeed, float shotspeedupward, float maxshottime, float applygravity);
+void bot_clientconnect();
+void bot_clientdisconnect();
+void bot_cmdhelp(string scmd);
+void bot_endgame();
+bool bot_fixcount();
+void bot_list_commands();
+void bot_queuecommand(entity bot, string cmdstring);
+void bot_relinkplayerlist();
+void bot_resetqueues();
+void bot_serverframe();
+bool bot_shouldattack(entity e);
+void bot_think();
+
+entity find_bot_by_name(string name);
+entity find_bot_by_number(float number);
+
+void havocbot_goalrating_controlpoints(float ratingscale, vector org, float sradius);
+void havocbot_goalrating_enemyplayers(float ratingscale, vector org, float sradius);
+void havocbot_goalrating_items(float ratingscale, vector org, float sradius);
+
+entity navigation_findnearestwaypoint(entity ent, float walkfromwp);
+void navigation_goalrating_end();
+void navigation_goalrating_start();
+void navigation_markroutes(entity fixed_source_waypoint);
+void navigation_markroutes_inverted(entity fixed_source_waypoint);
+void navigation_routerating(entity e, float f, float rangebias);
+
+bool tracewalk(entity e, vector start, vector m1, vector m2, vector end, float movemode);
+
+void waypoint_remove(entity e);
+void waypoint_saveall();
+void waypoint_schedulerelinkall();
+void waypoint_schedulerelink(entity wp);
+void waypoint_spawnforitem(entity e);
+void waypoint_spawnforitem_force(entity e, vector org);
+void waypoint_spawnforteleporter(entity e, vector destination, float timetaken);
+void waypoint_spawnforteleporter_v(entity e, vector org, vector destination, float timetaken);
+entity waypoint_spawn(vector m1, vector m2, float f);
+
+#endif
+++ /dev/null
-#include "bot.qh"
-#include "../_all.qh"
-
-#include "aim.qh"
-#include "navigation.qh"
-#include "scripting.qh"
-#include "waypoints.qh"
-
-#include "havocbot/havocbot.qh"
-#include "havocbot/scripting.qh"
-
-#include "../antilag.qh"
-#include "../autocvars.qh"
-#include "../campaign.qh"
-#include "../cl_client.qh"
-#include "../constants.qh"
-#include "../defs.qh"
-#include "../race.qh"
-#include "../t_items.qh"
-
-#include "../mutators/mutators_include.qh"
-
-#include "../weapons/accuracy.qh"
-
-#include "../../common/constants.qh"
-#include "../../common/mapinfo.qh"
-#include "../../common/teams.qh"
-#include "../../common/util.qh"
-
-#include "../../common/weapons/all.qh"
-
-#include "../../csqcmodellib/sv_model.qh"
-
-#include "../../dpdefs/dpextensions.qh"
-#include "../../dpdefs/progsdefs.qh"
-
-#include "../../warpzonelib/common.qh"
-#include "../../warpzonelib/util_server.qh"
-
-entity bot_spawn()
-{SELFPARAM();
- entity bot = spawnclient();
- if (bot)
- {
- currentbots = currentbots + 1;
- setself(bot);
- bot_setnameandstuff();
- ClientConnect();
- PutClientInServer();
- setself(this);
- }
- return bot;
-}
-
-void bot_think()
-{SELFPARAM();
- if (self.bot_nextthink > time)
- return;
-
- self.flags &= ~FL_GODMODE;
- if(autocvar_bot_god)
- self.flags |= FL_GODMODE;
-
- self.bot_nextthink = self.bot_nextthink + autocvar_bot_ai_thinkinterval * pow(0.5, self.bot_aiskill);
- //if (self.bot_painintensity > 0)
- // self.bot_painintensity = self.bot_painintensity - (skill + 1) * 40 * frametime;
-
- //self.bot_painintensity = self.bot_painintensity + self.bot_oldhealth - self.health;
- //self.bot_painintensity = bound(0, self.bot_painintensity, 100);
-
- if (!IS_PLAYER(self) || (autocvar_g_campaign && !campaign_bots_may_start))
- {
- self.bot_nextthink = time + 0.5;
- return;
- }
-
- if (self.fixangle)
- {
- self.v_angle = self.angles;
- self.v_angle_z = 0;
- self.fixangle = false;
- }
-
- self.dmg_take = 0;
- self.dmg_save = 0;
- self.dmg_inflictor = world;
-
- // calculate an aiming latency based on the skill setting
- // (simulated network latency + naturally delayed reflexes)
- //self.ping = 0.7 - bound(0, 0.05 * skill, 0.5); // moved the reflexes to bot_aimdir (under the name 'think')
- // minimum ping 20+10 random
- self.ping = bound(0,0.07 - bound(0, (skill + self.bot_pingskill) * 0.005,0.05)+random()*0.01,0.65); // Now holds real lag to server, and higer skill players take a less laggy server
- // skill 10 = ping 0.2 (adrenaline)
- // skill 0 = ping 0.7 (slightly drunk)
-
- // clear buttons
- self.BUTTON_ATCK = 0;
- self.button1 = 0;
- self.BUTTON_JUMP = 0;
- self.BUTTON_ATCK2 = 0;
- self.BUTTON_ZOOM = 0;
- self.BUTTON_CROUCH = 0;
- self.BUTTON_HOOK = 0;
- self.BUTTON_INFO = 0;
- self.button8 = 0;
- self.BUTTON_CHAT = 0;
- self.BUTTON_USE = 0;
-
- if (time < game_starttime)
- {
- // block the bot during the countdown to game start
- self.movement = '0 0 0';
- self.bot_nextthink = game_starttime;
- return;
- }
-
- // if dead, just wait until we can respawn
- if (self.deadflag)
- {
- if (self.deadflag == DEAD_DEAD)
- {
- self.BUTTON_JUMP = 1; // press jump to respawn
- self.bot_strategytime = 0;
- }
- }
- else if(self.aistatus & AI_STATUS_STUCK)
- navigation_unstuck();
-
- // now call the current bot AI (havocbot for example)
- self.bot_ai();
-}
-
-void bot_setnameandstuff()
-{SELFPARAM();
- string readfile, s;
- float file, tokens, prio;
- entity p;
-
- string bot_name, bot_model, bot_skin, bot_shirt, bot_pants;
- string name, prefix, suffix;
-
- if(autocvar_g_campaign)
- {
- prefix = "";
- suffix = "";
- }
- else
- {
- prefix = autocvar_bot_prefix;
- suffix = autocvar_bot_suffix;
- }
-
- file = fopen(autocvar_bot_config_file, FILE_READ);
-
- if(file < 0)
- {
- LOG_INFO(strcat("Error: Can not open the bot configuration file '",autocvar_bot_config_file,"'\n"));
- readfile = "";
- }
- else
- {
- RandomSelection_Init();
- while((readfile = fgets(file)))
- {
- if(substring(readfile, 0, 2) == "//")
- continue;
- if(substring(readfile, 0, 1) == "#")
- continue;
- tokens = tokenizebyseparator(readfile, "\t");
- if(tokens == 0)
- continue;
- s = argv(0);
- prio = 1;
- FOR_EACH_CLIENT(p)
- {
- if(IS_BOT_CLIENT(p))
- if(s == p.cleanname)
- {
- prio = 0;
- break;
- }
- }
- RandomSelection_Add(world, 0, readfile, 1, prio);
- }
- readfile = RandomSelection_chosen_string;
- fclose(file);
- }
-
- tokens = tokenizebyseparator(readfile, "\t");
- if(argv(0) != "") bot_name = argv(0);
- else bot_name = "Bot";
-
- if(argv(1) != "") bot_model = argv(1);
- else bot_model = "";
-
- if(argv(2) != "") bot_skin = argv(2);
- else bot_skin = "0";
-
- if(argv(3) != "" && stof(argv(3)) >= 0) bot_shirt = argv(3);
- else bot_shirt = ftos(floor(random() * 15));
-
- if(argv(4) != "" && stof(argv(4)) >= 0) bot_pants = argv(4);
- else bot_pants = ftos(floor(random() * 15));
-
- self.bot_forced_team = stof(argv(5));
-
- prio = 6;
-
- #define READSKILL(f,w,r) if(argv(prio) != "") self.f = stof(argv(prio)) * (w); else self.f = (!autocvar_g_campaign) * (2 * random() - 1) * (r) * (w); ++prio
- //print(bot_name, ": ping=", argv(9), "\n");
-
- READSKILL(havocbot_keyboardskill, 0.5, 0.5); // keyboard skill
- READSKILL(bot_moveskill, 2, 0); // move skill
- READSKILL(bot_dodgeskill, 2, 0); // dodge skill
-
- READSKILL(bot_pingskill, 0.5, 0); // ping skill
-
- READSKILL(bot_weaponskill, 2, 0); // weapon skill
- READSKILL(bot_aggresskill, 1, 0); // aggre skill
- READSKILL(bot_rangepreference, 1, 0); // read skill
-
- READSKILL(bot_aimskill, 2, 0); // aim skill
- READSKILL(bot_offsetskill, 2, 0.5); // offset skill
- READSKILL(bot_mouseskill, 1, 0.5); // mouse skill
-
- READSKILL(bot_thinkskill, 1, 0.5); // think skill
- READSKILL(bot_aiskill, 2, 0); // "ai" skill
-
- self.bot_config_loaded = true;
-
- // this is really only a default, JoinBestTeam is called later
- setcolor(self, stof(bot_shirt) * 16 + stof(bot_pants));
- self.bot_preferredcolors = self.clientcolors;
-
- // pick the name
- if (autocvar_bot_usemodelnames)
- name = bot_model;
- else
- name = bot_name;
-
- // number bots with identical names
- float i;
- i = 0;
- FOR_EACH_CLIENT(p)
- {
- if(IS_BOT_CLIENT(p))
- if(p.cleanname == name)
- ++i;
- }
- if (i)
- self.netname = self.netname_freeme = strzone(strcat(prefix, name, "(", ftos(i), ")", suffix));
- else
- self.netname = self.netname_freeme = strzone(strcat(prefix, name, suffix));
-
- self.cleanname = strzone(name);
-
- // pick the model and skin
- if(substring(bot_model, -4, 1) != ".")
- bot_model = strcat(bot_model, ".iqm");
- self.playermodel = self.playermodel_freeme = strzone(strcat("models/player/", bot_model));
- self.playerskin = self.playerskin_freeme = strzone(bot_skin);
-
- self.cvar_cl_accuracy_data_share = 1; // share the bots weapon accuracy data with the world
- self.cvar_cl_accuracy_data_receive = 0; // don't receive any weapon accuracy data
-}
-
-void bot_custom_weapon_priority_setup()
-{
- float tokens, i, w;
-
- bot_custom_weapon = false;
-
- if( autocvar_bot_ai_custom_weapon_priority_far == "" ||
- autocvar_bot_ai_custom_weapon_priority_mid == "" ||
- autocvar_bot_ai_custom_weapon_priority_close == "" ||
- autocvar_bot_ai_custom_weapon_priority_distances == ""
- )
- return;
-
- // Parse distances
- tokens = tokenizebyseparator(autocvar_bot_ai_custom_weapon_priority_distances," ");
-
- if (tokens!=2)
- return;
-
- bot_distance_far = stof(argv(0));
- bot_distance_close = stof(argv(1));
-
- if(bot_distance_far < bot_distance_close){
- bot_distance_far = stof(argv(1));
- bot_distance_close = stof(argv(0));
- }
-
- // Initialize list of weapons
- bot_weapons_far[0] = -1;
- bot_weapons_mid[0] = -1;
- bot_weapons_close[0] = -1;
-
- // Parse far distance weapon priorities
- tokens = tokenizebyseparator(W_NumberWeaponOrder(autocvar_bot_ai_custom_weapon_priority_far)," ");
-
- int c = 0;
- for(i=0; i < tokens && c < Weapons_COUNT; ++i){
- w = stof(argv(i));
- if ( w >= WEP_FIRST && w <= WEP_LAST) {
- bot_weapons_far[c] = w;
- ++c;
- }
- }
- if(c < Weapons_COUNT)
- bot_weapons_far[c] = -1;
-
- // Parse mid distance weapon priorities
- tokens = tokenizebyseparator(W_NumberWeaponOrder(autocvar_bot_ai_custom_weapon_priority_mid)," ");
-
- c = 0;
- for(i=0; i < tokens && c < Weapons_COUNT; ++i){
- w = stof(argv(i));
- if ( w >= WEP_FIRST && w <= WEP_LAST) {
- bot_weapons_mid[c] = w;
- ++c;
- }
- }
- if(c < Weapons_COUNT)
- bot_weapons_mid[c] = -1;
-
- // Parse close distance weapon priorities
- tokens = tokenizebyseparator(W_NumberWeaponOrder(autocvar_bot_ai_custom_weapon_priority_close)," ");
-
- c = 0;
- for(i=0; i < tokens && i < Weapons_COUNT; ++i){
- w = stof(argv(i));
- if ( w >= WEP_FIRST && w <= WEP_LAST) {
- bot_weapons_close[c] = w;
- ++c;
- }
- }
- if(c < Weapons_COUNT)
- bot_weapons_close[c] = -1;
-
- bot_custom_weapon = true;
-}
-
-void bot_endgame()
-{
- entity e;
- //dprint("bot_endgame\n");
- e = bot_list;
- while (e)
- {
- setcolor(e, e.bot_preferredcolors);
- e = e.nextbot;
- }
- // if dynamic waypoints are ever implemented, save them here
-}
-
-void bot_relinkplayerlist()
-{
- entity e;
- entity prevbot;
- player_count = 0;
- currentbots = 0;
- player_list = e = findchainflags(flags, FL_CLIENT);
- bot_list = world;
- prevbot = world;
- while (e)
- {
- player_count = player_count + 1;
- e.nextplayer = e.chain;
- if (IS_BOT_CLIENT(e))
- {
- if (prevbot)
- prevbot.nextbot = e;
- else
- {
- bot_list = e;
- bot_list.nextbot = world;
- }
- prevbot = e;
- currentbots = currentbots + 1;
- }
- e = e.chain;
- }
- LOG_TRACE(strcat("relink: ", ftos(currentbots), " bots seen.\n"));
- bot_strategytoken = bot_list;
- bot_strategytoken_taken = true;
-}
-
-void bot_clientdisconnect()
-{SELFPARAM();
- if (!IS_BOT_CLIENT(self))
- return;
- bot_clearqueue(self);
- if(self.cleanname)
- strunzone(self.cleanname);
- if(self.netname_freeme)
- strunzone(self.netname_freeme);
- if(self.playermodel_freeme)
- strunzone(self.playermodel_freeme);
- if(self.playerskin_freeme)
- strunzone(self.playerskin_freeme);
- self.cleanname = string_null;
- self.netname_freeme = string_null;
- self.playermodel_freeme = string_null;
- self.playerskin_freeme = string_null;
- if(self.bot_cmd_current)
- remove(self.bot_cmd_current);
- if(bot_waypoint_queue_owner==self)
- bot_waypoint_queue_owner = world;
-}
-
-void bot_clientconnect()
-{SELFPARAM();
- if (!IS_BOT_CLIENT(self))
- return;
- self.bot_preferredcolors = self.clientcolors;
- self.bot_nextthink = time - random();
- self.lag_func = bot_lagfunc;
- self.isbot = true;
- self.createdtime = self.bot_nextthink;
-
- if(!self.bot_config_loaded) // This is needed so team overrider doesn't break between matches
- bot_setnameandstuff();
-
- if(self.bot_forced_team==1)
- self.team = NUM_TEAM_1;
- else if(self.bot_forced_team==2)
- self.team = NUM_TEAM_2;
- else if(self.bot_forced_team==3)
- self.team = NUM_TEAM_3;
- else if(self.bot_forced_team==4)
- self.team = NUM_TEAM_4;
- else
- JoinBestTeam(self, false, true);
-
- havocbot_setupbot();
-}
-
-void bot_removefromlargestteam()
-{
- float besttime, bestcount, thiscount;
- entity best, head;
- CheckAllowedTeams(world);
- GetTeamCounts(world);
- head = findchainfloat(isbot, true);
- if (!head)
- return;
- best = head;
- besttime = head.createdtime;
- bestcount = 0;
- while (head)
- {
- if(head.team == NUM_TEAM_1)
- thiscount = c1;
- else if(head.team == NUM_TEAM_2)
- thiscount = c2;
- else if(head.team == NUM_TEAM_3)
- thiscount = c3;
- else if(head.team == NUM_TEAM_4)
- thiscount = c4;
- else
- thiscount = 0;
- if (thiscount > bestcount)
- {
- bestcount = thiscount;
- besttime = head.createdtime;
- best = head;
- }
- else if (thiscount == bestcount && besttime < head.createdtime)
- {
- besttime = head.createdtime;
- best = head;
- }
- head = head.chain;
- }
- currentbots = currentbots - 1;
- dropclient(best);
-}
-
-void bot_removenewest()
-{
- float besttime;
- entity best, head;
-
- if(teamplay)
- {
- bot_removefromlargestteam();
- return;
- }
-
- head = findchainfloat(isbot, true);
- if (!head)
- return;
- best = head;
- besttime = head.createdtime;
- while (head)
- {
- if (besttime < head.createdtime)
- {
- besttime = head.createdtime;
- best = head;
- }
- head = head.chain;
- }
- currentbots = currentbots - 1;
- dropclient(best);
-}
-
-void autoskill(float factor)
-{
- float bestbot;
- float bestplayer;
- entity head;
-
- bestbot = -1;
- bestplayer = -1;
- FOR_EACH_PLAYER(head)
- {
- if(IS_REAL_CLIENT(head))
- bestplayer = max(bestplayer, head.totalfrags - head.totalfrags_lastcheck);
- else
- bestbot = max(bestbot, head.totalfrags - head.totalfrags_lastcheck);
- }
-
- LOG_TRACE("autoskill: best player got ", ftos(bestplayer), ", ");
- LOG_TRACE("best bot got ", ftos(bestbot), "; ");
- if(bestbot < 0 || bestplayer < 0)
- {
- LOG_TRACE("not doing anything\n");
- // don't return, let it reset all counters below
- }
- else if(bestbot <= bestplayer * factor - 2)
- {
- if(autocvar_skill < 17)
- {
- LOG_TRACE("2 frags difference, increasing skill\n");
- cvar_set("skill", ftos(autocvar_skill + 1));
- bprint("^2SKILL UP!^7 Now at level ", ftos(autocvar_skill), "\n");
- }
- }
- else if(bestbot >= bestplayer * factor + 2)
- {
- if(autocvar_skill > 0)
- {
- LOG_TRACE("2 frags difference, decreasing skill\n");
- cvar_set("skill", ftos(autocvar_skill - 1));
- bprint("^1SKILL DOWN!^7 Now at level ", ftos(autocvar_skill), "\n");
- }
- }
- else
- {
- LOG_TRACE("not doing anything\n");
- return;
- // don't reset counters, wait for them to accumulate
- }
-
- FOR_EACH_PLAYER(head)
- head.totalfrags_lastcheck = head.totalfrags;
-}
-
-void bot_calculate_stepheightvec(void)
-{
- stepheightvec = autocvar_sv_stepheight * '0 0 1';
- jumpstepheightvec = stepheightvec +
- ((autocvar_sv_jumpvelocity * autocvar_sv_jumpvelocity) / (2 * autocvar_sv_gravity)) * '0 0 0.85';
- // 0.75 factor is for safety to make the jumps easy
-}
-
-float bot_fixcount()
-{
- entity head;
- float realplayers, bots, activerealplayers;
-
- activerealplayers = 0;
- realplayers = 0;
-
- FOR_EACH_REALCLIENT(head)
- {
- if(IS_PLAYER(head) || g_lms || head.caplayer == 1)
- ++activerealplayers;
- ++realplayers;
- }
-
- // add/remove bots if needed to make sure there are at least
- // minplayers+bot_number, or remove all bots if no one is playing
- // But don't remove bots immediately on level change, as the real players
- // usually haven't rejoined yet
- bots_would_leave = false;
- if (teamplay && autocvar_bot_vs_human && (c3==-1 && c4==-1))
- bots = min(ceil(fabs(autocvar_bot_vs_human) * activerealplayers), maxclients - realplayers);
- else if ((realplayers || autocvar_bot_join_empty || (currentbots > 0 && time < 5)))
- {
- float realminplayers, minplayers;
- realminplayers = autocvar_minplayers;
- minplayers = max(0, floor(realminplayers));
-
- float realminbots, minbots;
- realminbots = autocvar_bot_number;
- minbots = max(0, floor(realminbots));
-
- bots = min(max(minbots, minplayers - activerealplayers), maxclients - realplayers);
- if(bots > minbots)
- bots_would_leave = true;
- }
- else
- {
- // if there are no players, remove bots
- bots = 0;
- }
-
- // only add one bot per frame to avoid utter chaos
- if(time > botframe_nextthink)
- {
- //dprint(ftos(bots), " ? ", ftos(currentbots), "\n");
- while (currentbots < bots)
- {
- if (bot_spawn() == world)
- {
- bprint("Can not add bot, server full.\n");
- return false;
- }
- }
- while (currentbots > bots)
- bot_removenewest();
- }
-
- return true;
-}
-
-void bot_serverframe()
-{
- if (intermission_running)
- return;
-
- if (time < 2)
- return;
-
- bot_calculate_stepheightvec();
- bot_navigation_movemode = ((autocvar_bot_navigation_ignoreplayers) ? MOVE_NOMONSTERS : MOVE_NORMAL);
-
- if(time > autoskill_nextthink)
- {
- float a;
- a = autocvar_skill_auto;
- if(a)
- autoskill(a);
- autoskill_nextthink = time + 5;
- }
-
- if(time > botframe_nextthink)
- {
- if(!bot_fixcount())
- botframe_nextthink = time + 10;
- }
-
- bot_ignore_bots = autocvar_bot_ignore_bots;
-
- if(botframe_spawnedwaypoints)
- {
- if(autocvar_waypoint_benchmark)
- localcmd("quit\n");
- }
-
- if (currentbots > 0 || autocvar_g_waypointeditor || autocvar_g_waypointeditor_auto)
- if (botframe_spawnedwaypoints)
- {
- if(botframe_cachedwaypointlinks)
- {
- if(!botframe_loadedforcedlinks)
- waypoint_load_links_hardwired();
- }
- else
- {
- // TODO: Make this check cleaner
- entity wp = findchain(classname, "waypoint");
- if(time - wp.nextthink > 10)
- waypoint_save_links();
- }
- }
- else
- {
- botframe_spawnedwaypoints = true;
- waypoint_loadall();
- if(!waypoint_load_links())
- waypoint_schedulerelinkall();
- }
-
- if (bot_list)
- {
- // cycle the goal token from one bot to the next each frame
- // (this prevents them from all doing spawnfunc_waypoint searches on the same
- // frame, which causes choppy framerates)
- if (bot_strategytoken_taken)
- {
- bot_strategytoken_taken = false;
- if (bot_strategytoken)
- bot_strategytoken = bot_strategytoken.nextbot;
- if (!bot_strategytoken)
- bot_strategytoken = bot_list;
- }
-
- if (botframe_nextdangertime < time)
- {
- float interval;
- interval = autocvar_bot_ai_dangerdetectioninterval;
- if (botframe_nextdangertime < time - interval * 1.5)
- botframe_nextdangertime = time;
- botframe_nextdangertime = botframe_nextdangertime + interval;
- botframe_updatedangerousobjects(autocvar_bot_ai_dangerdetectionupdates);
- }
- }
-
- if (autocvar_g_waypointeditor)
- botframe_showwaypointlinks();
-
- if (autocvar_g_waypointeditor_auto)
- botframe_autowaypoints();
-
- if(time > bot_cvar_nextthink)
- {
- if(currentbots>0)
- bot_custom_weapon_priority_setup();
- bot_cvar_nextthink = time + 5;
- }
-}
+++ /dev/null
-#ifndef BOT_H
-#define BOT_H
-/*
- * Globals and Fields
- */
-
-const int AI_STATUS_ROAMING = 1; // Bot is just crawling the map. No enemies at sight
-const int AI_STATUS_ATTACKING = 2; // There are enemies at sight
-const int AI_STATUS_RUNNING = 4; // Bot is bunny hopping
-const int AI_STATUS_DANGER_AHEAD = 8; // There is lava/slime/trigger_hurt ahead
-const int AI_STATUS_OUT_JUMPPAD = 16; // Trying to get out of a "vertical" jump pad
-const int AI_STATUS_OUT_WATER = 32; // Trying to get out of water
-const int AI_STATUS_WAYPOINT_PERSONAL_LINKING = 64; // Waiting for the personal waypoint to be linked
-const int AI_STATUS_WAYPOINT_PERSONAL_GOING = 128; // Going to a personal waypoint
-const int AI_STATUS_WAYPOINT_PERSONAL_REACHED = 256; // Personal waypoint reached
-const int AI_STATUS_JETPACK_FLYING = 512;
-const int AI_STATUS_JETPACK_LANDING = 1024;
-const int AI_STATUS_STUCK = 2048; // Cannot reach any goal
-
-.float isbot; // true if this client is actually a bot
-.int aistatus;
-
-// Skill system
-float skill;
-float autoskill_nextthink;
-
-// havocbot_keyboardskill // keyboard movement
-.float bot_moveskill; // moving technique
-.float bot_dodgeskill; // dodging
-
-.float bot_pingskill; // ping offset
-
-.float bot_weaponskill; // weapon usage skill (combos, e.g.)
-.float bot_aggresskill; // aggressivity, controls "think before fire" behaviour
-.float bot_rangepreference; // weapon choice offset for range (>0 = prefer long range earlier "sniper", <0 = prefer short range "spammer")
-
-.float bot_aimskill; // aim accuracy
-.float bot_offsetskill; // aim breakage
-.float bot_mouseskill; // mouse "speed"
-
-.float bot_thinkskill; // target choice
-.float bot_aiskill; // strategy choice
-
-.float totalfrags_lastcheck;
-
-// Custom weapon priorities
-float bot_custom_weapon;
-float bot_distance_far;
-float bot_distance_close;
-
-float bot_weapons_far[Weapons_MAX];
-float bot_weapons_mid[Weapons_MAX];
-float bot_weapons_close[Weapons_MAX];
-
-entity bot_list;
-entity player_list;
-.entity nextbot;
-.entity nextplayer;
-.string cleanname;
-.string netname_freeme;
-.string playermodel_freeme;
-.string playerskin_freeme;
-
-.float bot_nextthink;
-
-.float createdtime;
-.float bot_preferredcolors;
-.float bot_attack;
-.float bot_dodge;
-.float bot_dodgerating;
-
-.float bot_pickup;
-.float bot_pickupbasevalue;
-.float bot_canfire;
-.float bot_strategytime;
-
-.float bot_forced_team;
-.float bot_config_loaded;
-
-float bot_strategytoken_taken;
-entity bot_strategytoken;
-
-float botframe_spawnedwaypoints;
-float botframe_nextthink;
-float botframe_nextdangertime;
-float bot_cvar_nextthink;
-float bot_ignore_bots; // let bots not attack other bots (only works in non-teamplay)
-
-/*
- * Functions
- */
-
-entity bot_spawn();
-float bot_fixcount();
-
-void bot_think();
-void bot_setnameandstuff();
-void bot_custom_weapon_priority_setup();
-void bot_endgame();
-void bot_relinkplayerlist();
-void bot_clientdisconnect();
-void bot_clientconnect();
-void bot_removefromlargestteam();
-void bot_removenewest();
-void autoskill(float factor);
-void bot_serverframe();
-
-.void() bot_ai;
-.float(entity player, entity item) bot_pickupevalfunc;
-
-/*
- * Imports
- */
-
-void() havocbot_setupbot;
-
-void bot_calculate_stepheightvec(void);
-#endif
--- /dev/null
+#include "aim.qh"
+
+// traces multiple trajectories to find one that will impact the target
+// 'end' vector is the place it aims for,
+// returns true only if it hit targ (don't target non-solid entities)
+
+float findtrajectorywithleading(vector org, vector m1, vector m2, entity targ, float shotspeed, float shotspeedupward, float maxtime, float shotdelay, entity ignore)
+{
+ float c, savesolid, shottime;
+ vector dir, end, v, o;
+ if (shotspeed < 1)
+ return false; // could cause division by zero if calculated
+ if (targ.solid < SOLID_BBOX) // SOLID_NOT and SOLID_TRIGGER
+ return false; // could never hit it
+ if (!tracetossent)
+ tracetossent = spawn();
+ tracetossent.owner = ignore;
+ setsize(tracetossent, m1, m2);
+ savesolid = targ.solid;
+ targ.solid = SOLID_NOT;
+ o = (targ.absmin + targ.absmax) * 0.5;
+ shottime = ((vlen(o - org) / shotspeed) + shotdelay);
+ v = targ.velocity * shottime + o;
+ tracebox(o, targ.mins, targ.maxs, v, false, targ);
+ v = trace_endpos;
+ end = v + (targ.mins + targ.maxs) * 0.5;
+ if ((vlen(end - org) / shotspeed + 0.2) > maxtime)
+ {
+ // out of range
+ targ.solid = savesolid;
+ return false;
+ }
+
+ if (!tracetossfaketarget)
+ tracetossfaketarget = spawn();
+ tracetossfaketarget.solid = savesolid;
+ tracetossfaketarget.movetype = targ.movetype;
+ _setmodel(tracetossfaketarget, targ.model); // no low precision
+ tracetossfaketarget.model = targ.model;
+ tracetossfaketarget.modelindex = targ.modelindex;
+ setsize(tracetossfaketarget, targ.mins, targ.maxs);
+ setorigin(tracetossfaketarget, v);
+
+ c = 0;
+ dir = normalize(end - org);
+ while (c < 10) // 10 traces
+ {
+ setorigin(tracetossent, org); // reset
+ tracetossent.velocity = findtrajectory_velocity = normalize(dir) * shotspeed + shotspeedupward * '0 0 1';
+ tracetoss(tracetossent, ignore); // love builtin functions...
+ if (trace_ent == tracetossfaketarget) // done
+ {
+ targ.solid = savesolid;
+
+ // make it disappear
+ tracetossfaketarget.solid = SOLID_NOT;
+ tracetossfaketarget.movetype = MOVETYPE_NONE;
+ tracetossfaketarget.model = "";
+ tracetossfaketarget.modelindex = 0;
+ // relink to remove it from physics considerations
+ setorigin(tracetossfaketarget, v);
+
+ return true;
+ }
+ dir.z = dir.z + 0.1; // aim up a little more
+ c = c + 1;
+ }
+ targ.solid = savesolid;
+
+ // make it disappear
+ tracetossfaketarget.solid = SOLID_NOT;
+ tracetossfaketarget.movetype = MOVETYPE_NONE;
+ tracetossfaketarget.model = "";
+ tracetossfaketarget.modelindex = 0;
+ // relink to remove it from physics considerations
+ setorigin(tracetossfaketarget, v);
+
+ // leave a valid one even if it won't reach
+ findtrajectory_velocity = normalize(end - org) * shotspeed + shotspeedupward * '0 0 1';
+ return false;
+}
+
+void lag_update()
+{SELFPARAM();
+ if (self.lag1_time) if (time > self.lag1_time) {self.lag_func(self.lag1_time, self.lag1_float1, self.lag1_float2, self.lag1_entity1, self.lag1_vec1, self.lag1_vec2, self.lag1_vec3, self.lag1_vec4);self.lag1_time = 0;}
+ if (self.lag2_time) if (time > self.lag2_time) {self.lag_func(self.lag2_time, self.lag2_float1, self.lag2_float2, self.lag2_entity1, self.lag2_vec1, self.lag2_vec2, self.lag2_vec3, self.lag2_vec4);self.lag2_time = 0;}
+ if (self.lag3_time) if (time > self.lag3_time) {self.lag_func(self.lag3_time, self.lag3_float1, self.lag3_float2, self.lag3_entity1, self.lag3_vec1, self.lag3_vec2, self.lag3_vec3, self.lag3_vec4);self.lag3_time = 0;}
+ if (self.lag4_time) if (time > self.lag4_time) {self.lag_func(self.lag4_time, self.lag4_float1, self.lag4_float2, self.lag4_entity1, self.lag4_vec1, self.lag4_vec2, self.lag4_vec3, self.lag4_vec4);self.lag4_time = 0;}
+ if (self.lag5_time) if (time > self.lag5_time) {self.lag_func(self.lag5_time, self.lag5_float1, self.lag5_float2, self.lag5_entity1, self.lag5_vec1, self.lag5_vec2, self.lag5_vec3, self.lag5_vec4);self.lag5_time = 0;}
+}
+
+float lag_additem(float t, float f1, float f2, entity e1, vector v1, vector v2, vector v3, vector v4)
+{SELFPARAM();
+ if (self.lag1_time == 0) {self.lag1_time = t;self.lag1_float1 = f1;self.lag1_float2 = f2;self.lag1_entity1 = e1;self.lag1_vec1 = v1;self.lag1_vec2 = v2;self.lag1_vec3 = v3;self.lag1_vec4 = v4;return true;}
+ if (self.lag2_time == 0) {self.lag2_time = t;self.lag2_float1 = f1;self.lag2_float2 = f2;self.lag2_entity1 = e1;self.lag2_vec1 = v1;self.lag2_vec2 = v2;self.lag2_vec3 = v3;self.lag2_vec4 = v4;return true;}
+ if (self.lag3_time == 0) {self.lag3_time = t;self.lag3_float1 = f1;self.lag3_float2 = f2;self.lag3_entity1 = e1;self.lag3_vec1 = v1;self.lag3_vec2 = v2;self.lag3_vec3 = v3;self.lag3_vec4 = v4;return true;}
+ if (self.lag4_time == 0) {self.lag4_time = t;self.lag4_float1 = f1;self.lag4_float2 = f2;self.lag4_entity1 = e1;self.lag4_vec1 = v1;self.lag4_vec2 = v2;self.lag4_vec3 = v3;self.lag4_vec4 = v4;return true;}
+ if (self.lag5_time == 0) {self.lag5_time = t;self.lag5_float1 = f1;self.lag5_float2 = f2;self.lag5_entity1 = e1;self.lag5_vec1 = v1;self.lag5_vec2 = v2;self.lag5_vec3 = v3;self.lag5_vec4 = v4;return true;}
+ // no room for it (what is the best thing to do here??)
+ return false;
+}
+
+float bot_shouldattack(entity e)
+{SELFPARAM();
+ if (e.team == self.team)
+ {
+ if (e == self)
+ return false;
+ if (teamplay)
+ if (e.team != 0)
+ return false;
+ }
+
+ if(e.frozen)
+ return false;
+
+ if(teamplay)
+ {
+ if(e.team==0)
+ return false;
+ }
+ else if(bot_ignore_bots)
+ if(IS_BOT_CLIENT(e))
+ return false;
+
+ if (!e.takedamage)
+ return false;
+ if (e.deadflag)
+ return false;
+ if (e.BUTTON_CHAT)
+ return false;
+ if(e.flags & FL_NOTARGET)
+ return false;
+
+ if(MUTATOR_CALLHOOK(BotShouldAttack, e))
+ return false;
+
+ return true;
+}
+
+void bot_lagfunc(float t, float f1, float f2, entity e1, vector v1, vector v2, vector v3, vector v4)
+{SELFPARAM();
+ if(self.flags & FL_INWATER)
+ {
+ self.bot_aimtarg = world;
+ return;
+ }
+ self.bot_aimtarg = e1;
+ self.bot_aimlatency = self.ping; // FIXME? Shouldn't this be in the lag item?
+ self.bot_aimselforigin = v1;
+ self.bot_aimselfvelocity = v2;
+ self.bot_aimtargorigin = v3;
+ self.bot_aimtargvelocity = v4;
+ if(skill <= 0)
+ self.bot_canfire = (random() < 0.8);
+ else if(skill <= 1)
+ self.bot_canfire = (random() < 0.9);
+ else if(skill <= 2)
+ self.bot_canfire = (random() < 0.95);
+ else
+ self.bot_canfire = 1;
+}
+
+float bot_aimdir(vector v, float maxfiredeviation)
+{SELFPARAM();
+ float dist, delta_t, blend;
+ vector desiredang, diffang;
+
+ //dprint("aim ", self.netname, ": old:", vtos(self.v_angle));
+ // make sure v_angle is sane first
+ self.v_angle_y = self.v_angle.y - floor(self.v_angle.y / 360) * 360;
+ self.v_angle_z = 0;
+
+ // get the desired angles to aim at
+ //dprint(" at:", vtos(v));
+ v = normalize(v);
+ //te_lightning2(world, self.origin + self.view_ofs, self.origin + self.view_ofs + v * 200);
+ if (time >= self.bot_badaimtime)
+ {
+ self.bot_badaimtime = max(self.bot_badaimtime + 0.3, time);
+ self.bot_badaimoffset = randomvec() * bound(0, 5 - 0.5 * (skill+self.bot_offsetskill), 5) * autocvar_bot_ai_aimskill_offset;
+ }
+ desiredang = vectoangles(v) + self.bot_badaimoffset;
+ //dprint(" desired:", vtos(desiredang));
+ if (desiredang.x >= 180)
+ desiredang.x = desiredang.x - 360;
+ desiredang.x = bound(-90, 0 - desiredang.x, 90);
+ desiredang.z = self.v_angle.z;
+ //dprint(" / ", vtos(desiredang));
+
+ //// pain throws off aim
+ //if (self.bot_painintensity)
+ //{
+ // // shake from pain
+ // desiredang = desiredang + randomvec() * self.bot_painintensity * 0.2;
+ //}
+
+ // calculate turn angles
+ diffang = (desiredang - self.bot_olddesiredang);
+ // wrap yaw turn
+ diffang.y = diffang.y - floor(diffang.y / 360) * 360;
+ if (diffang.y >= 180)
+ diffang.y = diffang.y - 360;
+ self.bot_olddesiredang = desiredang;
+ //dprint(" diff:", vtos(diffang));
+
+ delta_t = time-self.bot_prevaimtime;
+ self.bot_prevaimtime = time;
+ // Here we will try to anticipate the comming aiming direction
+ self.bot_1st_order_aimfilter= self.bot_1st_order_aimfilter
+ + (diffang * (1 / delta_t) - self.bot_1st_order_aimfilter) * bound(0, autocvar_bot_ai_aimskill_order_filter_1st,1);
+ self.bot_2nd_order_aimfilter= self.bot_2nd_order_aimfilter
+ + (self.bot_1st_order_aimfilter - self.bot_2nd_order_aimfilter) * bound(0, autocvar_bot_ai_aimskill_order_filter_2nd,1);
+ self.bot_3th_order_aimfilter= self.bot_3th_order_aimfilter
+ + (self.bot_2nd_order_aimfilter - self.bot_3th_order_aimfilter) * bound(0, autocvar_bot_ai_aimskill_order_filter_3th,1);
+ self.bot_4th_order_aimfilter= self.bot_4th_order_aimfilter
+ + (self.bot_3th_order_aimfilter - self.bot_4th_order_aimfilter) * bound(0, autocvar_bot_ai_aimskill_order_filter_4th,1);
+ self.bot_5th_order_aimfilter= self.bot_5th_order_aimfilter
+ + (self.bot_4th_order_aimfilter - self.bot_5th_order_aimfilter) * bound(0, autocvar_bot_ai_aimskill_order_filter_5th,1);
+
+ //blend = (bound(0,skill,10)*0.1)*pow(1-bound(0,skill,10)*0.05,2.5)*5.656854249; //Plot formule before changing !
+ blend = bound(0,skill+self.bot_aimskill,10)*0.1;
+ desiredang = desiredang + blend *
+ (
+ self.bot_1st_order_aimfilter * autocvar_bot_ai_aimskill_order_mix_1st
+ + self.bot_2nd_order_aimfilter * autocvar_bot_ai_aimskill_order_mix_2nd
+ + self.bot_3th_order_aimfilter * autocvar_bot_ai_aimskill_order_mix_3th
+ + self.bot_4th_order_aimfilter * autocvar_bot_ai_aimskill_order_mix_4th
+ + self.bot_5th_order_aimfilter * autocvar_bot_ai_aimskill_order_mix_5th
+ );
+
+ // calculate turn angles
+ diffang = desiredang - self.bot_mouseaim;
+ // wrap yaw turn
+ diffang.y = diffang.y - floor(diffang.y / 360) * 360;
+ if (diffang.y >= 180)
+ diffang.y = diffang.y - 360;
+ //dprint(" diff:", vtos(diffang));
+
+ if (time >= self.bot_aimthinktime)
+ {
+ self.bot_aimthinktime = max(self.bot_aimthinktime + 0.5 - 0.05*(skill+self.bot_thinkskill), time);
+ self.bot_mouseaim = self.bot_mouseaim + diffang * (1-random()*0.1*bound(1,10-(skill+self.bot_thinkskill),10));
+ }
+
+ //self.v_angle = self.v_angle + diffang * bound(0, r * frametime * (skill * 0.5 + 2), 1);
+
+ diffang = self.bot_mouseaim - desiredang;
+ // wrap yaw turn
+ diffang.y = diffang.y - floor(diffang.y / 360) * 360;
+ if (diffang.y >= 180)
+ diffang.y = diffang.y - 360;
+ desiredang = desiredang + diffang * bound(0,autocvar_bot_ai_aimskill_think,1);
+
+ // calculate turn angles
+ diffang = desiredang - self.v_angle;
+ // wrap yaw turn
+ diffang.y = diffang.y - floor(diffang.y / 360) * 360;
+ if (diffang.y >= 180)
+ diffang.y = diffang.y - 360;
+ //dprint(" diff:", vtos(diffang));
+
+ // jitter tracking
+ dist = vlen(diffang);
+ //diffang = diffang + randomvec() * (dist * 0.05 * (3.5 - bound(0, skill, 3)));
+
+ // turn
+ float r, fixedrate, blendrate;
+ fixedrate = autocvar_bot_ai_aimskill_fixedrate / bound(1,dist,1000);
+ blendrate = autocvar_bot_ai_aimskill_blendrate;
+ r = max(fixedrate, blendrate);
+ //self.v_angle = self.v_angle + diffang * bound(frametime, r * frametime * (2+skill*skill*0.05-random()*0.05*(10-skill)), 1);
+ self.v_angle = self.v_angle + diffang * bound(delta_t, r * delta_t * (2+pow(skill+self.bot_mouseskill,3)*0.005-random()), 1);
+ self.v_angle = self.v_angle * bound(0,autocvar_bot_ai_aimskill_mouse,1) + desiredang * bound(0,(1-autocvar_bot_ai_aimskill_mouse),1);
+ //self.v_angle = self.v_angle + diffang * bound(0, r * frametime * (skill * 0.5 + 2), 1);
+ //self.v_angle = self.v_angle + diffang * (1/ blendrate);
+ self.v_angle_z = 0;
+ self.v_angle_y = self.v_angle.y - floor(self.v_angle.y / 360) * 360;
+ //dprint(" turn:", vtos(self.v_angle));
+
+ makevectors(self.v_angle);
+ shotorg = self.origin + self.view_ofs;
+ shotdir = v_forward;
+
+ //dprint(" dir:", vtos(v_forward));
+ //te_lightning2(world, shotorg, shotorg + shotdir * 100);
+
+ // calculate turn angles again
+ //diffang = desiredang - self.v_angle;
+ //diffang_y = diffang_y - floor(diffang_y / 360) * 360;
+ //if (diffang_y >= 180)
+ // diffang_y = diffang_y - 360;
+
+ //dprint("e ", vtos(diffang), " < ", ftos(maxfiredeviation), "\n");
+
+ // decide whether to fire this time
+ // note the maxfiredeviation is in degrees so this has to convert to radians first
+ //if ((normalize(v) * shotdir) >= cos(maxfiredeviation * (3.14159265358979323846 / 180)))
+ if ((normalize(v) * shotdir) >= cos(maxfiredeviation * (3.14159265358979323846 / 180)))
+ if (vlen(trace_endpos-shotorg) < 500+500*bound(0, skill+self.bot_aggresskill, 10) || random()*random()>bound(0,(skill+self.bot_aggresskill)*0.05,1))
+ self.bot_firetimer = time + bound(0.1, 0.5-(skill+self.bot_aggresskill)*0.05, 0.5);
+ //traceline(shotorg,shotorg+shotdir*1000,false,world);
+ //dprint(ftos(maxfiredeviation),"\n");
+ //dprint(" diff:", vtos(diffang), "\n");
+
+ return self.bot_canfire && (time < self.bot_firetimer);
+}
+
+vector bot_shotlead(vector targorigin, vector targvelocity, float shotspeed, float shotdelay)
+{
+ // Try to add code here that predicts gravity effect here, no clue HOW to though ... well not yet atleast...
+ return targorigin + targvelocity * (shotdelay + vlen(targorigin - shotorg) / shotspeed);
+}
+
+float bot_aim(float shotspeed, float shotspeedupward, float maxshottime, float applygravity)
+{SELFPARAM();
+ float f, r, hf, distanceratio;
+ vector v;
+ /*
+ eprint(self);
+ dprint("bot_aim(", ftos(shotspeed));
+ dprint(", ", ftos(shotspeedupward));
+ dprint(", ", ftos(maxshottime));
+ dprint(", ", ftos(applygravity));
+ dprint(");\n");
+ */
+
+ hf = self.dphitcontentsmask;
+ self.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_CORPSE;
+
+ shotspeed *= W_WeaponSpeedFactor();
+ shotspeedupward *= W_WeaponSpeedFactor();
+ if (!shotspeed)
+ {
+ LOG_TRACE("bot_aim: WARNING: weapon ", WEP_NAME(self.weapon), " shotspeed is zero!\n");
+ shotspeed = 1000000;
+ }
+ if (!maxshottime)
+ {
+ LOG_TRACE("bot_aim: WARNING: weapon ", WEP_NAME(self.weapon), " maxshottime is zero!\n");
+ maxshottime = 1;
+ }
+ makevectors(self.v_angle);
+ shotorg = self.origin + self.view_ofs;
+ shotdir = v_forward;
+ v = bot_shotlead(self.bot_aimtargorigin, self.bot_aimtargvelocity, shotspeed, self.bot_aimlatency);
+ distanceratio = sqrt(bound(0,skill,10000))*0.3*(vlen(v-shotorg)-100)/autocvar_bot_ai_aimskill_firetolerance_distdegrees;
+ distanceratio = bound(0,distanceratio,1);
+ r = (autocvar_bot_ai_aimskill_firetolerance_maxdegrees-autocvar_bot_ai_aimskill_firetolerance_mindegrees)
+ * (1-distanceratio) + autocvar_bot_ai_aimskill_firetolerance_mindegrees;
+ if (applygravity && self.bot_aimtarg)
+ {
+ if (!findtrajectorywithleading(shotorg, '0 0 0', '0 0 0', self.bot_aimtarg, shotspeed, shotspeedupward, maxshottime, 0, self))
+ {
+ self.dphitcontentsmask = hf;
+ return false;
+ }
+
+ f = bot_aimdir(findtrajectory_velocity - shotspeedupward * '0 0 1', r);
+ }
+ else
+ {
+ f = bot_aimdir(v - shotorg, r);
+ //dprint("AIM: ");dprint(vtos(self.bot_aimtargorigin));dprint(" + ");dprint(vtos(self.bot_aimtargvelocity));dprint(" * ");dprint(ftos(self.bot_aimlatency + vlen(self.bot_aimtargorigin - shotorg) / shotspeed));dprint(" = ");dprint(vtos(v));dprint(" : aimdir = ");dprint(vtos(normalize(v - shotorg)));dprint(" : ");dprint(vtos(shotdir));dprint("\n");
+ //traceline(shotorg, shotorg + shotdir * 10000, false, self);
+ //if (trace_ent.takedamage)
+ //if (trace_fraction < 1)
+ //if (!bot_shouldattack(trace_ent))
+ // return false;
+ traceline(shotorg, self.bot_aimtargorigin, false, self);
+ if (trace_fraction < 1)
+ if (trace_ent != self.enemy)
+ if (!bot_shouldattack(trace_ent))
+ {
+ self.dphitcontentsmask = hf;
+ return false;
+ }
+ }
+
+ //if (r > maxshottime * shotspeed)
+ // return false;
+ self.dphitcontentsmask = hf;
+ return true;
+}
--- /dev/null
+#ifndef AIM_H
+#define AIM_H
+/*
+ * Globals and Fields
+ */
+
+entity tracetossent;
+entity tracetossfaketarget;
+vector findtrajectory_velocity;
+
+
+
+vector shotorg;
+vector shotdir;
+
+// lag simulation
+// upto 5 queued messages
+.float lag1_time;
+.float lag1_float1;
+.float lag1_float2;
+.entity lag1_entity1;
+.vector lag1_vec1;
+.vector lag1_vec2;
+.vector lag1_vec3;
+.vector lag1_vec4;
+
+.float lag2_time;
+.float lag2_float1;
+.float lag2_float2;
+.entity lag2_entity1;
+.vector lag2_vec1;
+.vector lag2_vec2;
+.vector lag2_vec3;
+.vector lag2_vec4;
+
+.float lag3_time;
+.float lag3_float1;
+.float lag3_float2;
+.entity lag3_entity1;
+.vector lag3_vec1;
+.vector lag3_vec2;
+.vector lag3_vec3;
+.vector lag3_vec4;
+
+.float lag4_time;
+.float lag4_float1;
+.float lag4_float2;
+.entity lag4_entity1;
+.vector lag4_vec1;
+.vector lag4_vec2;
+.vector lag4_vec3;
+.vector lag4_vec4;
+
+.float lag5_time;
+.float lag5_float1;
+.float lag5_float2;
+.entity lag5_entity1;
+.vector lag5_vec1;
+.vector lag5_vec2;
+.vector lag5_vec3;
+.vector lag5_vec4;
+
+.float bot_badaimtime;
+.float bot_aimthinktime;
+.float bot_prevaimtime;
+.float bot_firetimer;
+.float bot_aimlatency;
+
+.vector bot_mouseaim;
+.vector bot_badaimoffset;
+.vector bot_1st_order_aimfilter;
+.vector bot_2nd_order_aimfilter;
+.vector bot_3th_order_aimfilter;
+.vector bot_4th_order_aimfilter;
+.vector bot_5th_order_aimfilter;
+.vector bot_olddesiredang;
+
+.vector bot_aimselforigin;
+.vector bot_aimselfvelocity;
+.vector bot_aimtargorigin;
+.vector bot_aimtargvelocity;
+
+.entity bot_aimtarg;
+
+/*
+ * Functions
+ */
+
+float lag_additem(float t, float f1, float f2, entity e1, vector v1, vector v2, vector v3, vector v4);
+void lag_update();
+void bot_lagfunc(float t, float f1, float f2, entity e1, vector v1, vector v2, vector v3, vector v4);
+
+float bot_shouldattack(entity e);
+float bot_aimdir(vector v, float maxfiredeviation);
+float bot_aim(float shotspeed, float shotspeedupward, float maxshottime, float applygravity);
+float findtrajectorywithleading(vector org, vector m1, vector m2, entity targ, float shotspeed, float shotspeedupward, float maxtime, float shotdelay, entity ignore);
+
+vector bot_shotlead(vector targorigin, vector targvelocity, float shotspeed, float shotdelay);
+
+.void(float t, float f1, float f2, entity e1, vector v1, vector v2, vector v3, vector v4) lag_func;
+#endif
--- /dev/null
+#include "defs.qh"
+
+#include "bot.qc"
+#include "aim.qc"
+#include "navigation.qc"
+#include "scripting.qc"
+#include "waypoints.qc"
+
+#include "havocbot/havocbot.qc"
+#include "havocbot/role_keyhunt.qc"
+#include "havocbot/roles.qc"
--- /dev/null
+#include "bot.qh"
+
+#include "aim.qh"
+#include "navigation.qh"
+#include "scripting.qh"
+#include "waypoints.qh"
+
+#include "havocbot/havocbot.qh"
+
+#include "../../weapons/accuracy.qh"
+
+entity bot_spawn()
+{SELFPARAM();
+ entity bot = spawnclient();
+ if (bot)
+ {
+ currentbots = currentbots + 1;
+ setself(bot);
+ bot_setnameandstuff();
+ ClientConnect();
+ PutClientInServer();
+ setself(this);
+ }
+ return bot;
+}
+
+void bot_think()
+{SELFPARAM();
+ if (self.bot_nextthink > time)
+ return;
+
+ self.flags &= ~FL_GODMODE;
+ if(autocvar_bot_god)
+ self.flags |= FL_GODMODE;
+
+ self.bot_nextthink = self.bot_nextthink + autocvar_bot_ai_thinkinterval * pow(0.5, self.bot_aiskill);
+ //if (self.bot_painintensity > 0)
+ // self.bot_painintensity = self.bot_painintensity - (skill + 1) * 40 * frametime;
+
+ //self.bot_painintensity = self.bot_painintensity + self.bot_oldhealth - self.health;
+ //self.bot_painintensity = bound(0, self.bot_painintensity, 100);
+
+ if (!IS_PLAYER(self) || (autocvar_g_campaign && !campaign_bots_may_start))
+ {
+ self.bot_nextthink = time + 0.5;
+ return;
+ }
+
+ if (self.fixangle)
+ {
+ self.v_angle = self.angles;
+ self.v_angle_z = 0;
+ self.fixangle = false;
+ }
+
+ self.dmg_take = 0;
+ self.dmg_save = 0;
+ self.dmg_inflictor = world;
+
+ // calculate an aiming latency based on the skill setting
+ // (simulated network latency + naturally delayed reflexes)
+ //self.ping = 0.7 - bound(0, 0.05 * skill, 0.5); // moved the reflexes to bot_aimdir (under the name 'think')
+ // minimum ping 20+10 random
+ self.ping = bound(0,0.07 - bound(0, (skill + self.bot_pingskill) * 0.005,0.05)+random()*0.01,0.65); // Now holds real lag to server, and higer skill players take a less laggy server
+ // skill 10 = ping 0.2 (adrenaline)
+ // skill 0 = ping 0.7 (slightly drunk)
+
+ // clear buttons
+ self.BUTTON_ATCK = 0;
+ self.button1 = 0;
+ self.BUTTON_JUMP = 0;
+ self.BUTTON_ATCK2 = 0;
+ self.BUTTON_ZOOM = 0;
+ self.BUTTON_CROUCH = 0;
+ self.BUTTON_HOOK = 0;
+ self.BUTTON_INFO = 0;
+ self.button8 = 0;
+ self.BUTTON_CHAT = 0;
+ self.BUTTON_USE = 0;
+
+ if (time < game_starttime)
+ {
+ // block the bot during the countdown to game start
+ self.movement = '0 0 0';
+ self.bot_nextthink = game_starttime;
+ return;
+ }
+
+ // if dead, just wait until we can respawn
+ if (self.deadflag)
+ {
+ if (self.deadflag == DEAD_DEAD)
+ {
+ self.BUTTON_JUMP = 1; // press jump to respawn
+ self.bot_strategytime = 0;
+ }
+ }
+ else if(self.aistatus & AI_STATUS_STUCK)
+ navigation_unstuck();
+
+ // now call the current bot AI (havocbot for example)
+ self.bot_ai();
+}
+
+void bot_setnameandstuff()
+{SELFPARAM();
+ string readfile, s;
+ float file, tokens, prio;
+ entity p;
+
+ string bot_name, bot_model, bot_skin, bot_shirt, bot_pants;
+ string name, prefix, suffix;
+
+ if(autocvar_g_campaign)
+ {
+ prefix = "";
+ suffix = "";
+ }
+ else
+ {
+ prefix = autocvar_bot_prefix;
+ suffix = autocvar_bot_suffix;
+ }
+
+ file = fopen(autocvar_bot_config_file, FILE_READ);
+
+ if(file < 0)
+ {
+ LOG_INFO(strcat("Error: Can not open the bot configuration file '",autocvar_bot_config_file,"'\n"));
+ readfile = "";
+ }
+ else
+ {
+ RandomSelection_Init();
+ while((readfile = fgets(file)))
+ {
+ if(substring(readfile, 0, 2) == "//")
+ continue;
+ if(substring(readfile, 0, 1) == "#")
+ continue;
+ tokens = tokenizebyseparator(readfile, "\t");
+ if(tokens == 0)
+ continue;
+ s = argv(0);
+ prio = 1;
+ FOR_EACH_CLIENT(p)
+ {
+ if(IS_BOT_CLIENT(p))
+ if(s == p.cleanname)
+ {
+ prio = 0;
+ break;
+ }
+ }
+ RandomSelection_Add(world, 0, readfile, 1, prio);
+ }
+ readfile = RandomSelection_chosen_string;
+ fclose(file);
+ }
+
+ tokens = tokenizebyseparator(readfile, "\t");
+ if(argv(0) != "") bot_name = argv(0);
+ else bot_name = "Bot";
+
+ if(argv(1) != "") bot_model = argv(1);
+ else bot_model = "";
+
+ if(argv(2) != "") bot_skin = argv(2);
+ else bot_skin = "0";
+
+ if(argv(3) != "" && stof(argv(3)) >= 0) bot_shirt = argv(3);
+ else bot_shirt = ftos(floor(random() * 15));
+
+ if(argv(4) != "" && stof(argv(4)) >= 0) bot_pants = argv(4);
+ else bot_pants = ftos(floor(random() * 15));
+
+ self.bot_forced_team = stof(argv(5));
+
+ prio = 6;
+
+ #define READSKILL(f,w,r) if(argv(prio) != "") self.f = stof(argv(prio)) * (w); else self.f = (!autocvar_g_campaign) * (2 * random() - 1) * (r) * (w); ++prio
+ //print(bot_name, ": ping=", argv(9), "\n");
+
+ READSKILL(havocbot_keyboardskill, 0.5, 0.5); // keyboard skill
+ READSKILL(bot_moveskill, 2, 0); // move skill
+ READSKILL(bot_dodgeskill, 2, 0); // dodge skill
+
+ READSKILL(bot_pingskill, 0.5, 0); // ping skill
+
+ READSKILL(bot_weaponskill, 2, 0); // weapon skill
+ READSKILL(bot_aggresskill, 1, 0); // aggre skill
+ READSKILL(bot_rangepreference, 1, 0); // read skill
+
+ READSKILL(bot_aimskill, 2, 0); // aim skill
+ READSKILL(bot_offsetskill, 2, 0.5); // offset skill
+ READSKILL(bot_mouseskill, 1, 0.5); // mouse skill
+
+ READSKILL(bot_thinkskill, 1, 0.5); // think skill
+ READSKILL(bot_aiskill, 2, 0); // "ai" skill
+
+ self.bot_config_loaded = true;
+
+ // this is really only a default, JoinBestTeam is called later
+ setcolor(self, stof(bot_shirt) * 16 + stof(bot_pants));
+ self.bot_preferredcolors = self.clientcolors;
+
+ // pick the name
+ if (autocvar_bot_usemodelnames)
+ name = bot_model;
+ else
+ name = bot_name;
+
+ // number bots with identical names
+ float i;
+ i = 0;
+ FOR_EACH_CLIENT(p)
+ {
+ if(IS_BOT_CLIENT(p))
+ if(p.cleanname == name)
+ ++i;
+ }
+ if (i)
+ self.netname = self.netname_freeme = strzone(strcat(prefix, name, "(", ftos(i), ")", suffix));
+ else
+ self.netname = self.netname_freeme = strzone(strcat(prefix, name, suffix));
+
+ self.cleanname = strzone(name);
+
+ // pick the model and skin
+ if(substring(bot_model, -4, 1) != ".")
+ bot_model = strcat(bot_model, ".iqm");
+ self.playermodel = self.playermodel_freeme = strzone(strcat("models/player/", bot_model));
+ self.playerskin = self.playerskin_freeme = strzone(bot_skin);
+
+ self.cvar_cl_accuracy_data_share = 1; // share the bots weapon accuracy data with the world
+ self.cvar_cl_accuracy_data_receive = 0; // don't receive any weapon accuracy data
+}
+
+void bot_custom_weapon_priority_setup()
+{
+ float tokens, i, w;
+
+ bot_custom_weapon = false;
+
+ if( autocvar_bot_ai_custom_weapon_priority_far == "" ||
+ autocvar_bot_ai_custom_weapon_priority_mid == "" ||
+ autocvar_bot_ai_custom_weapon_priority_close == "" ||
+ autocvar_bot_ai_custom_weapon_priority_distances == ""
+ )
+ return;
+
+ // Parse distances
+ tokens = tokenizebyseparator(autocvar_bot_ai_custom_weapon_priority_distances," ");
+
+ if (tokens!=2)
+ return;
+
+ bot_distance_far = stof(argv(0));
+ bot_distance_close = stof(argv(1));
+
+ if(bot_distance_far < bot_distance_close){
+ bot_distance_far = stof(argv(1));
+ bot_distance_close = stof(argv(0));
+ }
+
+ // Initialize list of weapons
+ bot_weapons_far[0] = -1;
+ bot_weapons_mid[0] = -1;
+ bot_weapons_close[0] = -1;
+
+ // Parse far distance weapon priorities
+ tokens = tokenizebyseparator(W_NumberWeaponOrder(autocvar_bot_ai_custom_weapon_priority_far)," ");
+
+ int c = 0;
+ for(i=0; i < tokens && c < Weapons_COUNT; ++i){
+ w = stof(argv(i));
+ if ( w >= WEP_FIRST && w <= WEP_LAST) {
+ bot_weapons_far[c] = w;
+ ++c;
+ }
+ }
+ if(c < Weapons_COUNT)
+ bot_weapons_far[c] = -1;
+
+ // Parse mid distance weapon priorities
+ tokens = tokenizebyseparator(W_NumberWeaponOrder(autocvar_bot_ai_custom_weapon_priority_mid)," ");
+
+ c = 0;
+ for(i=0; i < tokens && c < Weapons_COUNT; ++i){
+ w = stof(argv(i));
+ if ( w >= WEP_FIRST && w <= WEP_LAST) {
+ bot_weapons_mid[c] = w;
+ ++c;
+ }
+ }
+ if(c < Weapons_COUNT)
+ bot_weapons_mid[c] = -1;
+
+ // Parse close distance weapon priorities
+ tokens = tokenizebyseparator(W_NumberWeaponOrder(autocvar_bot_ai_custom_weapon_priority_close)," ");
+
+ c = 0;
+ for(i=0; i < tokens && i < Weapons_COUNT; ++i){
+ w = stof(argv(i));
+ if ( w >= WEP_FIRST && w <= WEP_LAST) {
+ bot_weapons_close[c] = w;
+ ++c;
+ }
+ }
+ if(c < Weapons_COUNT)
+ bot_weapons_close[c] = -1;
+
+ bot_custom_weapon = true;
+}
+
+void bot_endgame()
+{
+ entity e;
+ //dprint("bot_endgame\n");
+ e = bot_list;
+ while (e)
+ {
+ setcolor(e, e.bot_preferredcolors);
+ e = e.nextbot;
+ }
+ // if dynamic waypoints are ever implemented, save them here
+}
+
+void bot_relinkplayerlist()
+{
+ entity e;
+ entity prevbot;
+ player_count = 0;
+ currentbots = 0;
+ player_list = e = findchainflags(flags, FL_CLIENT);
+ bot_list = world;
+ prevbot = world;
+ while (e)
+ {
+ player_count = player_count + 1;
+ e.nextplayer = e.chain;
+ if (IS_BOT_CLIENT(e))
+ {
+ if (prevbot)
+ prevbot.nextbot = e;
+ else
+ {
+ bot_list = e;
+ bot_list.nextbot = world;
+ }
+ prevbot = e;
+ currentbots = currentbots + 1;
+ }
+ e = e.chain;
+ }
+ LOG_TRACE(strcat("relink: ", ftos(currentbots), " bots seen.\n"));
+ bot_strategytoken = bot_list;
+ bot_strategytoken_taken = true;
+}
+
+void bot_clientdisconnect()
+{SELFPARAM();
+ if (!IS_BOT_CLIENT(self))
+ return;
+ bot_clearqueue(self);
+ if(self.cleanname)
+ strunzone(self.cleanname);
+ if(self.netname_freeme)
+ strunzone(self.netname_freeme);
+ if(self.playermodel_freeme)
+ strunzone(self.playermodel_freeme);
+ if(self.playerskin_freeme)
+ strunzone(self.playerskin_freeme);
+ self.cleanname = string_null;
+ self.netname_freeme = string_null;
+ self.playermodel_freeme = string_null;
+ self.playerskin_freeme = string_null;
+ if(self.bot_cmd_current)
+ remove(self.bot_cmd_current);
+ if(bot_waypoint_queue_owner==self)
+ bot_waypoint_queue_owner = world;
+}
+
+void bot_clientconnect()
+{SELFPARAM();
+ if (!IS_BOT_CLIENT(self))
+ return;
+ self.bot_preferredcolors = self.clientcolors;
+ self.bot_nextthink = time - random();
+ self.lag_func = bot_lagfunc;
+ self.isbot = true;
+ self.createdtime = self.bot_nextthink;
+
+ if(!self.bot_config_loaded) // This is needed so team overrider doesn't break between matches
+ bot_setnameandstuff();
+
+ if(self.bot_forced_team==1)
+ self.team = NUM_TEAM_1;
+ else if(self.bot_forced_team==2)
+ self.team = NUM_TEAM_2;
+ else if(self.bot_forced_team==3)
+ self.team = NUM_TEAM_3;
+ else if(self.bot_forced_team==4)
+ self.team = NUM_TEAM_4;
+ else
+ JoinBestTeam(self, false, true);
+
+ havocbot_setupbot();
+}
+
+void bot_removefromlargestteam()
+{
+ float besttime, bestcount, thiscount;
+ entity best, head;
+ CheckAllowedTeams(world);
+ GetTeamCounts(world);
+ head = findchainfloat(isbot, true);
+ if (!head)
+ return;
+ best = head;
+ besttime = head.createdtime;
+ bestcount = 0;
+ while (head)
+ {
+ if(head.team == NUM_TEAM_1)
+ thiscount = c1;
+ else if(head.team == NUM_TEAM_2)
+ thiscount = c2;
+ else if(head.team == NUM_TEAM_3)
+ thiscount = c3;
+ else if(head.team == NUM_TEAM_4)
+ thiscount = c4;
+ else
+ thiscount = 0;
+ if (thiscount > bestcount)
+ {
+ bestcount = thiscount;
+ besttime = head.createdtime;
+ best = head;
+ }
+ else if (thiscount == bestcount && besttime < head.createdtime)
+ {
+ besttime = head.createdtime;
+ best = head;
+ }
+ head = head.chain;
+ }
+ currentbots = currentbots - 1;
+ dropclient(best);
+}
+
+void bot_removenewest()
+{
+ float besttime;
+ entity best, head;
+
+ if(teamplay)
+ {
+ bot_removefromlargestteam();
+ return;
+ }
+
+ head = findchainfloat(isbot, true);
+ if (!head)
+ return;
+ best = head;
+ besttime = head.createdtime;
+ while (head)
+ {
+ if (besttime < head.createdtime)
+ {
+ besttime = head.createdtime;
+ best = head;
+ }
+ head = head.chain;
+ }
+ currentbots = currentbots - 1;
+ dropclient(best);
+}
+
+void autoskill(float factor)
+{
+ float bestbot;
+ float bestplayer;
+ entity head;
+
+ bestbot = -1;
+ bestplayer = -1;
+ FOR_EACH_PLAYER(head)
+ {
+ if(IS_REAL_CLIENT(head))
+ bestplayer = max(bestplayer, head.totalfrags - head.totalfrags_lastcheck);
+ else
+ bestbot = max(bestbot, head.totalfrags - head.totalfrags_lastcheck);
+ }
+
+ LOG_TRACE("autoskill: best player got ", ftos(bestplayer), ", ");
+ LOG_TRACE("best bot got ", ftos(bestbot), "; ");
+ if(bestbot < 0 || bestplayer < 0)
+ {
+ LOG_TRACE("not doing anything\n");
+ // don't return, let it reset all counters below
+ }
+ else if(bestbot <= bestplayer * factor - 2)
+ {
+ if(autocvar_skill < 17)
+ {
+ LOG_TRACE("2 frags difference, increasing skill\n");
+ cvar_set("skill", ftos(autocvar_skill + 1));
+ bprint("^2SKILL UP!^7 Now at level ", ftos(autocvar_skill), "\n");
+ }
+ }
+ else if(bestbot >= bestplayer * factor + 2)
+ {
+ if(autocvar_skill > 0)
+ {
+ LOG_TRACE("2 frags difference, decreasing skill\n");
+ cvar_set("skill", ftos(autocvar_skill - 1));
+ bprint("^1SKILL DOWN!^7 Now at level ", ftos(autocvar_skill), "\n");
+ }
+ }
+ else
+ {
+ LOG_TRACE("not doing anything\n");
+ return;
+ // don't reset counters, wait for them to accumulate
+ }
+
+ FOR_EACH_PLAYER(head)
+ head.totalfrags_lastcheck = head.totalfrags;
+}
+
+void bot_calculate_stepheightvec(void)
+{
+ stepheightvec = autocvar_sv_stepheight * '0 0 1';
+ jumpstepheightvec = stepheightvec +
+ ((autocvar_sv_jumpvelocity * autocvar_sv_jumpvelocity) / (2 * autocvar_sv_gravity)) * '0 0 0.85';
+ // 0.75 factor is for safety to make the jumps easy
+}
+
+float bot_fixcount()
+{
+ entity head;
+ float realplayers, bots, activerealplayers;
+
+ activerealplayers = 0;
+ realplayers = 0;
+
+ FOR_EACH_REALCLIENT(head)
+ {
+ if(IS_PLAYER(head) || g_lms || head.caplayer == 1)
+ ++activerealplayers;
+ ++realplayers;
+ }
+
+ // add/remove bots if needed to make sure there are at least
+ // minplayers+bot_number, or remove all bots if no one is playing
+ // But don't remove bots immediately on level change, as the real players
+ // usually haven't rejoined yet
+ bots_would_leave = false;
+ if (teamplay && autocvar_bot_vs_human && (c3==-1 && c4==-1))
+ bots = min(ceil(fabs(autocvar_bot_vs_human) * activerealplayers), maxclients - realplayers);
+ else if ((realplayers || autocvar_bot_join_empty || (currentbots > 0 && time < 5)))
+ {
+ float realminplayers, minplayers;
+ realminplayers = autocvar_minplayers;
+ minplayers = max(0, floor(realminplayers));
+
+ float realminbots, minbots;
+ realminbots = autocvar_bot_number;
+ minbots = max(0, floor(realminbots));
+
+ bots = min(max(minbots, minplayers - activerealplayers), maxclients - realplayers);
+ if(bots > minbots)
+ bots_would_leave = true;
+ }
+ else
+ {
+ // if there are no players, remove bots
+ bots = 0;
+ }
+
+ // only add one bot per frame to avoid utter chaos
+ if(time > botframe_nextthink)
+ {
+ //dprint(ftos(bots), " ? ", ftos(currentbots), "\n");
+ while (currentbots < bots)
+ {
+ if (bot_spawn() == world)
+ {
+ bprint("Can not add bot, server full.\n");
+ return false;
+ }
+ }
+ while (currentbots > bots)
+ bot_removenewest();
+ }
+
+ return true;
+}
+
+void bot_serverframe()
+{
+ if (intermission_running)
+ return;
+
+ if (time < 2)
+ return;
+
+ bot_calculate_stepheightvec();
+ bot_navigation_movemode = ((autocvar_bot_navigation_ignoreplayers) ? MOVE_NOMONSTERS : MOVE_NORMAL);
+
+ if(time > autoskill_nextthink)
+ {
+ float a;
+ a = autocvar_skill_auto;
+ if(a)
+ autoskill(a);
+ autoskill_nextthink = time + 5;
+ }
+
+ if(time > botframe_nextthink)
+ {
+ if(!bot_fixcount())
+ botframe_nextthink = time + 10;
+ }
+
+ bot_ignore_bots = autocvar_bot_ignore_bots;
+
+ if(botframe_spawnedwaypoints)
+ {
+ if(autocvar_waypoint_benchmark)
+ localcmd("quit\n");
+ }
+
+ if (currentbots > 0 || autocvar_g_waypointeditor || autocvar_g_waypointeditor_auto)
+ if (botframe_spawnedwaypoints)
+ {
+ if(botframe_cachedwaypointlinks)
+ {
+ if(!botframe_loadedforcedlinks)
+ waypoint_load_links_hardwired();
+ }
+ else
+ {
+ // TODO: Make this check cleaner
+ entity wp = findchain(classname, "waypoint");
+ if(time - wp.nextthink > 10)
+ waypoint_save_links();
+ }
+ }
+ else
+ {
+ botframe_spawnedwaypoints = true;
+ waypoint_loadall();
+ if(!waypoint_load_links())
+ waypoint_schedulerelinkall();
+ }
+
+ if (bot_list)
+ {
+ // cycle the goal token from one bot to the next each frame
+ // (this prevents them from all doing spawnfunc_waypoint searches on the same
+ // frame, which causes choppy framerates)
+ if (bot_strategytoken_taken)
+ {
+ bot_strategytoken_taken = false;
+ if (bot_strategytoken)
+ bot_strategytoken = bot_strategytoken.nextbot;
+ if (!bot_strategytoken)
+ bot_strategytoken = bot_list;
+ }
+
+ if (botframe_nextdangertime < time)
+ {
+ float interval;
+ interval = autocvar_bot_ai_dangerdetectioninterval;
+ if (botframe_nextdangertime < time - interval * 1.5)
+ botframe_nextdangertime = time;
+ botframe_nextdangertime = botframe_nextdangertime + interval;
+ botframe_updatedangerousobjects(autocvar_bot_ai_dangerdetectionupdates);
+ }
+ }
+
+ if (autocvar_g_waypointeditor)
+ botframe_showwaypointlinks();
+
+ if (autocvar_g_waypointeditor_auto)
+ botframe_autowaypoints();
+
+ if(time > bot_cvar_nextthink)
+ {
+ if(currentbots>0)
+ bot_custom_weapon_priority_setup();
+ bot_cvar_nextthink = time + 5;
+ }
+}
--- /dev/null
+#ifndef BOT_H
+#define BOT_H
+/*
+ * Globals and Fields
+ */
+
+const int AI_STATUS_ROAMING = 1; // Bot is just crawling the map. No enemies at sight
+const int AI_STATUS_ATTACKING = 2; // There are enemies at sight
+const int AI_STATUS_RUNNING = 4; // Bot is bunny hopping
+const int AI_STATUS_DANGER_AHEAD = 8; // There is lava/slime/trigger_hurt ahead
+const int AI_STATUS_OUT_JUMPPAD = 16; // Trying to get out of a "vertical" jump pad
+const int AI_STATUS_OUT_WATER = 32; // Trying to get out of water
+const int AI_STATUS_WAYPOINT_PERSONAL_LINKING = 64; // Waiting for the personal waypoint to be linked
+const int AI_STATUS_WAYPOINT_PERSONAL_GOING = 128; // Going to a personal waypoint
+const int AI_STATUS_WAYPOINT_PERSONAL_REACHED = 256; // Personal waypoint reached
+const int AI_STATUS_JETPACK_FLYING = 512;
+const int AI_STATUS_JETPACK_LANDING = 1024;
+const int AI_STATUS_STUCK = 2048; // Cannot reach any goal
+
+.float isbot; // true if this client is actually a bot
+.int aistatus;
+
+// Skill system
+float autoskill_nextthink;
+
+// havocbot_keyboardskill // keyboard movement
+.float bot_moveskill; // moving technique
+.float bot_dodgeskill; // dodging
+
+.float bot_pingskill; // ping offset
+
+.float bot_weaponskill; // weapon usage skill (combos, e.g.)
+.float bot_aggresskill; // aggressivity, controls "think before fire" behaviour
+.float bot_rangepreference; // weapon choice offset for range (>0 = prefer long range earlier "sniper", <0 = prefer short range "spammer")
+
+.float bot_aimskill; // aim accuracy
+.float bot_offsetskill; // aim breakage
+.float bot_mouseskill; // mouse "speed"
+
+.float bot_thinkskill; // target choice
+.float bot_aiskill; // strategy choice
+
+.float totalfrags_lastcheck;
+
+
+
+entity bot_list;
+entity player_list;
+.entity nextbot;
+.entity nextplayer;
+.string cleanname;
+.string netname_freeme;
+.string playermodel_freeme;
+.string playerskin_freeme;
+
+.float bot_nextthink;
+
+.float createdtime;
+.float bot_preferredcolors;
+.float bot_attack;
+.float bot_dodge;
+.float bot_dodgerating;
+
+.float bot_pickup;
+.float bot_pickupbasevalue;
+.float bot_canfire;
+.float bot_strategytime;
+
+.float bot_forced_team;
+.float bot_config_loaded;
+
+float bot_strategytoken_taken;
+entity bot_strategytoken;
+
+float botframe_spawnedwaypoints;
+float botframe_nextthink;
+float botframe_nextdangertime;
+float bot_cvar_nextthink;
+
+/*
+ * Functions
+ */
+
+entity bot_spawn();
+float bot_fixcount();
+
+void bot_think();
+void bot_setnameandstuff();
+void bot_custom_weapon_priority_setup();
+void bot_endgame();
+void bot_relinkplayerlist();
+void bot_clientdisconnect();
+void bot_clientconnect();
+void bot_removefromlargestteam();
+void bot_removenewest();
+void autoskill(float factor);
+void bot_serverframe();
+
+.void() bot_ai;
+.float(entity player, entity item) bot_pickupevalfunc;
+
+/*
+ * Imports
+ */
+
+void() havocbot_setupbot;
+
+void bot_calculate_stepheightvec(void);
+#endif
--- /dev/null
+float bot_distance_close;
+float bot_distance_far;
+float bot_ignore_bots; // let bots not attack other bots (only works in non-teamplay)
+
+int autocvar_g_waypointeditor_auto;
+float autocvar_skill_auto;
+float autocvar_sv_jumpvelocity;
+bool autocvar_waypoint_benchmark;
+bool autocvar_bot_god;
+float autocvar_bot_ai_thinkinterval;
+float autocvar_bot_ai_aimskill_blendrate;
+float autocvar_bot_ai_aimskill_firetolerance_distdegrees;
+float autocvar_bot_ai_aimskill_firetolerance_maxdegrees;
+float autocvar_bot_ai_aimskill_firetolerance_mindegrees;
+float autocvar_bot_ai_aimskill_fixedrate;
+float autocvar_bot_ai_aimskill_mouse;
+float autocvar_bot_ai_aimskill_offset;
+float autocvar_bot_ai_aimskill_order_filter_1st;
+float autocvar_bot_ai_aimskill_order_filter_2nd;
+float autocvar_bot_ai_aimskill_order_filter_3th;
+float autocvar_bot_ai_aimskill_order_filter_4th;
+float autocvar_bot_ai_aimskill_order_filter_5th;
+float autocvar_bot_ai_aimskill_order_mix_1st;
+float autocvar_bot_ai_aimskill_order_mix_2nd;
+float autocvar_bot_ai_aimskill_order_mix_3th;
+float autocvar_bot_ai_aimskill_order_mix_4th;
+float autocvar_bot_ai_aimskill_order_mix_5th;
+float autocvar_bot_ai_aimskill_think;
+float autocvar_bot_ai_bunnyhop_firstjumpdelay;
+float autocvar_bot_ai_bunnyhop_skilloffset;
+float autocvar_bot_ai_bunnyhop_startdistance;
+float autocvar_bot_ai_bunnyhop_stopdistance;
+float autocvar_bot_ai_chooseweaponinterval;
+string autocvar_bot_ai_custom_weapon_priority_close;
+string autocvar_bot_ai_custom_weapon_priority_distances;
+string autocvar_bot_ai_custom_weapon_priority_far;
+string autocvar_bot_ai_custom_weapon_priority_mid;
+float autocvar_bot_ai_dangerdetectioninterval;
+float autocvar_bot_ai_dangerdetectionupdates;
+float autocvar_bot_ai_enemydetectioninterval;
+float autocvar_bot_ai_enemydetectionradius;
+float autocvar_bot_ai_friends_aware_pickup_radius;
+float autocvar_bot_ai_ignoregoal_timeout;
+float autocvar_bot_ai_keyboard_distance;
+float autocvar_bot_ai_keyboard_threshold;
+float autocvar_bot_ai_navigation_jetpack;
+float autocvar_bot_ai_navigation_jetpack_mindistance;
+bool autocvar_bot_ai_weapon_combo;
+float autocvar_bot_ai_weapon_combo_threshold;
+string autocvar_bot_config_file;
+bool autocvar_bot_ignore_bots;
+bool autocvar_bot_join_empty;
+bool autocvar_bot_navigation_ignoreplayers;
+bool autocvar_bot_nofire;
+#define autocvar_bot_prefix cvar_string("bot_prefix")
+#define autocvar_bot_suffix cvar_string("bot_suffix")
+bool autocvar_bot_usemodelnames;
+int autocvar_bot_debug;
+bool autocvar_bot_debug_tracewalk;
+bool autocvar_bot_debug_goalstack;
+bool autocvar_bot_wander_enable;
--- /dev/null
+#include "havocbot.qh"
+
+void havocbot_ai()
+{SELFPARAM();
+ if(self.draggedby)
+ return;
+
+ if(bot_execute_commands())
+ return;
+
+ if (bot_strategytoken == self)
+ if (!bot_strategytoken_taken)
+ {
+ if(self.havocbot_blockhead)
+ {
+ self.havocbot_blockhead = false;
+ }
+ else
+ {
+ if (!self.jumppadcount)
+ self.havocbot_role();
+ }
+
+ // TODO: tracewalk() should take care of this job (better path finding under water)
+ // if we don't have a goal and we're under water look for a waypoint near the "shore" and push it
+ if(self.deadflag != DEAD_NO)
+ if(self.goalcurrent==world)
+ if(self.waterlevel==WATERLEVEL_SWIMMING || (self.aistatus & AI_STATUS_OUT_WATER))
+ {
+ // Look for the closest waypoint out of water
+ entity newgoal, head;
+ float bestdistance, distance;
+
+ newgoal = world;
+ bestdistance = 10000;
+ for (head = findchain(classname, "waypoint"); head; head = head.chain)
+ {
+ distance = vlen(head.origin - self.origin);
+ if(distance>10000)
+ continue;
+
+ if(head.origin.z < self.origin.z)
+ continue;
+
+ if(head.origin.z - self.origin.z - self.view_ofs.z > 100)
+ continue;
+
+ if (pointcontents(head.origin + head.maxs + '0 0 1') != CONTENT_EMPTY)
+ continue;
+
+ traceline(self.origin + self.view_ofs , head.origin, true, head);
+
+ if(trace_fraction<1)
+ continue;
+
+ if(distance<bestdistance)
+ {
+ newgoal = head;
+ bestdistance = distance;
+ }
+ }
+
+ if(newgoal)
+ {
+ // te_wizspike(newgoal.origin);
+ navigation_pushroute(newgoal);
+ }
+ }
+
+ // token has been used this frame
+ bot_strategytoken_taken = true;
+ }
+
+ if(self.deadflag != DEAD_NO)
+ return;
+
+ havocbot_chooseenemy();
+ if (self.bot_chooseweapontime < time )
+ {
+ self.bot_chooseweapontime = time + autocvar_bot_ai_chooseweaponinterval;
+ havocbot_chooseweapon();
+ }
+ havocbot_aim();
+ lag_update();
+ if (self.bot_aimtarg)
+ {
+ self.aistatus |= AI_STATUS_ATTACKING;
+ self.aistatus &= ~AI_STATUS_ROAMING;
+
+ if(self.weapons)
+ {
+ Weapon w = get_weaponinfo(self.weapon);
+ w.wr_aim(w);
+ if (autocvar_bot_nofire || IS_INDEPENDENT_PLAYER(self))
+ {
+ self.BUTTON_ATCK = false;
+ self.BUTTON_ATCK2 = false;
+ }
+ else
+ {
+ if(self.BUTTON_ATCK||self.BUTTON_ATCK2)
+ self.lastfiredweapon = self.weapon;
+ }
+ }
+ else
+ {
+ if(IS_PLAYER(self.bot_aimtarg))
+ bot_aimdir(self.bot_aimtarg.origin + self.bot_aimtarg.view_ofs - self.origin - self.view_ofs , -1);
+ }
+ }
+ else if (self.goalcurrent)
+ {
+ self.aistatus |= AI_STATUS_ROAMING;
+ self.aistatus &= ~AI_STATUS_ATTACKING;
+
+ vector now,v,next;//,heading;
+ float aimdistance,skillblend,distanceblend,blend;
+ next = now = ( (self.goalcurrent.absmin + self.goalcurrent.absmax) * 0.5) - (self.origin + self.view_ofs);
+ aimdistance = vlen(now);
+ //heading = self.velocity;
+ //dprint(self.goalstack01.classname,etos(self.goalstack01),"\n");
+ if(
+ self.goalstack01 != self && self.goalstack01 != world && ((self.aistatus & AI_STATUS_RUNNING) == 0) &&
+ !(self.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT)
+ )
+ next = ((self.goalstack01.absmin + self.goalstack01.absmax) * 0.5) - (self.origin + self.view_ofs);
+
+ skillblend=bound(0,(skill+self.bot_moveskill-2.5)*0.5,1); //lower skill player can't preturn
+ distanceblend=bound(0,aimdistance/autocvar_bot_ai_keyboard_distance,1);
+ blend = skillblend * (1-distanceblend);
+ //v = (now * (distanceblend) + next * (1-distanceblend)) * (skillblend) + now * (1-skillblend);
+ //v = now * (distanceblend) * (skillblend) + next * (1-distanceblend) * (skillblend) + now * (1-skillblend);
+ //v = now * ((1-skillblend) + (distanceblend) * (skillblend)) + next * (1-distanceblend) * (skillblend);
+ v = now + blend * (next - now);
+ //dprint(etos(self), " ");
+ //dprint(vtos(now), ":", vtos(next), "=", vtos(v), " (blend ", ftos(blend), ")\n");
+ //v = now * (distanceblend) + next * (1-distanceblend);
+ if (self.waterlevel < WATERLEVEL_SWIMMING)
+ v.z = 0;
+ //dprint("walk at:", vtos(v), "\n");
+ //te_lightning2(world, self.origin, self.goalcurrent.origin);
+ bot_aimdir(v, -1);
+ }
+ havocbot_movetogoal();
+
+ // if the bot is not attacking, consider reloading weapons
+ if (!(self.aistatus & AI_STATUS_ATTACKING))
+ {
+ // we are currently holding a weapon that's not fully loaded, reload it
+ if(skill >= 2) // bots can only reload the held weapon on purpose past this skill
+ if(self.clip_load < self.clip_size)
+ self.impulse = 20; // "press" the reload button, not sure if this is done right
+
+ // if we're not reloading a weapon, switch to any weapon in our invnetory that's not fully loaded to reload it next
+ // the code above executes next frame, starting the reloading then
+ if(skill >= 5) // bots can only look for unloaded weapons past this skill
+ if(self.clip_load >= 0) // only if we're not reloading a weapon already
+ {
+ for (int i = WEP_FIRST; i <= WEP_LAST; ++i)
+ {
+ entity e = get_weaponinfo(i);
+ if ((self.weapons & WepSet_FromWeapon(i)) && (e.spawnflags & WEP_FLAG_RELOADABLE) && (self.weapon_load[i] < e.reloading_ammo))
+ self.switchweapon = i;
+ }
+ }
+ }
+}
+
+void havocbot_keyboard_movement(vector destorg)
+{SELFPARAM();
+ vector keyboard;
+ float blend, maxspeed;
+ float sk;
+
+ sk = skill + self.bot_moveskill;
+
+ maxspeed = autocvar_sv_maxspeed;
+
+ if (time < self.havocbot_keyboardtime)
+ return;
+
+ self.havocbot_keyboardtime =
+ max(
+ self.havocbot_keyboardtime
+ + 0.05/max(1, sk+self.havocbot_keyboardskill)
+ + random()*0.025/max(0.00025, skill+self.havocbot_keyboardskill)
+ , time);
+ keyboard = self.movement * (1.0 / maxspeed);
+
+ float trigger, trigger1;
+ blend = bound(0,sk*0.1,1);
+ trigger = autocvar_bot_ai_keyboard_threshold;
+ trigger1 = 0 - trigger;
+
+ // categorize forward movement
+ // at skill < 1.5 only forward
+ // at skill < 2.5 only individual directions
+ // at skill < 4.5 only individual directions, and forward diagonals
+ // at skill >= 4.5, all cases allowed
+ if (keyboard.x > trigger)
+ {
+ keyboard.x = 1;
+ if (sk < 2.5)
+ keyboard.y = 0;
+ }
+ else if (keyboard.x < trigger1 && sk > 1.5)
+ {
+ keyboard.x = -1;
+ if (sk < 4.5)
+ keyboard.y = 0;
+ }
+ else
+ {
+ keyboard.x = 0;
+ if (sk < 1.5)
+ keyboard.y = 0;
+ }
+ if (sk < 4.5)
+ keyboard.z = 0;
+
+ if (keyboard.y > trigger)
+ keyboard.y = 1;
+ else if (keyboard.y < trigger1)
+ keyboard.y = -1;
+ else
+ keyboard.y = 0;
+
+ if (keyboard.z > trigger)
+ keyboard.z = 1;
+ else if (keyboard.z < trigger1)
+ keyboard.z = -1;
+ else
+ keyboard.z = 0;
+
+ self.havocbot_keyboard = keyboard * maxspeed;
+ if (self.havocbot_ducktime>time) self.BUTTON_CROUCH=true;
+
+ keyboard = self.havocbot_keyboard;
+ blend = bound(0,vlen(destorg-self.origin)/autocvar_bot_ai_keyboard_distance,1); // When getting close move with 360 degree
+ //dprint("movement ", vtos(self.movement), " keyboard ", vtos(keyboard), " blend ", ftos(blend), "\n");
+ self.movement = self.movement + (keyboard - self.movement) * blend;
+}
+
+void havocbot_bunnyhop(vector dir)
+{SELFPARAM();
+ float bunnyhopdistance;
+ vector deviation;
+ float maxspeed;
+ vector gco, gno;
+
+ // Don't jump when attacking
+ if(self.aistatus & AI_STATUS_ATTACKING)
+ return;
+
+ if(IS_PLAYER(self.goalcurrent))
+ return;
+
+ maxspeed = autocvar_sv_maxspeed;
+
+ if(self.aistatus & AI_STATUS_DANGER_AHEAD)
+ {
+ self.aistatus &= ~AI_STATUS_RUNNING;
+ self.BUTTON_JUMP = false;
+ self.bot_canruntogoal = 0;
+ self.bot_timelastseengoal = 0;
+ return;
+ }
+
+ if(self.waterlevel > WATERLEVEL_WETFEET)
+ {
+ self.aistatus &= ~AI_STATUS_RUNNING;
+ return;
+ }
+
+ if(self.bot_lastseengoal != self.goalcurrent && !(self.aistatus & AI_STATUS_RUNNING))
+ {
+ self.bot_canruntogoal = 0;
+ self.bot_timelastseengoal = 0;
+ }
+
+ gco = (self.goalcurrent.absmin + self.goalcurrent.absmax) * 0.5;
+ bunnyhopdistance = vlen(self.origin - gco);
+
+ // Run only to visible goals
+ if(self.flags & FL_ONGROUND)
+ if(self.speed==maxspeed)
+ if(checkpvs(self.origin + self.view_ofs, self.goalcurrent))
+ {
+ self.bot_lastseengoal = self.goalcurrent;
+
+ // seen it before
+ if(self.bot_timelastseengoal)
+ {
+ // for a period of time
+ if(time - self.bot_timelastseengoal > autocvar_bot_ai_bunnyhop_firstjumpdelay)
+ {
+ float checkdistance;
+ checkdistance = true;
+
+ // don't run if it is too close
+ if(self.bot_canruntogoal==0)
+ {
+ if(bunnyhopdistance > autocvar_bot_ai_bunnyhop_startdistance)
+ self.bot_canruntogoal = 1;
+ else
+ self.bot_canruntogoal = -1;
+ }
+
+ if(self.bot_canruntogoal != 1)
+ return;
+
+ if(self.aistatus & AI_STATUS_ROAMING)
+ if(self.goalcurrent.classname=="waypoint")
+ if (!(self.goalcurrent.wpflags & WAYPOINTFLAG_PERSONAL))
+ if(fabs(gco.z - self.origin.z) < self.maxs.z - self.mins.z)
+ if(self.goalstack01!=world)
+ {
+ gno = (self.goalstack01.absmin + self.goalstack01.absmax) * 0.5;
+ deviation = vectoangles(gno - self.origin) - vectoangles(gco - self.origin);
+ while (deviation.y < -180) deviation.y = deviation.y + 360;
+ while (deviation.y > 180) deviation.y = deviation.y - 360;
+
+ if(fabs(deviation.y) < 20)
+ if(bunnyhopdistance < vlen(self.origin - gno))
+ if(fabs(gno.z - gco.z) < self.maxs.z - self.mins.z)
+ {
+ if(vlen(gco - gno) > autocvar_bot_ai_bunnyhop_startdistance)
+ if(checkpvs(self.origin + self.view_ofs, self.goalstack01))
+ {
+ checkdistance = false;
+ }
+ }
+ }
+
+ if(checkdistance)
+ {
+ self.aistatus &= ~AI_STATUS_RUNNING;
+ if(bunnyhopdistance > autocvar_bot_ai_bunnyhop_stopdistance)
+ self.BUTTON_JUMP = true;
+ }
+ else
+ {
+ self.aistatus |= AI_STATUS_RUNNING;
+ self.BUTTON_JUMP = true;
+ }
+ }
+ }
+ else
+ {
+ self.bot_timelastseengoal = time;
+ }
+ }
+ else
+ {
+ self.bot_timelastseengoal = 0;
+ }
+
+#if 0
+ // Release jump button
+ if(!cvar("sv_pogostick"))
+ if((self.flags & FL_ONGROUND) == 0)
+ {
+ if(self.velocity.z < 0 || vlen(self.velocity)<maxspeed)
+ self.BUTTON_JUMP = false;
+
+ // Strafe
+ if(self.aistatus & AI_STATUS_RUNNING)
+ if(vlen(self.velocity)>maxspeed)
+ {
+ deviation = vectoangles(dir) - vectoangles(self.velocity);
+ while (deviation.y < -180) deviation.y = deviation.y + 360;
+ while (deviation.y > 180) deviation.y = deviation.y - 360;
+
+ if(fabs(deviation.y)>10)
+ self.movement_x = 0;
+
+ if(deviation.y>10)
+ self.movement_y = maxspeed * -1;
+ else if(deviation.y<10)
+ self.movement_y = maxspeed;
+
+ }
+ }
+#endif
+}
+
+void havocbot_movetogoal()
+{SELFPARAM();
+ vector destorg;
+ vector diff;
+ vector dir;
+ vector flatdir;
+ vector m1;
+ vector m2;
+ vector evadeobstacle;
+ vector evadelava;
+ float s;
+ float maxspeed;
+ vector gco;
+ //float dist;
+ vector dodge;
+ //if (self.goalentity)
+ // te_lightning2(self, self.origin, (self.goalentity.absmin + self.goalentity.absmax) * 0.5);
+ self.movement = '0 0 0';
+ maxspeed = autocvar_sv_maxspeed;
+
+ // Jetpack navigation
+ if(self.goalcurrent)
+ if(self.navigation_jetpack_goal)
+ if(self.goalcurrent==self.navigation_jetpack_goal)
+ if(self.ammo_fuel)
+ {
+ if(autocvar_bot_debug_goalstack)
+ {
+ debuggoalstack();
+ te_wizspike(self.navigation_jetpack_point);
+ }
+
+ // Take off
+ if (!(self.aistatus & AI_STATUS_JETPACK_FLYING))
+ {
+ // Brake almost completely so it can get a good direction
+ if(vlen(self.velocity)>10)
+ return;
+ self.aistatus |= AI_STATUS_JETPACK_FLYING;
+ }
+
+ makevectors(self.v_angle.y * '0 1 0');
+ dir = normalize(self.navigation_jetpack_point - self.origin);
+
+ // Landing
+ if(self.aistatus & AI_STATUS_JETPACK_LANDING)
+ {
+ // Calculate brake distance in xy
+ float db, v, d;
+ vector dxy;
+
+ dxy = self.origin - ( ( self.goalcurrent.absmin + self.goalcurrent.absmax ) * 0.5 ); dxy.z = 0;
+ d = vlen(dxy);
+ v = vlen(self.velocity - self.velocity.z * '0 0 1');
+ db = (pow(v,2) / (autocvar_g_jetpack_acceleration_side * 2)) + 100;
+ // dprint("distance ", ftos(ceil(d)), " velocity ", ftos(ceil(v)), " brake at ", ftos(ceil(db)), "\n");
+ if(d < db || d < 500)
+ {
+ // Brake
+ if(fabs(self.velocity.x)>maxspeed*0.3)
+ {
+ self.movement_x = dir * v_forward * -maxspeed;
+ return;
+ }
+ // Switch to normal mode
+ self.navigation_jetpack_goal = world;
+ self.aistatus &= ~AI_STATUS_JETPACK_LANDING;
+ self.aistatus &= ~AI_STATUS_JETPACK_FLYING;
+ return;
+ }
+ }
+ else if(checkpvs(self.origin,self.goalcurrent))
+ {
+ // If I can see the goal switch to landing code
+ self.aistatus &= ~AI_STATUS_JETPACK_FLYING;
+ self.aistatus |= AI_STATUS_JETPACK_LANDING;
+ return;
+ }
+
+ // Flying
+ self.BUTTON_HOOK = true;
+ if(self.navigation_jetpack_point.z - PL_MAX.z + PL_MIN.z < self.origin.z)
+ {
+ self.movement_x = dir * v_forward * maxspeed;
+ self.movement_y = dir * v_right * maxspeed;
+ }
+ return;
+ }
+
+ // Handling of jump pads
+ if(self.jumppadcount)
+ {
+ // If got stuck on the jump pad try to reach the farthest visible waypoint
+ if(self.aistatus & AI_STATUS_OUT_JUMPPAD)
+ {
+ if(fabs(self.velocity.z)<50)
+ {
+ entity head, newgoal = world;
+ float distance, bestdistance = 0;
+
+ for (head = findchain(classname, "waypoint"); head; head = head.chain)
+ {
+
+ distance = vlen(head.origin - self.origin);
+ if(distance>1000)
+ continue;
+
+ traceline(self.origin + self.view_ofs , ( ( head.absmin + head.absmax ) * 0.5 ), true, world);
+
+ if(trace_fraction<1)
+ continue;
+
+ if(distance>bestdistance)
+ {
+ newgoal = head;
+ bestdistance = distance;
+ }
+ }
+
+ if(newgoal)
+ {
+ self.ignoregoal = self.goalcurrent;
+ self.ignoregoaltime = time + autocvar_bot_ai_ignoregoal_timeout;
+ navigation_clearroute();
+ navigation_routetogoal(newgoal, self.origin);
+ self.aistatus &= ~AI_STATUS_OUT_JUMPPAD;
+ }
+ }
+ else
+ return;
+ }
+ else
+ {
+ if(self.velocity.z>0)
+ {
+ float threshold, sxy;
+ vector velxy = self.velocity; velxy_z = 0;
+ sxy = vlen(velxy);
+ threshold = maxspeed * 0.2;
+ if(sxy < threshold)
+ {
+ LOG_TRACE("Warning: ", self.netname, " got stuck on a jumppad (velocity in xy is ", ftos(sxy), "), trying to get out of it now\n");
+ self.aistatus |= AI_STATUS_OUT_JUMPPAD;
+ }
+ return;
+ }
+
+ // Don't chase players while using a jump pad
+ if(IS_PLAYER(self.goalcurrent) || IS_PLAYER(self.goalstack01))
+ return;
+ }
+ }
+ else if(self.aistatus & AI_STATUS_OUT_JUMPPAD)
+ self.aistatus &= ~AI_STATUS_OUT_JUMPPAD;
+
+ // If there is a trigger_hurt right below try to use the jetpack or make a rocketjump
+ if(skill>6)
+ if (!(self.flags & FL_ONGROUND))
+ {
+ tracebox(self.origin, self.mins, self.maxs, self.origin + '0 0 -65536', MOVE_NOMONSTERS, self);
+ if(tracebox_hits_trigger_hurt(self.origin, self.mins, self.maxs, trace_endpos ))
+ if(self.items & IT_JETPACK)
+ {
+ tracebox(self.origin, self.mins, self.maxs, self.origin + '0 0 65536', MOVE_NOMONSTERS, self);
+ if(tracebox_hits_trigger_hurt(self.origin, self.mins, self.maxs, trace_endpos + '0 0 1' ))
+ {
+ if(self.velocity.z<0)
+ {
+ self.BUTTON_HOOK = true;
+ }
+ }
+ else
+ self.BUTTON_HOOK = true;
+
+ // If there is no goal try to move forward
+
+ if(self.goalcurrent==world)
+ dir = v_forward;
+ else
+ dir = normalize(( ( self.goalcurrent.absmin + self.goalcurrent.absmax ) * 0.5 ) - self.origin);
+
+ vector xyvelocity = self.velocity; xyvelocity_z = 0;
+ float xyspeed = xyvelocity * dir;
+
+ if(xyspeed < (maxspeed / 2))
+ {
+ makevectors(self.v_angle.y * '0 1 0');
+ tracebox(self.origin, self.mins, self.maxs, self.origin + (dir * maxspeed * 3), MOVE_NOMONSTERS, self);
+ if(trace_fraction==1)
+ {
+ self.movement_x = dir * v_forward * maxspeed;
+ self.movement_y = dir * v_right * maxspeed;
+ if (skill < 10)
+ havocbot_keyboard_movement(self.origin + dir * 100);
+ }
+ }
+
+ self.havocbot_blockhead = true;
+
+ return;
+ }
+ else if(self.health>WEP_CVAR(devastator, damage)*0.5)
+ {
+ if(self.velocity.z < 0)
+ if(client_hasweapon(self, WEP_DEVASTATOR.m_id, true, false))
+ {
+ self.movement_x = maxspeed;
+
+ if(self.rocketjumptime)
+ {
+ if(time > self.rocketjumptime)
+ {
+ self.BUTTON_ATCK2 = true;
+ self.rocketjumptime = 0;
+ }
+ return;
+ }
+
+ self.switchweapon = WEP_DEVASTATOR.m_id;
+ self.v_angle_x = 90;
+ self.BUTTON_ATCK = true;
+ self.rocketjumptime = time + WEP_CVAR(devastator, detonatedelay);
+ return;
+ }
+ }
+ else
+ {
+ // If there is no goal try to move forward
+ if(self.goalcurrent==world)
+ self.movement_x = maxspeed;
+ }
+ }
+
+ // If we are under water with no goals, swim up
+ if(self.waterlevel)
+ if(self.goalcurrent==world)
+ {
+ dir = '0 0 0';
+ if(self.waterlevel>WATERLEVEL_SWIMMING)
+ dir.z = 1;
+ else if(self.velocity.z >= 0 && !(self.waterlevel == WATERLEVEL_WETFEET && self.watertype == CONTENT_WATER))
+ self.BUTTON_JUMP = true;
+ else
+ self.BUTTON_JUMP = false;
+ makevectors(self.v_angle.y * '0 1 0');
+ self.movement_x = dir * v_forward * maxspeed;
+ self.movement_y = dir * v_right * maxspeed;
+ self.movement_z = dir * v_up * maxspeed;
+ }
+
+ // if there is nowhere to go, exit
+ if (self.goalcurrent == world)
+ return;
+
+ if (self.goalcurrent)
+ navigation_poptouchedgoals();
+
+ // if ran out of goals try to use an alternative goal or get a new strategy asap
+ if(self.goalcurrent == world)
+ {
+ self.bot_strategytime = 0;
+ return;
+ }
+
+
+ if(autocvar_bot_debug_goalstack)
+ debuggoalstack();
+
+ m1 = self.goalcurrent.origin + self.goalcurrent.mins;
+ m2 = self.goalcurrent.origin + self.goalcurrent.maxs;
+ destorg = self.origin;
+ destorg.x = bound(m1_x, destorg.x, m2_x);
+ destorg.y = bound(m1_y, destorg.y, m2_y);
+ destorg.z = bound(m1_z, destorg.z, m2_z);
+ diff = destorg - self.origin;
+ //dist = vlen(diff);
+ dir = normalize(diff);
+ flatdir = diff;flatdir.z = 0;
+ flatdir = normalize(flatdir);
+ gco = (self.goalcurrent.absmin + self.goalcurrent.absmax) * 0.5;
+
+ //if (self.bot_dodgevector_time < time)
+ {
+ // self.bot_dodgevector_time = time + cvar("bot_ai_dodgeupdateinterval");
+ // self.bot_dodgevector_jumpbutton = 1;
+ evadeobstacle = '0 0 0';
+ evadelava = '0 0 0';
+
+ if (self.waterlevel)
+ {
+ if(self.waterlevel>WATERLEVEL_SWIMMING)
+ {
+ // flatdir_z = 1;
+ self.aistatus |= AI_STATUS_OUT_WATER;
+ }
+ else
+ {
+ if(self.velocity.z >= 0 && !(self.watertype == CONTENT_WATER && gco.z < self.origin.z) &&
+ ( !(self.waterlevel == WATERLEVEL_WETFEET && self.watertype == CONTENT_WATER) || self.aistatus & AI_STATUS_OUT_WATER))
+ self.BUTTON_JUMP = true;
+ else
+ self.BUTTON_JUMP = false;
+ }
+ dir = normalize(flatdir);
+ makevectors(self.v_angle.y * '0 1 0');
+ }
+ else
+ {
+ if(self.aistatus & AI_STATUS_OUT_WATER)
+ self.aistatus &= ~AI_STATUS_OUT_WATER;
+
+ // jump if going toward an obstacle that doesn't look like stairs we
+ // can walk up directly
+ tracebox(self.origin, self.mins, self.maxs, self.origin + self.velocity * 0.2, false, self);
+ if (trace_fraction < 1)
+ if (trace_plane_normal.z < 0.7)
+ {
+ s = trace_fraction;
+ tracebox(self.origin + stepheightvec, self.mins, self.maxs, self.origin + self.velocity * 0.2 + stepheightvec, false, self);
+ if (trace_fraction < s + 0.01)
+ if (trace_plane_normal.z < 0.7)
+ {
+ s = trace_fraction;
+ tracebox(self.origin + jumpstepheightvec, self.mins, self.maxs, self.origin + self.velocity * 0.2 + jumpstepheightvec, false, self);
+ if (trace_fraction > s)
+ self.BUTTON_JUMP = 1;
+ }
+ }
+
+ // avoiding dangers and obstacles
+ vector dst_ahead, dst_down;
+ makevectors(self.v_angle.y * '0 1 0');
+ dst_ahead = self.origin + self.view_ofs + (self.velocity * 0.4) + (v_forward * 32 * 3);
+ dst_down = dst_ahead - '0 0 1500';
+
+ // Look ahead
+ traceline(self.origin + self.view_ofs, dst_ahead, true, world);
+
+ // Check head-banging against walls
+ if(vlen(self.origin + self.view_ofs - trace_endpos) < 25 && !(self.aistatus & AI_STATUS_OUT_WATER))
+ {
+ self.BUTTON_JUMP = true;
+ if(self.facingwalltime && time > self.facingwalltime)
+ {
+ self.ignoregoal = self.goalcurrent;
+ self.ignoregoaltime = time + autocvar_bot_ai_ignoregoal_timeout;
+ self.bot_strategytime = 0;
+ return;
+ }
+ else
+ {
+ self.facingwalltime = time + 0.05;
+ }
+ }
+ else
+ {
+ self.facingwalltime = 0;
+
+ if(self.ignoregoal != world && time > self.ignoregoaltime)
+ {
+ self.ignoregoal = world;
+ self.ignoregoaltime = 0;
+ }
+ }
+
+ // Check for water/slime/lava and dangerous edges
+ // (only when the bot is on the ground or jumping intentionally)
+ self.aistatus &= ~AI_STATUS_DANGER_AHEAD;
+
+ if(trace_fraction == 1 && self.jumppadcount == 0 && !self.goalcurrent.wphardwired )
+ if((self.flags & FL_ONGROUND) || (self.aistatus & AI_STATUS_RUNNING) || self.BUTTON_JUMP == true)
+ {
+ // Look downwards
+ traceline(dst_ahead , dst_down, true, world);
+ // te_lightning2(world, self.origin, dst_ahead); // Draw "ahead" look
+ // te_lightning2(world, dst_ahead, dst_down); // Draw "downwards" look
+ if(trace_endpos.z < self.origin.z + self.mins.z)
+ {
+ s = pointcontents(trace_endpos + '0 0 1');
+ if (s != CONTENT_SOLID)
+ if (s == CONTENT_LAVA || s == CONTENT_SLIME)
+ evadelava = normalize(self.velocity) * -1;
+ else if (s == CONTENT_SKY)
+ evadeobstacle = normalize(self.velocity) * -1;
+ else if (!boxesoverlap(dst_ahead - self.view_ofs + self.mins, dst_ahead - self.view_ofs + self.maxs,
+ self.goalcurrent.absmin, self.goalcurrent.absmax))
+ {
+ // if ain't a safe goal with "holes" (like the jumpad on soylent)
+ // and there is a trigger_hurt below
+ if(tracebox_hits_trigger_hurt(dst_ahead, self.mins, self.maxs, trace_endpos))
+ {
+ // Remove dangerous dynamic goals from stack
+ LOG_TRACE("bot ", self.netname, " avoided the goal ", self.goalcurrent.classname, " ", etos(self.goalcurrent), " because it led to a dangerous path; goal stack cleared\n");
+ navigation_clearroute();
+ return;
+ }
+ }
+ }
+ }
+
+ dir = flatdir;
+ evadeobstacle.z = 0;
+ evadelava.z = 0;
+ makevectors(self.v_angle.y * '0 1 0');
+
+ if(evadeobstacle!='0 0 0'||evadelava!='0 0 0')
+ self.aistatus |= AI_STATUS_DANGER_AHEAD;
+ }
+
+ dodge = havocbot_dodge();
+ dodge = dodge * bound(0,0.5+(skill+self.bot_dodgeskill)*0.1,1);
+ evadelava = evadelava * bound(1,3-(skill+self.bot_dodgeskill),3); //Noobs fear lava a lot and take more distance from it
+ traceline(self.origin, ( ( self.enemy.absmin + self.enemy.absmax ) * 0.5 ), true, world);
+ if(IS_PLAYER(trace_ent))
+ dir = dir * bound(0,(skill+self.bot_dodgeskill)/7,1);
+
+ dir = normalize(dir + dodge + evadeobstacle + evadelava);
+ // self.bot_dodgevector = dir;
+ // self.bot_dodgevector_jumpbutton = self.BUTTON_JUMP;
+ }
+
+ if(time < self.ladder_time)
+ {
+ if(self.goalcurrent.origin.z + self.goalcurrent.mins.z > self.origin.z + self.mins.z)
+ {
+ if(self.origin.z + self.mins.z < self.ladder_entity.origin.z + self.ladder_entity.maxs.z)
+ dir.z = 1;
+ }
+ else
+ {
+ if(self.origin.z + self.mins.z > self.ladder_entity.origin.z + self.ladder_entity.mins.z)
+ dir.z = -1;
+ }
+ }
+
+ //dir = self.bot_dodgevector;
+ //if (self.bot_dodgevector_jumpbutton)
+ // self.BUTTON_JUMP = 1;
+ self.movement_x = dir * v_forward * maxspeed;
+ self.movement_y = dir * v_right * maxspeed;
+ self.movement_z = dir * v_up * maxspeed;
+
+ // Emulate keyboard interface
+ if (skill < 10)
+ havocbot_keyboard_movement(destorg);
+
+ // Bunnyhop!
+// if(self.aistatus & AI_STATUS_ROAMING)
+ if(self.goalcurrent)
+ if(skill+self.bot_moveskill >= autocvar_bot_ai_bunnyhop_skilloffset)
+ havocbot_bunnyhop(dir);
+
+ if ((dir * v_up) >= autocvar_sv_jumpvelocity*0.5 && (self.flags & FL_ONGROUND)) self.BUTTON_JUMP=1;
+ if (((dodge * v_up) > 0) && random()*frametime >= 0.2*bound(0,(10-skill-self.bot_dodgeskill)*0.1,1)) self.BUTTON_JUMP=true;
+ if (((dodge * v_up) < 0) && random()*frametime >= 0.5*bound(0,(10-skill-self.bot_dodgeskill)*0.1,1)) self.havocbot_ducktime=time+0.3/bound(0.1,skill+self.bot_dodgeskill,10);
+}
+
+void havocbot_chooseenemy()
+{SELFPARAM();
+ entity head, best, head2;
+ float rating, bestrating, hf;
+ vector eye, v;
+ if (autocvar_bot_nofire || IS_INDEPENDENT_PLAYER(self))
+ {
+ self.enemy = world;
+ return;
+ }
+ if (self.enemy)
+ {
+ if (!bot_shouldattack(self.enemy))
+ {
+ // enemy died or something, find a new target
+ self.enemy = world;
+ self.havocbot_chooseenemy_finished = time;
+ }
+ else if (self.havocbot_stickenemy)
+ {
+ // tracking last chosen enemy
+ // if enemy is visible
+ // and not really really far away
+ // and we're not severely injured
+ // then keep tracking for a half second into the future
+ traceline(self.origin+self.view_ofs, ( self.enemy.absmin + self.enemy.absmax ) * 0.5,false,world);
+ if (trace_ent == self.enemy || trace_fraction == 1)
+ if (vlen((( self.enemy.absmin + self.enemy.absmax ) * 0.5) - self.origin) < 1000)
+ if (self.health > 30)
+ {
+ // remain tracking him for a shot while (case he went after a small corner or pilar
+ self.havocbot_chooseenemy_finished = time + 0.5;
+ return;
+ }
+ // enemy isn't visible, or is far away, or we're injured severely
+ // so stop preferring this enemy
+ // (it will still take a half second until a new one is chosen)
+ self.havocbot_stickenemy = 0;
+ }
+ }
+ if (time < self.havocbot_chooseenemy_finished)
+ return;
+ self.havocbot_chooseenemy_finished = time + autocvar_bot_ai_enemydetectioninterval;
+ eye = self.origin + self.view_ofs;
+ best = world;
+ bestrating = 100000000;
+ head = head2 = findchainfloat(bot_attack, true);
+
+ // Backup hit flags
+ hf = self.dphitcontentsmask;
+
+ // Search for enemies, if no enemy can be seen directly try to look through transparent objects
+
+ self.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_CORPSE;
+
+ bool scan_transparent = false;
+ bool scan_secondary_targets = false;
+ bool have_secondary_targets = false;
+ while(true)
+ {
+ scan_secondary_targets = false;
+ :scan_targets
+ for( ; head; head = head.chain)
+ {
+ if(!scan_secondary_targets)
+ {
+ if(head.classname == "misc_breakablemodel")
+ {
+ have_secondary_targets = true;
+ continue;
+ }
+ }
+ else
+ {
+ if(head.classname != "misc_breakablemodel")
+ continue;
+ }
+
+ v = (head.absmin + head.absmax) * 0.5;
+ rating = vlen(v - eye);
+ if (rating<autocvar_bot_ai_enemydetectionradius)
+ if (bestrating > rating)
+ if (bot_shouldattack(head))
+ {
+ traceline(eye, v, true, self);
+ if (trace_ent == head || trace_fraction >= 1)
+ {
+ best = head;
+ bestrating = rating;
+ }
+ }
+ }
+
+ if(!best && have_secondary_targets && !scan_secondary_targets)
+ {
+ scan_secondary_targets = true;
+ // restart the loop
+ head = head2;
+ bestrating = 100000000;
+ goto scan_targets;
+ }
+
+ // I want to do a second scan if no enemy was found or I don't have weapons
+ // TODO: Perform the scan when using the rifle (requires changes on the rifle code)
+ if(best || self.weapons) // || self.weapon == WEP_RIFLE.m_id
+ break;
+ if(scan_transparent)
+ break;
+
+ // Set flags to see through transparent objects
+ self.dphitcontentsmask |= DPCONTENTS_OPAQUE;
+
+ head = head2;
+ scan_transparent = true;
+ }
+
+ // Restore hit flags
+ self.dphitcontentsmask = hf;
+
+ self.enemy = best;
+ self.havocbot_stickenemy = true;
+ if(best && best.classname == "misc_breakablemodel")
+ self.havocbot_stickenemy = false;
+}
+
+float havocbot_chooseweapon_checkreload(int new_weapon)
+{SELFPARAM();
+ // bots under this skill cannot find unloaded weapons to reload idly when not in combat,
+ // so skip this for them, or they'll never get to reload their weapons at all.
+ // this also allows bots under this skill to be more stupid, and reload more often during combat :)
+ if(skill < 5)
+ return false;
+
+ // if this weapon is scheduled for reloading, don't switch to it during combat
+ if (self.weapon_load[new_weapon] < 0)
+ {
+ float i, other_weapon_available = false;
+ for(i = WEP_FIRST; i <= WEP_LAST; ++i)
+ {
+ Weapon w = get_weaponinfo(i);
+ // if we are out of ammo for all other weapons, it's an emergency to switch to anything else
+ if (w.wr_checkammo1(w) + w.wr_checkammo2(w))
+ other_weapon_available = true;
+ }
+ if(other_weapon_available)
+ return true;
+ }
+
+ return false;
+}
+
+void havocbot_chooseweapon()
+{SELFPARAM();
+ int i;
+
+ // ;)
+ if(g_weaponarena_weapons == WEPSET(TUBA))
+ {
+ self.switchweapon = WEP_TUBA.m_id;
+ return;
+ }
+
+ // TODO: clean this up by moving it to weapon code
+ if(self.enemy==world)
+ {
+ // If no weapon was chosen get the first available weapon
+ if(self.weapon==0)
+ for(i = WEP_FIRST; i <= WEP_LAST; ++i) if(i != WEP_BLASTER.m_id)
+ {
+ if(client_hasweapon(self, i, true, false))
+ {
+ self.switchweapon = i;
+ return;
+ }
+ }
+ return;
+ }
+
+ // Do not change weapon during the next second after a combo
+ float f = time - self.lastcombotime;
+ if(f < 1)
+ return;
+
+ float w;
+ float distance; distance=bound(10,vlen(self.origin-self.enemy.origin)-200,10000);
+
+ // Should it do a weapon combo?
+ float af, ct, combo_time, combo;
+
+ af = ATTACK_FINISHED(self);
+ ct = autocvar_bot_ai_weapon_combo_threshold;
+
+ // Bots with no skill will be 4 times more slower than "godlike" bots when doing weapon combos
+ // Ideally this 4 should be calculated as longest_weapon_refire / bot_ai_weapon_combo_threshold
+ combo_time = time + ct + (ct * ((-0.3*(skill+self.bot_weaponskill))+3));
+
+ combo = false;
+
+ if(autocvar_bot_ai_weapon_combo)
+ if(self.weapon == self.lastfiredweapon)
+ if(af > combo_time)
+ {
+ combo = true;
+ self.lastcombotime = time;
+ }
+
+ distance *= pow(2, self.bot_rangepreference);
+
+ // Custom weapon list based on distance to the enemy
+ if(bot_custom_weapon){
+
+ // Choose weapons for far distance
+ if ( distance > bot_distance_far ) {
+ for(i=0; i < Weapons_COUNT && bot_weapons_far[i] != -1 ; ++i){
+ w = bot_weapons_far[i];
+ if ( client_hasweapon(self, w, true, false) )
+ {
+ if ((self.weapon == w && combo) || havocbot_chooseweapon_checkreload(w))
+ continue;
+ self.switchweapon = w;
+ return;
+ }
+ }
+ }
+
+ // Choose weapons for mid distance
+ if ( distance > bot_distance_close) {
+ for(i=0; i < Weapons_COUNT && bot_weapons_mid[i] != -1 ; ++i){
+ w = bot_weapons_mid[i];
+ if ( client_hasweapon(self, w, true, false) )
+ {
+ if ((self.weapon == w && combo) || havocbot_chooseweapon_checkreload(w))
+ continue;
+ self.switchweapon = w;
+ return;
+ }
+ }
+ }
+
+ // Choose weapons for close distance
+ for(i=0; i < Weapons_COUNT && bot_weapons_close[i] != -1 ; ++i){
+ w = bot_weapons_close[i];
+ if ( client_hasweapon(self, w, true, false) )
+ {
+ if ((self.weapon == w && combo) || havocbot_chooseweapon_checkreload(w))
+ continue;
+ self.switchweapon = w;
+ return;
+ }
+ }
+ }
+}
+
+void havocbot_aim()
+{SELFPARAM();
+ vector selfvel, enemyvel;
+// if(self.flags & FL_INWATER)
+// return;
+ if (time < self.nextaim)
+ return;
+ self.nextaim = time + 0.1;
+ selfvel = self.velocity;
+ if (!self.waterlevel)
+ selfvel.z = 0;
+ if (self.enemy)
+ {
+ enemyvel = self.enemy.velocity;
+ if (!self.enemy.waterlevel)
+ enemyvel.z = 0;
+ lag_additem(time + self.ping, 0, 0, self.enemy, self.origin, selfvel, (self.enemy.absmin + self.enemy.absmax) * 0.5, enemyvel);
+ }
+ else
+ lag_additem(time + self.ping, 0, 0, world, self.origin, selfvel, ( self.goalcurrent.absmin + self.goalcurrent.absmax ) * 0.5, '0 0 0');
+}
+
+float havocbot_moveto_refresh_route()
+{SELFPARAM();
+ // Refresh path to goal if necessary
+ entity wp;
+ wp = self.havocbot_personal_waypoint;
+ navigation_goalrating_start();
+ navigation_routerating(wp, 10000, 10000);
+ navigation_goalrating_end();
+ return self.navigation_hasgoals;
+}
+
+float havocbot_moveto(vector pos)
+{SELFPARAM();
+ entity wp;
+
+ if(self.aistatus & AI_STATUS_WAYPOINT_PERSONAL_GOING)
+ {
+ // Step 4: Move to waypoint
+ if(self.havocbot_personal_waypoint==world)
+ {
+ LOG_TRACE("Error: ", self.netname, " trying to walk to a non existent personal waypoint\n");
+ self.aistatus &= ~AI_STATUS_WAYPOINT_PERSONAL_GOING;
+ return CMD_STATUS_ERROR;
+ }
+
+ if (!bot_strategytoken_taken)
+ if(self.havocbot_personal_waypoint_searchtime<time)
+ {
+ bot_strategytoken_taken = true;
+ if(havocbot_moveto_refresh_route())
+ {
+ LOG_TRACE(self.netname, " walking to its personal waypoint (after ", ftos(self.havocbot_personal_waypoint_failcounter), " failed attempts)\n");
+ self.havocbot_personal_waypoint_searchtime = time + 10;
+ self.havocbot_personal_waypoint_failcounter = 0;
+ }
+ else
+ {
+ self.havocbot_personal_waypoint_failcounter += 1;
+ self.havocbot_personal_waypoint_searchtime = time + 2;
+ if(self.havocbot_personal_waypoint_failcounter >= 30)
+ {
+ LOG_TRACE("Warning: can't walk to the personal waypoint located at ", vtos(self.havocbot_personal_waypoint.origin),"\n");
+ self.aistatus &= ~AI_STATUS_WAYPOINT_PERSONAL_LINKING;
+ remove(self.havocbot_personal_waypoint);
+ return CMD_STATUS_ERROR;
+ }
+ else
+ LOG_TRACE(self.netname, " can't walk to its personal waypoint (after ", ftos(self.havocbot_personal_waypoint_failcounter), " failed attempts), trying later\n");
+ }
+ }
+
+ if(autocvar_bot_debug_goalstack)
+ debuggoalstack();
+
+ // Heading
+ vector dir = ( ( self.goalcurrent.absmin + self.goalcurrent.absmax ) * 0.5 ) - (self.origin + self.view_ofs);
+ dir.z = 0;
+ bot_aimdir(dir, -1);
+
+ // Go!
+ havocbot_movetogoal();
+
+ if(self.aistatus & AI_STATUS_WAYPOINT_PERSONAL_REACHED)
+ {
+ // Step 5: Waypoint reached
+ LOG_TRACE(self.netname, "'s personal waypoint reached\n");
+ remove(self.havocbot_personal_waypoint);
+ self.aistatus &= ~AI_STATUS_WAYPOINT_PERSONAL_REACHED;
+ return CMD_STATUS_FINISHED;
+ }
+
+ return CMD_STATUS_EXECUTING;
+ }
+
+ // Step 2: Linking waypoint
+ if(self.aistatus & AI_STATUS_WAYPOINT_PERSONAL_LINKING)
+ {
+ // Wait until it is linked
+ if(!self.havocbot_personal_waypoint.wplinked)
+ {
+ LOG_TRACE(self.netname, " waiting for personal waypoint to be linked\n");
+ return CMD_STATUS_EXECUTING;
+ }
+
+ self.havocbot_personal_waypoint_searchtime = time; // so we set the route next frame
+ self.aistatus &= ~AI_STATUS_WAYPOINT_PERSONAL_LINKING;
+ self.aistatus |= AI_STATUS_WAYPOINT_PERSONAL_GOING;
+
+ // Step 3: Route to waypoint
+ LOG_TRACE(self.netname, " walking to its personal waypoint\n");
+
+ return CMD_STATUS_EXECUTING;
+ }
+
+ // Step 1: Spawning waypoint
+ wp = waypoint_spawnpersonal(pos);
+ if(wp==world)
+ {
+ LOG_TRACE("Error: Can't spawn personal waypoint at ",vtos(pos),"\n");
+ return CMD_STATUS_ERROR;
+ }
+
+ self.havocbot_personal_waypoint = wp;
+ self.havocbot_personal_waypoint_failcounter = 0;
+ self.aistatus |= AI_STATUS_WAYPOINT_PERSONAL_LINKING;
+
+ // if pos is inside a teleport, then let's mark it as teleport waypoint
+ entity head;
+ for(head = world; (head = find(head, classname, "trigger_teleport")); )
+ {
+ if(WarpZoneLib_BoxTouchesBrush(pos, pos, head, world))
+ {
+ wp.wpflags |= WAYPOINTFLAG_TELEPORT;
+ self.lastteleporttime = 0;
+ }
+ }
+
+/*
+ if(wp.wpflags & WAYPOINTFLAG_TELEPORT)
+ print("routing to a teleporter\n");
+ else
+ print("routing to a non-teleporter\n");
+*/
+
+ return CMD_STATUS_EXECUTING;
+}
+
+float havocbot_resetgoal()
+{
+ navigation_clearroute();
+ return CMD_STATUS_FINISHED;
+}
+
+void havocbot_setupbot()
+{SELFPARAM();
+ self.bot_ai = havocbot_ai;
+ self.cmd_moveto = havocbot_moveto;
+ self.cmd_resetgoal = havocbot_resetgoal;
+
+ havocbot_chooserole();
+}
+
+vector havocbot_dodge()
+{SELFPARAM();
+ // LordHavoc: disabled because this is too expensive
+ return '0 0 0';
+#if 0
+ entity head;
+ vector dodge, v, n;
+ float danger, bestdanger, vl, d;
+ dodge = '0 0 0';
+ bestdanger = -20;
+ // check for dangerous objects near bot or approaching bot
+ head = findchainfloat(bot_dodge, true);
+ while(head)
+ {
+ if (head.owner != self)
+ {
+ vl = vlen(head.velocity);
+ if (vl > autocvar_sv_maxspeed * 0.3)
+ {
+ n = normalize(head.velocity);
+ v = self.origin - head.origin;
+ d = v * n;
+ if (d > (0 - head.bot_dodgerating))
+ if (d < (vl * 0.2 + head.bot_dodgerating))
+ {
+ // calculate direction and distance from the flight path, by removing the forward axis
+ v = v - (n * (v * n));
+ danger = head.bot_dodgerating - vlen(v);
+ if (bestdanger < danger)
+ {
+ bestdanger = danger;
+ // dodge to the side of the object
+ dodge = normalize(v);
+ }
+ }
+ }
+ else
+ {
+ danger = head.bot_dodgerating - vlen(head.origin - self.origin);
+ if (bestdanger < danger)
+ {
+ bestdanger = danger;
+ dodge = normalize(self.origin - head.origin);
+ }
+ }
+ }
+ head = head.chain;
+ }
+ return dodge;
+#endif
+}
--- /dev/null
+#ifndef HAVOCBOT_H
+#define HAVOCBOT_H
+
+/*
+ * Globals and Fields
+ */
+
+.float havocbot_keyboardskill;
+.float facingwalltime, ignoregoaltime;
+.float lastfiredweapon;
+.float lastcombotime;
+.float havocbot_blockhead;
+
+.float havocbot_keyboardtime;
+.float havocbot_ducktime;
+.float bot_timelastseengoal;
+.float bot_canruntogoal;
+.float bot_chooseweapontime;
+.float rocketjumptime;
+.float nextaim;
+.float havocbot_personal_waypoint_searchtime;
+.float havocbot_personal_waypoint_failcounter;
+.float havocbot_chooseenemy_finished;
+.float havocbot_stickenemy;
+.float havocbot_role_timeout;
+
+.entity ignoregoal;
+.entity bot_lastseengoal;
+.entity havocbot_personal_waypoint;
+
+.vector havocbot_keyboard;
+
+/*
+ * Functions
+ */
+
+void havocbot_ai();
+void havocbot_aim();
+void havocbot_setupbot();
+void havocbot_movetogoal();
+void havocbot_chooserole();
+void havocbot_chooseenemy();
+void havocbot_chooseweapon();
+void havocbot_bunnyhop(vector dir);
+void havocbot_keyboard_movement(vector destorg);
+
+float havocbot_resetgoal();
+float havocbot_moveto(vector pos);
+float havocbot_moveto_refresh_route();
+
+vector havocbot_dodge();
+
+.void() havocbot_role;
+.void() havocbot_previous_role;
+
+void(float ratingscale, vector org, float sradius) havocbot_goalrating_items;
+void(float ratingscale, vector org, float sradius) havocbot_goalrating_enemyplayers;
+
+/*
+ * Imports
+ */
+
+.entity draggedby;
+.float ladder_time;
+.entity ladder_entity;
+#endif
--- /dev/null
+#include "role_keyhunt.qh"
+
+void() havocbot_role_kh_carrier;
+void() havocbot_role_kh_defense;
+void() havocbot_role_kh_offense;
+void() havocbot_role_kh_freelancer;
+
+
+void havocbot_goalrating_kh(float ratingscale_team, float ratingscale_dropped, float ratingscale_enemy)
+{SELFPARAM();
+ entity head;
+ for (head = kh_worldkeylist; head; head = head.kh_worldkeynext)
+ {
+ if(head.owner == self)
+ continue;
+ if(!kh_tracking_enabled)
+ {
+ // if it's carried by our team we know about it
+ // otherwise we have to see it to know about it
+ if(!head.owner || head.team != self.team)
+ {
+ traceline(self.origin + self.view_ofs, head.origin, MOVE_NOMONSTERS, self);
+ if (trace_fraction < 1 && trace_ent != head)
+ continue; // skip what I can't see
+ }
+ }
+ if(!head.owner)
+ navigation_routerating(head, ratingscale_dropped * BOT_PICKUP_RATING_HIGH, 100000);
+ else if(head.team == self.team)
+ navigation_routerating(head.owner, ratingscale_team * BOT_PICKUP_RATING_HIGH, 100000);
+ else
+ navigation_routerating(head.owner, ratingscale_enemy * BOT_PICKUP_RATING_HIGH, 100000);
+ }
+
+ havocbot_goalrating_items(1, self.origin, 10000);
+}
+
+void havocbot_role_kh_carrier()
+{SELFPARAM();
+ if(self.deadflag != DEAD_NO)
+ return;
+
+ if (!(self.kh_next))
+ {
+ LOG_TRACE("changing role to freelancer\n");
+ self.havocbot_role = havocbot_role_kh_freelancer;
+ self.havocbot_role_timeout = 0;
+ return;
+ }
+
+ if (self.bot_strategytime < time)
+ {
+ self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
+ navigation_goalrating_start();
+
+ if(kh_Key_AllOwnedByWhichTeam() == self.team)
+ havocbot_goalrating_kh(10, 0.1, 0.1); // bring home
+ else
+ havocbot_goalrating_kh(4, 4, 1); // play defensively
+
+ navigation_goalrating_end();
+ }
+}
+
+void havocbot_role_kh_defense()
+{SELFPARAM();
+ if(self.deadflag != DEAD_NO)
+ return;
+
+ if (self.kh_next)
+ {
+ LOG_TRACE("changing role to carrier\n");
+ self.havocbot_role = havocbot_role_kh_carrier;
+ self.havocbot_role_timeout = 0;
+ return;
+ }
+
+ if (!self.havocbot_role_timeout)
+ self.havocbot_role_timeout = time + random() * 10 + 20;
+ if (time > self.havocbot_role_timeout)
+ {
+ LOG_TRACE("changing role to freelancer\n");
+ self.havocbot_role = havocbot_role_kh_freelancer;
+ self.havocbot_role_timeout = 0;
+ return;
+ }
+
+ if (self.bot_strategytime < time)
+ {
+ float key_owner_team;
+ self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
+ navigation_goalrating_start();
+
+ key_owner_team = kh_Key_AllOwnedByWhichTeam();
+ if(key_owner_team == self.team)
+ havocbot_goalrating_kh(10, 0.1, 0.1); // defend key carriers
+ else if(key_owner_team == -1)
+ havocbot_goalrating_kh(4, 1, 0.1); // play defensively
+ else
+ havocbot_goalrating_kh(0.1, 0.1, 10); // ATTACK ANYWAY
+
+ navigation_goalrating_end();
+ }
+}
+
+void havocbot_role_kh_offense()
+{SELFPARAM();
+ if(self.deadflag != DEAD_NO)
+ return;
+
+ if (self.kh_next)
+ {
+ LOG_TRACE("changing role to carrier\n");
+ self.havocbot_role = havocbot_role_kh_carrier;
+ self.havocbot_role_timeout = 0;
+ return;
+ }
+
+ if (!self.havocbot_role_timeout)
+ self.havocbot_role_timeout = time + random() * 10 + 20;
+ if (time > self.havocbot_role_timeout)
+ {
+ LOG_TRACE("changing role to freelancer\n");
+ self.havocbot_role = havocbot_role_kh_freelancer;
+ self.havocbot_role_timeout = 0;
+ return;
+ }
+
+ if (self.bot_strategytime < time)
+ {
+ float key_owner_team;
+
+ self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
+ navigation_goalrating_start();
+
+ key_owner_team = kh_Key_AllOwnedByWhichTeam();
+ if(key_owner_team == self.team)
+ havocbot_goalrating_kh(10, 0.1, 0.1); // defend anyway
+ else if(key_owner_team == -1)
+ havocbot_goalrating_kh(0.1, 1, 4); // play offensively
+ else
+ havocbot_goalrating_kh(0.1, 0.1, 10); // ATTACK! EMERGENCY!
+
+ navigation_goalrating_end();
+ }
+}
+
+void havocbot_role_kh_freelancer()
+{SELFPARAM();
+ if(self.deadflag != DEAD_NO)
+ return;
+
+ if (self.kh_next)
+ {
+ LOG_TRACE("changing role to carrier\n");
+ self.havocbot_role = havocbot_role_kh_carrier;
+ self.havocbot_role_timeout = 0;
+ return;
+ }
+
+ if (!self.havocbot_role_timeout)
+ self.havocbot_role_timeout = time + random() * 10 + 10;
+ if (time > self.havocbot_role_timeout)
+ {
+ if (random() < 0.5)
+ {
+ LOG_TRACE("changing role to offense\n");
+ self.havocbot_role = havocbot_role_kh_offense;
+ }
+ else
+ {
+ LOG_TRACE("changing role to defense\n");
+ self.havocbot_role = havocbot_role_kh_defense;
+ }
+ self.havocbot_role_timeout = 0;
+ return;
+ }
+
+ if (self.bot_strategytime < time)
+ {
+ float key_owner_team;
+
+ self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
+ navigation_goalrating_start();
+
+ key_owner_team = kh_Key_AllOwnedByWhichTeam();
+ if(key_owner_team == self.team)
+ havocbot_goalrating_kh(10, 0.1, 0.1); // defend anyway
+ else if(key_owner_team == -1)
+ havocbot_goalrating_kh(1, 10, 4); // prefer dropped keys
+ else
+ havocbot_goalrating_kh(0.1, 0.1, 10); // ATTACK ANYWAY
+
+ navigation_goalrating_end();
+ }
+}
+
+void havocbot_chooserole_kh()
+{SELFPARAM();
+ float r;
+
+ if(self.deadflag != DEAD_NO)
+ return;
+
+ r = random() * 3;
+ if (r < 1)
+ self.havocbot_role = havocbot_role_kh_offense;
+ else if (r < 2)
+ self.havocbot_role = havocbot_role_kh_defense;
+ else
+ self.havocbot_role = havocbot_role_kh_freelancer;
+}
--- /dev/null
+#ifndef ROLE_KEYHUNT_H
+#define ROLE_KEYHUNT_H
+
+void havocbot_chooserole_kh();
+#endif
--- /dev/null
+#include "roles.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();
+}
--- /dev/null
+#ifndef ROLES_H
+#define ROLES_H
+void havocbot_goalrating_controlpoints(float ratingscale, vector org, float sradius);
+#endif
--- /dev/null
+#include "navigation.qh"
+
+#include "../common/triggers/trigger/jumppads.qh"
+
+void bot_debug(string input)
+{
+ switch(autocvar_bot_debug)
+ {
+ case 1: LOG_TRACE(input); break;
+ case 2: LOG_INFO(input); break;
+ }
+}
+
+// rough simulation of walking from one point to another to test if a path
+// can be traveled, used for waypoint linking and havocbot
+
+float tracewalk(entity e, vector start, vector m1, vector m2, vector end, float movemode)
+{SELFPARAM();
+ vector org;
+ vector move;
+ vector dir;
+ float dist;
+ float totaldist;
+ float stepdist;
+ float yaw;
+ float ignorehazards;
+ float swimming;
+
+ if(autocvar_bot_debug_tracewalk)
+ {
+ debugresetnodes();
+ debugnode(start);
+ }
+
+ move = end - start;
+ move.z = 0;
+ org = start;
+ dist = totaldist = vlen(move);
+ dir = normalize(move);
+ stepdist = 32;
+ ignorehazards = false;
+ swimming = false;
+
+ // Analyze starting point
+ traceline(start, start, MOVE_NORMAL, e);
+ if (trace_dpstartcontents & (DPCONTENTS_SLIME | DPCONTENTS_LAVA))
+ ignorehazards = true;
+ else
+ {
+ traceline( start, start + '0 0 -65536', MOVE_NORMAL, e);
+ if (trace_dpstartcontents & (DPCONTENTS_SLIME | DPCONTENTS_LAVA))
+ {
+ ignorehazards = true;
+ swimming = true;
+ }
+ }
+ tracebox(start, m1, m2, start, MOVE_NOMONSTERS, e);
+ if (trace_startsolid)
+ {
+ // Bad start
+ if(autocvar_bot_debug_tracewalk)
+ debugnodestatus(start, DEBUG_NODE_FAIL);
+
+ //print("tracewalk: ", vtos(start), " is a bad start\n");
+ return false;
+ }
+
+ // Movement loop
+ yaw = vectoyaw(move);
+ move = end - org;
+ for (;;)
+ {
+ if (boxesoverlap(end, end, org + m1 + '-1 -1 -1', org + m2 + '1 1 1'))
+ {
+ // Succeeded
+ if(autocvar_bot_debug_tracewalk)
+ debugnodestatus(org, DEBUG_NODE_SUCCESS);
+
+ //print("tracewalk: ", vtos(start), " can reach ", vtos(end), "\n");
+ return true;
+ }
+ if(autocvar_bot_debug_tracewalk)
+ debugnode(org);
+
+ if (dist <= 0)
+ break;
+ if (stepdist > dist)
+ stepdist = dist;
+ dist = dist - stepdist;
+ traceline(org, org, MOVE_NORMAL, e);
+ if (!ignorehazards)
+ {
+ if (trace_dpstartcontents & (DPCONTENTS_SLIME | DPCONTENTS_LAVA))
+ {
+ // hazards blocking path
+ if(autocvar_bot_debug_tracewalk)
+ debugnodestatus(org, DEBUG_NODE_FAIL);
+
+ //print("tracewalk: ", vtos(start), " hits a hazard when trying to reach ", vtos(end), "\n");
+ return false;
+ }
+ }
+ if (trace_dpstartcontents & DPCONTENTS_LIQUIDSMASK)
+ {
+ move = normalize(end - org);
+ tracebox(org, m1, m2, org + move * stepdist, movemode, e);
+
+ if(autocvar_bot_debug_tracewalk)
+ debugnode(trace_endpos);
+
+ if (trace_fraction < 1)
+ {
+ swimming = true;
+ org = trace_endpos - normalize(org - trace_endpos) * stepdist;
+ for (; org.z < end.z + self.maxs.z; org.z += stepdist)
+ {
+ if(autocvar_bot_debug_tracewalk)
+ debugnode(org);
+
+ if(pointcontents(org) == CONTENT_EMPTY)
+ break;
+ }
+
+ if(pointcontents(org + '0 0 1') != CONTENT_EMPTY)
+ {
+ if(autocvar_bot_debug_tracewalk)
+ debugnodestatus(org, DEBUG_NODE_FAIL);
+
+ return false;
+ //print("tracewalk: ", vtos(start), " failed under water\n");
+ }
+ continue;
+
+ }
+ else
+ org = trace_endpos;
+ }
+ else
+ {
+ move = dir * stepdist + org;
+ tracebox(org, m1, m2, move, movemode, e);
+
+ if(autocvar_bot_debug_tracewalk)
+ debugnode(trace_endpos);
+
+ // hit something
+ if (trace_fraction < 1)
+ {
+ // check if we can walk over this obstacle, possibly by jumpstepping
+ tracebox(org + stepheightvec, m1, m2, move + stepheightvec, movemode, e);
+ if (trace_fraction < 1 || trace_startsolid)
+ {
+ tracebox(org + jumpstepheightvec, m1, m2, move + jumpstepheightvec, movemode, e);
+ if (trace_fraction < 1 || trace_startsolid)
+ {
+ if(autocvar_bot_debug_tracewalk)
+ debugnodestatus(trace_endpos, DEBUG_NODE_WARNING);
+
+ // check for doors
+ traceline( org, move, movemode, e);
+ if ( trace_ent.classname == "door_rotating" || trace_ent.classname == "door")
+ {
+ vector nextmove;
+ move = trace_endpos;
+ while(trace_ent.classname == "door_rotating" || trace_ent.classname == "door")
+ {
+ nextmove = move + (dir * stepdist);
+ traceline( move, nextmove, movemode, e);
+ move = nextmove;
+ }
+ }
+ else
+ {
+ if(autocvar_bot_debug_tracewalk)
+ debugnodestatus(trace_endpos, DEBUG_NODE_FAIL);
+
+ //print("tracewalk: ", vtos(start), " hit something when trying to reach ", vtos(end), "\n");
+ //te_explosion(trace_endpos);
+ //print(ftos(e.dphitcontentsmask), "\n");
+ return false; // failed
+ }
+ }
+ else
+ move = trace_endpos;
+ }
+ else
+ move = trace_endpos;
+ }
+ else
+ move = trace_endpos;
+
+ // trace down from stepheight as far as possible and move there,
+ // if this starts in solid we try again without the stepup, and
+ // if that also fails we assume it is a wall
+ // (this is the same logic as the Quake walkmove function used)
+ tracebox(move, m1, m2, move + '0 0 -65536', movemode, e);
+
+ // moved successfully
+ if(swimming)
+ {
+ float c;
+ c = pointcontents(org + '0 0 1');
+ if (!(c == CONTENT_WATER || c == CONTENT_LAVA || c == CONTENT_SLIME))
+ swimming = false;
+ else
+ continue;
+ }
+
+ org = trace_endpos;
+ }
+ }
+
+ //print("tracewalk: ", vtos(start), " did not arrive at ", vtos(end), " but at ", vtos(org), "\n");
+
+ // moved but didn't arrive at the intended destination
+ if(autocvar_bot_debug_tracewalk)
+ debugnodestatus(org, DEBUG_NODE_FAIL);
+
+ return false;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// goal stack
+/////////////////////////////////////////////////////////////////////////////
+
+// completely empty the goal stack, used when deciding where to go
+void navigation_clearroute()
+{SELFPARAM();
+ //print("bot ", etos(self), " clear\n");
+ self.navigation_hasgoals = false;
+ self.goalcurrent = world;
+ self.goalstack01 = world;
+ self.goalstack02 = world;
+ self.goalstack03 = world;
+ self.goalstack04 = world;
+ self.goalstack05 = world;
+ self.goalstack06 = world;
+ self.goalstack07 = world;
+ self.goalstack08 = world;
+ self.goalstack09 = world;
+ self.goalstack10 = world;
+ self.goalstack11 = world;
+ self.goalstack12 = world;
+ self.goalstack13 = world;
+ self.goalstack14 = world;
+ self.goalstack15 = world;
+ self.goalstack16 = world;
+ self.goalstack17 = world;
+ self.goalstack18 = world;
+ self.goalstack19 = world;
+ self.goalstack20 = world;
+ self.goalstack21 = world;
+ self.goalstack22 = world;
+ self.goalstack23 = world;
+ self.goalstack24 = world;
+ self.goalstack25 = world;
+ self.goalstack26 = world;
+ self.goalstack27 = world;
+ self.goalstack28 = world;
+ self.goalstack29 = world;
+ self.goalstack30 = world;
+ self.goalstack31 = world;
+}
+
+// add a new goal at the beginning of the stack
+// (in other words: add a new prerequisite before going to the later goals)
+// NOTE: when a waypoint is added, the WP gets pushed first, then the
+// next-closest WP on the shortest path to the WP
+// That means, if the stack overflows, the bot will know how to do the FIRST 32
+// steps to the goal, and then recalculate the path.
+void navigation_pushroute(entity e)
+{SELFPARAM();
+ //print("bot ", etos(self), " push ", etos(e), "\n");
+ self.goalstack31 = self.goalstack30;
+ self.goalstack30 = self.goalstack29;
+ self.goalstack29 = self.goalstack28;
+ self.goalstack28 = self.goalstack27;
+ self.goalstack27 = self.goalstack26;
+ self.goalstack26 = self.goalstack25;
+ self.goalstack25 = self.goalstack24;
+ self.goalstack24 = self.goalstack23;
+ self.goalstack23 = self.goalstack22;
+ self.goalstack22 = self.goalstack21;
+ self.goalstack21 = self.goalstack20;
+ self.goalstack20 = self.goalstack19;
+ self.goalstack19 = self.goalstack18;
+ self.goalstack18 = self.goalstack17;
+ self.goalstack17 = self.goalstack16;
+ self.goalstack16 = self.goalstack15;
+ self.goalstack15 = self.goalstack14;
+ self.goalstack14 = self.goalstack13;
+ self.goalstack13 = self.goalstack12;
+ self.goalstack12 = self.goalstack11;
+ self.goalstack11 = self.goalstack10;
+ self.goalstack10 = self.goalstack09;
+ self.goalstack09 = self.goalstack08;
+ self.goalstack08 = self.goalstack07;
+ self.goalstack07 = self.goalstack06;
+ self.goalstack06 = self.goalstack05;
+ self.goalstack05 = self.goalstack04;
+ self.goalstack04 = self.goalstack03;
+ self.goalstack03 = self.goalstack02;
+ self.goalstack02 = self.goalstack01;
+ self.goalstack01 = self.goalcurrent;
+ self.goalcurrent = e;
+}
+
+// remove first goal from stack
+// (in other words: remove a prerequisite for reaching the later goals)
+// (used when a spawnfunc_waypoint is reached)
+void navigation_poproute()
+{SELFPARAM();
+ //print("bot ", etos(self), " pop\n");
+ self.goalcurrent = self.goalstack01;
+ self.goalstack01 = self.goalstack02;
+ self.goalstack02 = self.goalstack03;
+ self.goalstack03 = self.goalstack04;
+ self.goalstack04 = self.goalstack05;
+ self.goalstack05 = self.goalstack06;
+ self.goalstack06 = self.goalstack07;
+ self.goalstack07 = self.goalstack08;
+ self.goalstack08 = self.goalstack09;
+ self.goalstack09 = self.goalstack10;
+ self.goalstack10 = self.goalstack11;
+ self.goalstack11 = self.goalstack12;
+ self.goalstack12 = self.goalstack13;
+ self.goalstack13 = self.goalstack14;
+ self.goalstack14 = self.goalstack15;
+ self.goalstack15 = self.goalstack16;
+ self.goalstack16 = self.goalstack17;
+ self.goalstack17 = self.goalstack18;
+ self.goalstack18 = self.goalstack19;
+ self.goalstack19 = self.goalstack20;
+ self.goalstack20 = self.goalstack21;
+ self.goalstack21 = self.goalstack22;
+ self.goalstack22 = self.goalstack23;
+ self.goalstack23 = self.goalstack24;
+ self.goalstack24 = self.goalstack25;
+ self.goalstack25 = self.goalstack26;
+ self.goalstack26 = self.goalstack27;
+ self.goalstack27 = self.goalstack28;
+ self.goalstack28 = self.goalstack29;
+ self.goalstack29 = self.goalstack30;
+ self.goalstack30 = self.goalstack31;
+ self.goalstack31 = world;
+}
+
+float navigation_waypoint_will_link(vector v, vector org, entity ent, float walkfromwp, float bestdist)
+{
+ float dist;
+ dist = vlen(v - org);
+ if (bestdist > dist)
+ {
+ traceline(v, org, true, ent);
+ if (trace_fraction == 1)
+ {
+ if (walkfromwp)
+ {
+ if (tracewalk(ent, v, PL_MIN, PL_MAX, org, bot_navigation_movemode))
+ return true;
+ }
+ else
+ {
+ if (tracewalk(ent, org, PL_MIN, PL_MAX, v, bot_navigation_movemode))
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+// find the spawnfunc_waypoint near a dynamic goal such as a dropped weapon
+entity navigation_findnearestwaypoint_withdist_except(entity ent, float walkfromwp, float bestdist, entity except)
+{
+ entity waylist, w, best;
+ vector v, org, pm1, pm2;
+
+ pm1 = ent.origin + ent.mins;
+ pm2 = ent.origin + ent.maxs;
+ waylist = findchain(classname, "waypoint");
+
+ // do two scans, because box test is cheaper
+ w = waylist;
+ while (w)
+ {
+ // if object is touching spawnfunc_waypoint
+ if(w != ent && w != except)
+ if (boxesoverlap(pm1, pm2, w.absmin, w.absmax))
+ return w;
+ w = w.chain;
+ }
+
+ org = ent.origin + 0.5 * (ent.mins + ent.maxs);
+ org.z = ent.origin.z + ent.mins.z - PL_MIN.z; // player height
+ // TODO possibly make other code have the same support for bboxes
+ if(ent.tag_entity)
+ org = org + ent.tag_entity.origin;
+ if (navigation_testtracewalk)
+ te_plasmaburn(org);
+
+ best = world;
+
+ // box check failed, try walk
+ w = waylist;
+ while (w)
+ {
+ // if object can walk from spawnfunc_waypoint
+ if(w != ent)
+ {
+ if (w.wpisbox)
+ {
+ vector wm1, wm2;
+ wm1 = w.origin + w.mins;
+ wm2 = w.origin + w.maxs;
+ v.x = bound(wm1_x, org.x, wm2_x);
+ v.y = bound(wm1_y, org.y, wm2_y);
+ v.z = bound(wm1_z, org.z, wm2_z);
+ }
+ else
+ v = w.origin;
+ if(navigation_waypoint_will_link(v, org, ent, walkfromwp, bestdist))
+ {
+ bestdist = vlen(v - org);
+ best = w;
+ }
+ }
+ w = w.chain;
+ }
+ return best;
+}
+entity navigation_findnearestwaypoint(entity ent, float walkfromwp)
+{
+ entity wp = navigation_findnearestwaypoint_withdist_except(ent, walkfromwp, 1050, world);
+ if (autocvar_g_waypointeditor_auto)
+ {
+ entity wp2 = navigation_findnearestwaypoint_withdist_except(ent, walkfromwp, 1050, wp);
+ if (wp && !wp2)
+ wp.wpflags |= WAYPOINTFLAG_PROTECTED;
+ }
+ return wp;
+}
+
+// finds the waypoints near the bot initiating a navigation query
+float navigation_markroutes_nearestwaypoints(entity waylist, float maxdist)
+{SELFPARAM();
+ entity head;
+ vector v, m1, m2, diff;
+ float c;
+// navigation_testtracewalk = true;
+ c = 0;
+ head = waylist;
+ while (head)
+ {
+ if (!head.wpconsidered)
+ {
+ if (head.wpisbox)
+ {
+ m1 = head.origin + head.mins;
+ m2 = head.origin + head.maxs;
+ v = self.origin;
+ v.x = bound(m1_x, v.x, m2_x);
+ v.y = bound(m1_y, v.y, m2_y);
+ v.z = bound(m1_z, v.z, m2_z);
+ }
+ else
+ v = head.origin;
+ diff = v - self.origin;
+ diff.z = max(0, diff.z);
+ if (vlen(diff) < maxdist)
+ {
+ head.wpconsidered = true;
+ if (tracewalk(self, self.origin, self.mins, self.maxs, v, bot_navigation_movemode))
+ {
+ head.wpnearestpoint = v;
+ head.wpcost = vlen(v - self.origin) + head.dmg;
+ head.wpfire = 1;
+ head.enemy = world;
+ c = c + 1;
+ }
+ }
+ }
+ head = head.chain;
+ }
+ //navigation_testtracewalk = false;
+ return c;
+}
+
+// updates a path link if a spawnfunc_waypoint link is better than the current one
+void navigation_markroutes_checkwaypoint(entity w, entity wp, float cost2, vector p)
+{
+ vector m1;
+ vector m2;
+ vector v;
+ if (wp.wpisbox)
+ {
+ m1 = wp.absmin;
+ m2 = wp.absmax;
+ v.x = bound(m1_x, p.x, m2_x);
+ v.y = bound(m1_y, p.y, m2_y);
+ v.z = bound(m1_z, p.z, m2_z);
+ }
+ else
+ v = wp.origin;
+ cost2 = cost2 + vlen(v - p);
+ if (wp.wpcost > cost2)
+ {
+ wp.wpcost = cost2;
+ wp.enemy = w;
+ wp.wpfire = 1;
+ wp.wpnearestpoint = v;
+ }
+}
+
+// queries the entire spawnfunc_waypoint network for pathes leading away from the bot
+void navigation_markroutes(entity fixed_source_waypoint)
+{SELFPARAM();
+ entity w, wp, waylist;
+ float searching, cost, cost2;
+ vector p;
+ w = waylist = findchain(classname, "waypoint");
+ while (w)
+ {
+ w.wpconsidered = false;
+ w.wpnearestpoint = '0 0 0';
+ w.wpcost = 10000000;
+ w.wpfire = 0;
+ w.enemy = world;
+ w = w.chain;
+ }
+
+ if(fixed_source_waypoint)
+ {
+ fixed_source_waypoint.wpconsidered = true;
+ fixed_source_waypoint.wpnearestpoint = fixed_source_waypoint.origin + 0.5 * (fixed_source_waypoint.mins + fixed_source_waypoint.maxs);
+ fixed_source_waypoint.wpcost = fixed_source_waypoint.dmg;
+ fixed_source_waypoint.wpfire = 1;
+ fixed_source_waypoint.enemy = world;
+ }
+ else
+ {
+ // try a short range search for the nearest waypoints, and expand the search repeatedly if none are found
+ // as this search is expensive we will use lower values if the bot is on the air
+ float i, increment, maxdistance;
+ if(self.flags & FL_ONGROUND)
+ {
+ increment = 750;
+ maxdistance = 50000;
+ }
+ else
+ {
+ increment = 500;
+ maxdistance = 1500;
+ }
+
+ for(i=increment;!navigation_markroutes_nearestwaypoints(waylist, i)&&i<maxdistance;i+=increment);
+ }
+
+ searching = true;
+ while (searching)
+ {
+ searching = false;
+ w = waylist;
+ while (w)
+ {
+ if (w.wpfire)
+ {
+ searching = true;
+ w.wpfire = 0;
+ cost = w.wpcost;
+ p = w.wpnearestpoint;
+ wp = w.wp00;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp00mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
+ wp = w.wp01;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp01mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
+ wp = w.wp02;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp02mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
+ wp = w.wp03;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp03mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
+ wp = w.wp04;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp04mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
+ wp = w.wp05;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp05mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
+ wp = w.wp06;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp06mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
+ wp = w.wp07;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp07mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
+ wp = w.wp08;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp08mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
+ wp = w.wp09;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp09mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
+ wp = w.wp10;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp10mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
+ wp = w.wp11;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp11mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
+ wp = w.wp12;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp12mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
+ wp = w.wp13;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp13mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
+ wp = w.wp14;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp14mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
+ wp = w.wp15;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp15mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
+ wp = w.wp16;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp16mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
+ wp = w.wp17;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp17mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
+ wp = w.wp18;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp18mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
+ wp = w.wp19;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp19mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
+ wp = w.wp20;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp20mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
+ wp = w.wp21;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp21mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
+ wp = w.wp22;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp22mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
+ wp = w.wp23;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp23mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
+ wp = w.wp24;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp24mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
+ wp = w.wp25;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp25mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
+ wp = w.wp26;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp26mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
+ wp = w.wp27;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp27mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
+ wp = w.wp28;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp28mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
+ wp = w.wp29;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp29mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
+ wp = w.wp30;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp30mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
+ wp = w.wp31;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp31mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
+ }}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
+ }
+ w = w.chain;
+ }
+ }
+}
+
+// queries the entire spawnfunc_waypoint network for pathes leading to the bot
+void navigation_markroutes_inverted(entity fixed_source_waypoint)
+{
+ entity w, wp, waylist;
+ float searching, cost, cost2;
+ vector p;
+ w = waylist = findchain(classname, "waypoint");
+ while (w)
+ {
+ w.wpconsidered = false;
+ w.wpnearestpoint = '0 0 0';
+ w.wpcost = 10000000;
+ w.wpfire = 0;
+ w.enemy = world;
+ w = w.chain;
+ }
+
+ if(fixed_source_waypoint)
+ {
+ fixed_source_waypoint.wpconsidered = true;
+ fixed_source_waypoint.wpnearestpoint = fixed_source_waypoint.origin + 0.5 * (fixed_source_waypoint.mins + fixed_source_waypoint.maxs);
+ fixed_source_waypoint.wpcost = fixed_source_waypoint.dmg; // the cost to get from X to fixed_source_waypoint
+ fixed_source_waypoint.wpfire = 1;
+ fixed_source_waypoint.enemy = world;
+ }
+ else
+ {
+ error("need to start with a waypoint\n");
+ }
+
+ searching = true;
+ while (searching)
+ {
+ searching = false;
+ w = waylist;
+ while (w)
+ {
+ if (w.wpfire)
+ {
+ searching = true;
+ w.wpfire = 0;
+ cost = w.wpcost; // cost to walk from w to home
+ p = w.wpnearestpoint;
+ for(wp = waylist; wp; wp = wp.chain)
+ {
+ if(w != wp.wp00) if(w != wp.wp01) if(w != wp.wp02) if(w != wp.wp03)
+ if(w != wp.wp04) if(w != wp.wp05) if(w != wp.wp06) if(w != wp.wp07)
+ if(w != wp.wp08) if(w != wp.wp09) if(w != wp.wp10) if(w != wp.wp11)
+ if(w != wp.wp12) if(w != wp.wp13) if(w != wp.wp14) if(w != wp.wp15)
+ if(w != wp.wp16) if(w != wp.wp17) if(w != wp.wp18) if(w != wp.wp19)
+ if(w != wp.wp20) if(w != wp.wp21) if(w != wp.wp22) if(w != wp.wp23)
+ if(w != wp.wp24) if(w != wp.wp25) if(w != wp.wp26) if(w != wp.wp27)
+ if(w != wp.wp28) if(w != wp.wp29) if(w != wp.wp30) if(w != wp.wp31)
+ continue;
+ cost2 = cost + wp.dmg;
+ navigation_markroutes_checkwaypoint(w, wp, cost2, p);
+ }
+ }
+ w = w.chain;
+ }
+ }
+}
+
+// updates the best goal according to a weighted calculation of travel cost and item value of a new proposed item
+void navigation_routerating(entity e, float f, float rangebias)
+{SELFPARAM();
+ entity nwp;
+ vector o;
+ if (!e)
+ return;
+
+ if(e.blacklisted)
+ return;
+
+ o = (e.absmin + e.absmax) * 0.5;
+
+ //print("routerating ", etos(e), " = ", ftos(f), " - ", ftos(rangebias), "\n");
+
+ // Evaluate path using jetpack
+ if(g_jetpack)
+ if(self.items & IT_JETPACK)
+ if(autocvar_bot_ai_navigation_jetpack)
+ if(vlen(self.origin - o) > autocvar_bot_ai_navigation_jetpack_mindistance)
+ {
+ vector pointa, pointb;
+
+ bot_debug(strcat("jetpack ai: evaluating path for ", e.classname, "\n"));
+
+ // Point A
+ traceline(self.origin, self.origin + '0 0 65535', MOVE_NORMAL, self);
+ pointa = trace_endpos - '0 0 1';
+
+ // Point B
+ traceline(o, o + '0 0 65535', MOVE_NORMAL, e);
+ pointb = trace_endpos - '0 0 1';
+
+ // Can I see these two points from the sky?
+ traceline(pointa, pointb, MOVE_NORMAL, self);
+
+ if(trace_fraction==1)
+ {
+ bot_debug("jetpack ai: can bridge these two points\n");
+
+ // Lower the altitude of these points as much as possible
+ float zdistance, xydistance, cost, t, fuel;
+ vector down, npa, npb;
+
+ down = '0 0 -1' * (PL_MAX.z - PL_MIN.z) * 10;
+
+ do{
+ npa = pointa + down;
+ npb = pointb + down;
+
+ if(npa.z<=self.absmax.z)
+ break;
+
+ if(npb.z<=e.absmax.z)
+ break;
+
+ traceline(npa, npb, MOVE_NORMAL, self);
+ if(trace_fraction==1)
+ {
+ pointa = npa;
+ pointb = npb;
+ }
+ }
+ while(trace_fraction == 1);
+
+
+ // Rough estimation of fuel consumption
+ // (ignores acceleration and current xyz velocity)
+ xydistance = vlen(pointa - pointb);
+ zdistance = fabs(pointa.z - self.origin.z);
+
+ t = zdistance / autocvar_g_jetpack_maxspeed_up;
+ t += xydistance / autocvar_g_jetpack_maxspeed_side;
+ fuel = t * autocvar_g_jetpack_fuel * 0.8;
+
+ bot_debug(strcat("jetpack ai: required fuel ", ftos(fuel), " self.ammo_fuel ", ftos(self.ammo_fuel), "\n"));
+
+ // enough fuel ?
+ if(self.ammo_fuel>fuel)
+ {
+ // Estimate cost
+ // (as onground costs calculation is mostly based on distances, here we do the same establishing some relationship
+ // - between air and ground speeds)
+
+ cost = xydistance / (autocvar_g_jetpack_maxspeed_side/autocvar_sv_maxspeed);
+ cost += zdistance / (autocvar_g_jetpack_maxspeed_up/autocvar_sv_maxspeed);
+ cost *= 1.5;
+
+ // Compare against other goals
+ f = f * rangebias / (rangebias + cost);
+
+ if (navigation_bestrating < f)
+ {
+ bot_debug(strcat("jetpack path: added goal ", e.classname, " (with rating ", ftos(f), ")\n"));
+ navigation_bestrating = f;
+ navigation_bestgoal = e;
+ self.navigation_jetpack_goal = e;
+ self.navigation_jetpack_point = pointb;
+ }
+ return;
+ }
+ }
+ }
+
+ //te_wizspike(e.origin);
+ //bprint(etos(e));
+ //bprint("\n");
+ // update the cached spawnfunc_waypoint link on a dynamic item entity
+ if(e.classname == "waypoint" && !(e.wpflags & WAYPOINTFLAG_PERSONAL))
+ {
+ nwp = e;
+ }
+ else
+ {
+ float search;
+
+ search = true;
+
+ if(e.flags & FL_ITEM)
+ {
+ if (!(e.flags & FL_WEAPON))
+ if(e.nearestwaypoint)
+ search = false;
+ }
+ else if (e.flags & FL_WEAPON)
+ {
+ if(e.classname != "droppedweapon")
+ if(e.nearestwaypoint)
+ search = false;
+ }
+
+ if(search)
+ if (time > e.nearestwaypointtimeout)
+ {
+ nwp = navigation_findnearestwaypoint(e, true);
+ if(nwp)
+ e.nearestwaypoint = nwp;
+ else
+ {
+ bot_debug(strcat("FAILED to find a nearest waypoint to '", e.classname, "' #", etos(e), "\n"));
+
+ if(e.flags & FL_ITEM)
+ e.blacklisted = true;
+ else if (e.flags & FL_WEAPON)
+ {
+ if(e.classname != "droppedweapon")
+ e.blacklisted = true;
+ }
+
+ if(e.blacklisted)
+ {
+ bot_debug(strcat("The entity '", e.classname, "' is going to be excluded from path finding during this match\n"));
+ return;
+ }
+ }
+
+ // TODO: Cleaner solution, probably handling this timeout from ctf.qc
+ if(e.classname=="item_flag_team")
+ e.nearestwaypointtimeout = time + 2;
+ else
+ e.nearestwaypointtimeout = time + random() * 3 + 5;
+ }
+ nwp = e.nearestwaypoint;
+ }
+
+ bot_debug(strcat("-- checking ", e.classname, " (with cost ", ftos(nwp.wpcost), ")\n"));
+ if (nwp)
+ if (nwp.wpcost < 10000000)
+ {
+ //te_wizspike(nwp.wpnearestpoint);
+ bot_debug(strcat(e.classname, " ", ftos(f), "/(1+", ftos((nwp.wpcost + vlen(e.origin - nwp.wpnearestpoint))), "/", ftos(rangebias), ") = "));
+ f = f * rangebias / (rangebias + (nwp.wpcost + vlen(o - nwp.wpnearestpoint)));
+ bot_debug(strcat("considering ", e.classname, " (with rating ", ftos(f), ")\n"));
+ if (navigation_bestrating < f)
+ {
+ bot_debug(strcat("ground path: added goal ", e.classname, " (with rating ", ftos(f), ")\n"));
+ navigation_bestrating = f;
+ navigation_bestgoal = e;
+ }
+ }
+}
+
+// adds an item to the the goal stack with the path to a given item
+float navigation_routetogoal(entity e, vector startposition)
+{SELFPARAM();
+ self.goalentity = e;
+
+ // if there is no goal, just exit
+ if (!e)
+ return false;
+
+ self.navigation_hasgoals = true;
+
+ // put the entity on the goal stack
+ //print("routetogoal ", etos(e), "\n");
+ navigation_pushroute(e);
+
+ if(g_jetpack)
+ if(e==self.navigation_jetpack_goal)
+ return true;
+
+ // if it can reach the goal there is nothing more to do
+ if (tracewalk(self, startposition, PL_MIN, PL_MAX, (e.absmin + e.absmax) * 0.5, bot_navigation_movemode))
+ return true;
+
+ // see if there are waypoints describing a path to the item
+ if(e.classname != "waypoint" || (e.wpflags & WAYPOINTFLAG_PERSONAL))
+ e = e.nearestwaypoint;
+ else
+ e = e.enemy; // we already have added it, so...
+
+ if(e == world)
+ return false;
+
+ for (;;)
+ {
+ // add the spawnfunc_waypoint to the path
+ navigation_pushroute(e);
+ e = e.enemy;
+
+ if(e==world)
+ break;
+ }
+
+ return false;
+}
+
+// removes any currently touching waypoints from the goal stack
+// (this is how bots detect if they reached a goal)
+void navigation_poptouchedgoals()
+{SELFPARAM();
+ vector org, m1, m2;
+ org = self.origin;
+ m1 = org + self.mins;
+ m2 = org + self.maxs;
+
+ if(self.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT)
+ {
+ if(self.lastteleporttime>0)
+ if(time-self.lastteleporttime<(self.goalcurrent.wpflags & WAYPOINTFLAG_PERSONAL)?2:0.15)
+ {
+ if(self.aistatus & AI_STATUS_WAYPOINT_PERSONAL_GOING)
+ if(self.goalcurrent.wpflags & WAYPOINTFLAG_PERSONAL && self.goalcurrent.owner==self)
+ {
+ self.aistatus &= ~AI_STATUS_WAYPOINT_PERSONAL_GOING;
+ self.aistatus |= AI_STATUS_WAYPOINT_PERSONAL_REACHED;
+ }
+ navigation_poproute();
+ return;
+ }
+ }
+
+ // If for some reason the bot is closer to the next goal, pop the current one
+ if(self.goalstack01)
+ if(vlen(self.goalcurrent.origin - self.origin) > vlen(self.goalstack01.origin - self.origin))
+ if(checkpvs(self.origin + self.view_ofs, self.goalstack01))
+ if(tracewalk(self, self.origin, self.mins, self.maxs, (self.goalstack01.absmin + self.goalstack01.absmax) * 0.5, bot_navigation_movemode))
+ {
+ bot_debug(strcat("path optimized for ", self.netname, ", removed a goal from the queue\n"));
+ navigation_poproute();
+ // TODO this may also be a nice idea to do "early" (e.g. by
+ // manipulating the vlen() comparisons) to shorten paths in
+ // general - this would make bots walk more "on rails" than
+ // "zigzagging" which they currently do with sufficiently
+ // random-like waypoints, and thus can make a nice bot
+ // personality property
+ }
+
+ // HACK: remove players/bots as goals, they can lead a bot to unexpected places (cliffs, lava, etc)
+ // TODO: rate waypoints near the targetted player at that moment, instead of the player itself
+ if(IS_PLAYER(self.goalcurrent))
+ navigation_poproute();
+
+ // aid for detecting jump pads better (distance based check fails sometimes)
+ if(self.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT && self.jumppadcount > 0 )
+ navigation_poproute();
+
+ // Loose goal touching check when running
+ if(self.aistatus & AI_STATUS_RUNNING)
+ if(self.speed >= autocvar_sv_maxspeed) // if -really- running
+ if(self.goalcurrent.classname=="waypoint")
+ {
+ if(vlen(self.origin - self.goalcurrent.origin)<150)
+ {
+ traceline(self.origin + self.view_ofs , self.goalcurrent.origin, true, world);
+ if(trace_fraction==1)
+ {
+ // Detect personal waypoints
+ if(self.aistatus & AI_STATUS_WAYPOINT_PERSONAL_GOING)
+ if(self.goalcurrent.wpflags & WAYPOINTFLAG_PERSONAL && self.goalcurrent.owner==self)
+ {
+ self.aistatus &= ~AI_STATUS_WAYPOINT_PERSONAL_GOING;
+ self.aistatus |= AI_STATUS_WAYPOINT_PERSONAL_REACHED;
+ }
+
+ navigation_poproute();
+ }
+ }
+ }
+
+ while (self.goalcurrent && boxesoverlap(m1, m2, self.goalcurrent.absmin, self.goalcurrent.absmax))
+ {
+ // Detect personal waypoints
+ if(self.aistatus & AI_STATUS_WAYPOINT_PERSONAL_GOING)
+ if(self.goalcurrent.wpflags & WAYPOINTFLAG_PERSONAL && self.goalcurrent.owner==self)
+ {
+ self.aistatus &= ~AI_STATUS_WAYPOINT_PERSONAL_GOING;
+ self.aistatus |= AI_STATUS_WAYPOINT_PERSONAL_REACHED;
+ }
+
+ navigation_poproute();
+ }
+}
+
+// begin a goal selection session (queries spawnfunc_waypoint network)
+void navigation_goalrating_start()
+{SELFPARAM();
+ if(self.aistatus & AI_STATUS_STUCK)
+ return;
+
+ self.navigation_jetpack_goal = world;
+ navigation_bestrating = -1;
+ self.navigation_hasgoals = false;
+ navigation_clearroute();
+ navigation_bestgoal = world;
+ navigation_markroutes(world);
+}
+
+// ends a goal selection session (updates goal stack to the best goal)
+void navigation_goalrating_end()
+{SELFPARAM();
+ if(self.aistatus & AI_STATUS_STUCK)
+ return;
+
+ navigation_routetogoal(navigation_bestgoal, self.origin);
+ bot_debug(strcat("best goal ", self.goalcurrent.classname , "\n"));
+
+ // If the bot got stuck then try to reach the farthest waypoint
+ if (!self.navigation_hasgoals)
+ if (autocvar_bot_wander_enable)
+ {
+ if (!(self.aistatus & AI_STATUS_STUCK))
+ {
+ bot_debug(strcat(self.netname, " cannot walk to any goal\n"));
+ self.aistatus |= AI_STATUS_STUCK;
+ }
+
+ self.navigation_hasgoals = false; // Reset this value
+ }
+}
+
+void botframe_updatedangerousobjects(float maxupdate)
+{
+ entity head, bot_dodgelist;
+ vector m1, m2, v, o;
+ float c, d, danger;
+ c = 0;
+ bot_dodgelist = findchainfloat(bot_dodge, true);
+ botframe_dangerwaypoint = find(botframe_dangerwaypoint, classname, "waypoint");
+ while (botframe_dangerwaypoint != world)
+ {
+ danger = 0;
+ m1 = botframe_dangerwaypoint.mins;
+ m2 = botframe_dangerwaypoint.maxs;
+ head = bot_dodgelist;
+ while (head)
+ {
+ v = head.origin;
+ v.x = bound(m1_x, v.x, m2_x);
+ v.y = bound(m1_y, v.y, m2_y);
+ v.z = bound(m1_z, v.z, m2_z);
+ o = (head.absmin + head.absmax) * 0.5;
+ d = head.bot_dodgerating - vlen(o - v);
+ if (d > 0)
+ {
+ traceline(o, v, true, world);
+ if (trace_fraction == 1)
+ danger = danger + d;
+ }
+ head = head.chain;
+ }
+ botframe_dangerwaypoint.dmg = danger;
+ c = c + 1;
+ if (c >= maxupdate)
+ break;
+ botframe_dangerwaypoint = find(botframe_dangerwaypoint, classname, "waypoint");
+ }
+}
+
+void navigation_unstuck()
+{SELFPARAM();
+ float search_radius = 1000;
+
+ if (!autocvar_bot_wander_enable)
+ return;
+
+ if (!bot_waypoint_queue_owner)
+ {
+ bot_debug(strcat(self.netname, " sutck, taking over the waypoints queue\n"));
+ bot_waypoint_queue_owner = self;
+ bot_waypoint_queue_bestgoal = world;
+ bot_waypoint_queue_bestgoalrating = 0;
+ }
+
+ if(bot_waypoint_queue_owner!=self)
+ return;
+
+ if (bot_waypoint_queue_goal)
+ {
+ // evaluate the next goal on the queue
+ float d = vlen(self.origin - bot_waypoint_queue_goal.origin);
+ bot_debug(strcat(self.netname, " evaluating ", bot_waypoint_queue_goal.classname, " with distance ", ftos(d), "\n"));
+ if(tracewalk(bot_waypoint_queue_goal, self.origin, PL_MIN, PL_MAX, bot_waypoint_queue_goal.origin, bot_navigation_movemode))
+ {
+ if( d > bot_waypoint_queue_bestgoalrating)
+ {
+ bot_waypoint_queue_bestgoalrating = d;
+ bot_waypoint_queue_bestgoal = bot_waypoint_queue_goal;
+ }
+ }
+ bot_waypoint_queue_goal = bot_waypoint_queue_goal.bot_waypoint_queue_nextgoal;
+
+ if (!bot_waypoint_queue_goal)
+ {
+ if (bot_waypoint_queue_bestgoal)
+ {
+ bot_debug(strcat(self.netname, " stuck, reachable waypoint found, heading to it\n"));
+ navigation_routetogoal(bot_waypoint_queue_bestgoal, self.origin);
+ self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
+ self.aistatus &= ~AI_STATUS_STUCK;
+ }
+ else
+ {
+ bot_debug(strcat(self.netname, " stuck, cannot walk to any waypoint at all\n"));
+ }
+
+ bot_waypoint_queue_owner = world;
+ }
+ }
+ else
+ {
+ if(bot_strategytoken!=self)
+ return;
+
+ // build a new queue
+ bot_debug(strcat(self.netname, " stuck, scanning reachable waypoints within ", ftos(search_radius)," qu\n"));
+
+ entity head, first;
+
+ first = world;
+ head = findradius(self.origin, search_radius);
+
+ while(head)
+ {
+ if(head.classname=="waypoint")
+ // if(!(head.wpflags & WAYPOINTFLAG_GENERATED))
+ {
+ if(bot_waypoint_queue_goal)
+ bot_waypoint_queue_goal.bot_waypoint_queue_nextgoal = head;
+ else
+ first = head;
+
+ bot_waypoint_queue_goal = head;
+ bot_waypoint_queue_goal.bot_waypoint_queue_nextgoal = world;
+ }
+
+ head = head.chain;
+ }
+
+ if (first)
+ bot_waypoint_queue_goal = first;
+ else
+ {
+ bot_debug(strcat(self.netname, " stuck, cannot walk to any waypoint at all\n"));
+ bot_waypoint_queue_owner = world;
+ }
+ }
+}
+
+// Support for debugging tracewalk visually
+
+void debugresetnodes()
+{
+ debuglastnode = '0 0 0';
+}
+
+void debugnode(vector node)
+{SELFPARAM();
+ if (!IS_PLAYER(self))
+ return;
+
+ if(debuglastnode=='0 0 0')
+ {
+ debuglastnode = node;
+ return;
+ }
+
+ te_lightning2(world, node, debuglastnode);
+ debuglastnode = node;
+}
+
+void debugnodestatus(vector position, float status)
+{
+ vector c;
+
+ switch (status)
+ {
+ case DEBUG_NODE_SUCCESS:
+ c = '0 15 0';
+ break;
+ case DEBUG_NODE_WARNING:
+ c = '15 15 0';
+ break;
+ case DEBUG_NODE_FAIL:
+ c = '15 0 0';
+ break;
+ default:
+ c = '15 15 15';
+ }
+
+ te_customflash(position, 40, 2, c);
+}
+
+// Support for debugging the goal stack visually
+
+.float goalcounter;
+.vector lastposition;
+
+// Debug the goal stack visually
+void debuggoalstack()
+{SELFPARAM();
+ entity goal;
+ vector org, go;
+
+ if(self.goalcounter==0)goal=self.goalcurrent;
+ else if(self.goalcounter==1)goal=self.goalstack01;
+ else if(self.goalcounter==2)goal=self.goalstack02;
+ else if(self.goalcounter==3)goal=self.goalstack03;
+ else if(self.goalcounter==4)goal=self.goalstack04;
+ else if(self.goalcounter==5)goal=self.goalstack05;
+ else if(self.goalcounter==6)goal=self.goalstack06;
+ else if(self.goalcounter==7)goal=self.goalstack07;
+ else if(self.goalcounter==8)goal=self.goalstack08;
+ else if(self.goalcounter==9)goal=self.goalstack09;
+ else if(self.goalcounter==10)goal=self.goalstack10;
+ else if(self.goalcounter==11)goal=self.goalstack11;
+ else if(self.goalcounter==12)goal=self.goalstack12;
+ else if(self.goalcounter==13)goal=self.goalstack13;
+ else if(self.goalcounter==14)goal=self.goalstack14;
+ else if(self.goalcounter==15)goal=self.goalstack15;
+ else if(self.goalcounter==16)goal=self.goalstack16;
+ else if(self.goalcounter==17)goal=self.goalstack17;
+ else if(self.goalcounter==18)goal=self.goalstack18;
+ else if(self.goalcounter==19)goal=self.goalstack19;
+ else if(self.goalcounter==20)goal=self.goalstack20;
+ else if(self.goalcounter==21)goal=self.goalstack21;
+ else if(self.goalcounter==22)goal=self.goalstack22;
+ else if(self.goalcounter==23)goal=self.goalstack23;
+ else if(self.goalcounter==24)goal=self.goalstack24;
+ else if(self.goalcounter==25)goal=self.goalstack25;
+ else if(self.goalcounter==26)goal=self.goalstack26;
+ else if(self.goalcounter==27)goal=self.goalstack27;
+ else if(self.goalcounter==28)goal=self.goalstack28;
+ else if(self.goalcounter==29)goal=self.goalstack29;
+ else if(self.goalcounter==30)goal=self.goalstack30;
+ else if(self.goalcounter==31)goal=self.goalstack31;
+ else goal=world;
+
+ if(goal==world)
+ {
+ self.goalcounter = 0;
+ self.lastposition='0 0 0';
+ return;
+ }
+
+ if(self.lastposition=='0 0 0')
+ org = self.origin;
+ else
+ org = self.lastposition;
+
+
+ go = ( goal.absmin + goal.absmax ) * 0.5;
+ te_lightning2(world, org, go);
+ self.lastposition = go;
+
+ self.goalcounter++;
+}
--- /dev/null
+#ifndef NAVIGATION_H
+#define NAVIGATION_H
+/*
+ * Globals and Fields
+ */
+
+float navigation_bestrating;
+float bot_navigation_movemode;
+float navigation_testtracewalk;
+
+vector jumpstepheightvec;
+vector stepheightvec;
+
+entity botframe_dangerwaypoint;
+entity navigation_bestgoal;
+
+// stack of current goals (the last one of which may be an item or other
+// desirable object, the rest are typically waypoints to reach it)
+.entity goalcurrent, goalstack01, goalstack02, goalstack03;
+.entity goalstack04, goalstack05, goalstack06, goalstack07;
+.entity goalstack08, goalstack09, goalstack10, goalstack11;
+.entity goalstack12, goalstack13, goalstack14, goalstack15;
+.entity goalstack16, goalstack17, goalstack18, goalstack19;
+.entity goalstack20, goalstack21, goalstack22, goalstack23;
+.entity goalstack24, goalstack25, goalstack26, goalstack27;
+.entity goalstack28, goalstack29, goalstack30, goalstack31;
+.entity nearestwaypoint;
+
+.float nearestwaypointtimeout;
+.float navigation_hasgoals;
+.float lastteleporttime;
+
+.float blacklisted;
+
+.entity navigation_jetpack_goal;
+.vector navigation_jetpack_point;
+
+const float DEBUG_NODE_SUCCESS = 1;
+const float DEBUG_NODE_WARNING = 2;
+const float DEBUG_NODE_FAIL = 3;
+vector debuglastnode;
+
+entity bot_waypoint_queue_owner; // Owner of the temporary list of goals
+entity bot_waypoint_queue_goal; // Head of the temporary list of goals
+.entity bot_waypoint_queue_nextgoal;
+entity bot_waypoint_queue_bestgoal;
+float bot_waypoint_queue_bestgoalrating;
+
+/*
+ * Functions
+ */
+
+void debugresetnodes();
+void debugnode(vector node);
+void debugnodestatus(vector position, float status);
+
+void debuggoalstack();
+
+float tracewalk(entity e, vector start, vector m1, vector m2, vector end, float movemode);
+
+float navigation_markroutes_nearestwaypoints(entity waylist, float maxdist);
+float navigation_routetogoal(entity e, vector startposition);
+
+void navigation_clearroute();
+void navigation_pushroute(entity e);
+void navigation_poproute();
+void navigation_markroutes_checkwaypoint(entity w, entity wp, float cost2, vector p);
+void navigation_markroutes(entity fixed_source_waypoint);
+void navigation_markroutes_inverted(entity fixed_source_waypoint);
+void navigation_routerating(entity e, float f, float rangebias);
+void navigation_poptouchedgoals();
+void navigation_goalrating_start();
+void navigation_goalrating_end();
+void navigation_unstuck();
+
+void botframe_updatedangerousobjects(float maxupdate);
+
+entity navigation_findnearestwaypoint(entity ent, float walkfromwp);
+float navigation_waypoint_will_link(vector v, vector org, entity ent, float walkfromwp, float bestdist);
+#endif
--- /dev/null
+#include "scripting.qh"
+
+.float bot_cmdqueuebuf_allocated;
+.float bot_cmdqueuebuf;
+.float bot_cmdqueuebuf_start;
+.float bot_cmdqueuebuf_end;
+
+void bot_clearqueue(entity bot)
+{
+ if(!bot.bot_cmdqueuebuf_allocated)
+ return;
+ buf_del(bot.bot_cmdqueuebuf);
+ bot.bot_cmdqueuebuf_allocated = false;
+ LOG_TRACE("bot ", bot.netname, " queue cleared\n");
+}
+
+void bot_queuecommand(entity bot, string cmdstring)
+{
+ if(!bot.bot_cmdqueuebuf_allocated)
+ {
+ bot.bot_cmdqueuebuf = buf_create();
+ bot.bot_cmdqueuebuf_allocated = true;
+ bot.bot_cmdqueuebuf_start = 0;
+ bot.bot_cmdqueuebuf_end = 0;
+ }
+
+ bufstr_set(bot.bot_cmdqueuebuf, bot.bot_cmdqueuebuf_end, cmdstring);
+
+ // if the command was a "sound" command, precache the sound NOW
+ // this prevents lagging!
+ {
+ float sp;
+ string parm;
+ string cmdstr;
+
+ sp = strstrofs(cmdstring, " ", 0);
+ if(sp >= 0)
+ {
+ parm = substring(cmdstring, sp + 1, -1);
+ cmdstr = substring(cmdstring, 0, sp);
+ if(cmdstr == "sound")
+ {
+ // find the LAST word
+ for (;;)
+ {
+ sp = strstrofs(parm, " ", 0);
+ if(sp < 0)
+ break;
+ parm = substring(parm, sp + 1, -1);
+ }
+ precache_sound(parm);
+ }
+ }
+ }
+
+ bot.bot_cmdqueuebuf_end += 1;
+}
+
+void bot_dequeuecommand(entity bot, float idx)
+{
+ if(!bot.bot_cmdqueuebuf_allocated)
+ error("dequeuecommand but no queue allocated");
+ if(idx < bot.bot_cmdqueuebuf_start)
+ error("dequeueing a command in the past");
+ if(idx >= bot.bot_cmdqueuebuf_end)
+ error("dequeueing a command in the future");
+ bufstr_set(bot.bot_cmdqueuebuf, idx, "");
+ if(idx == bot.bot_cmdqueuebuf_start)
+ bot.bot_cmdqueuebuf_start += 1;
+ if(bot.bot_cmdqueuebuf_start >= bot.bot_cmdqueuebuf_end)
+ bot_clearqueue(bot);
+}
+
+string bot_readcommand(entity bot, float idx)
+{
+ if(!bot.bot_cmdqueuebuf_allocated)
+ error("readcommand but no queue allocated");
+ if(idx < bot.bot_cmdqueuebuf_start)
+ error("reading a command in the past");
+ if(idx >= bot.bot_cmdqueuebuf_end)
+ error("reading a command in the future");
+ return bufstr_get(bot.bot_cmdqueuebuf, idx);
+}
+
+float bot_havecommand(entity bot, float idx)
+{
+ if(!bot.bot_cmdqueuebuf_allocated)
+ return 0;
+ if(idx < bot.bot_cmdqueuebuf_start)
+ return 0;
+ if(idx >= bot.bot_cmdqueuebuf_end)
+ return 0;
+ return 1;
+}
+
+const int MAX_BOT_PLACES = 4;
+.float bot_places_count;
+.entity bot_places[MAX_BOT_PLACES];
+.string bot_placenames[MAX_BOT_PLACES];
+entity bot_getplace(string placename)
+{SELFPARAM();
+ entity e;
+ if(substring(placename, 0, 1) == "@")
+ {
+ int i, p;
+ placename = substring(placename, 1, -1);
+ string s, s2;
+ for(i = 0; i < self.bot_places_count; ++i)
+ if(self.(bot_placenames[i]) == placename)
+ return self.(bot_places[i]);
+ // now: i == self.bot_places_count
+ s = s2 = cvar_string(placename);
+ p = strstrofs(s2, " ", 0);
+ if(p >= 0)
+ {
+ s = substring(s2, 0, p);
+ //print("places: ", placename, " -> ", cvar_string(placename), "\n");
+ cvar_set(placename, strcat(substring(s2, p+1, -1), " ", s));
+ //print("places: ", placename, " := ", cvar_string(placename), "\n");
+ }
+ e = find(world, targetname, s);
+ if(!e)
+ LOG_INFO("invalid place ", s, "\n");
+ if(i < MAX_BOT_PLACES)
+ {
+ self.(bot_placenames[i]) = strzone(placename);
+ self.(bot_places[i]) = e;
+ self.bot_places_count += 1;
+ }
+ return e;
+ }
+ else
+ {
+ e = find(world, targetname, placename);
+ if(!e)
+ LOG_INFO("invalid place ", placename, "\n");
+ return e;
+ }
+}
+
+
+// Initialize global commands list
+// NOTE: New commands should be initialized here
+void bot_commands_init()
+{
+ bot_cmd_string[BOT_CMD_NULL] = "";
+ bot_cmd_parm_type[BOT_CMD_NULL] = BOT_CMD_PARAMETER_NONE;
+
+ bot_cmd_string[BOT_CMD_PAUSE] = "pause";
+ bot_cmd_parm_type[BOT_CMD_PAUSE] = BOT_CMD_PARAMETER_NONE;
+
+ bot_cmd_string[BOT_CMD_CONTINUE] = "continue";
+ bot_cmd_parm_type[BOT_CMD_CONTINUE] = BOT_CMD_PARAMETER_NONE;
+
+ bot_cmd_string[BOT_CMD_WAIT] = "wait";
+ bot_cmd_parm_type[BOT_CMD_WAIT] = BOT_CMD_PARAMETER_FLOAT;
+
+ bot_cmd_string[BOT_CMD_TURN] = "turn";
+ bot_cmd_parm_type[BOT_CMD_TURN] = BOT_CMD_PARAMETER_FLOAT;
+
+ bot_cmd_string[BOT_CMD_MOVETO] = "moveto";
+ bot_cmd_parm_type[BOT_CMD_MOVETO] = BOT_CMD_PARAMETER_VECTOR;
+
+ bot_cmd_string[BOT_CMD_MOVETOTARGET] = "movetotarget";
+ bot_cmd_parm_type[BOT_CMD_MOVETOTARGET] = BOT_CMD_PARAMETER_STRING;
+
+ bot_cmd_string[BOT_CMD_RESETGOAL] = "resetgoal";
+ bot_cmd_parm_type[BOT_CMD_RESETGOAL] = BOT_CMD_PARAMETER_NONE;
+
+ bot_cmd_string[BOT_CMD_CC] = "cc";
+ bot_cmd_parm_type[BOT_CMD_CC] = BOT_CMD_PARAMETER_STRING;
+
+ bot_cmd_string[BOT_CMD_IF] = "if";
+ bot_cmd_parm_type[BOT_CMD_IF] = BOT_CMD_PARAMETER_STRING;
+
+ bot_cmd_string[BOT_CMD_ELSE] = "else";
+ bot_cmd_parm_type[BOT_CMD_ELSE] = BOT_CMD_PARAMETER_NONE;
+
+ bot_cmd_string[BOT_CMD_FI] = "fi";
+ bot_cmd_parm_type[BOT_CMD_FI] = BOT_CMD_PARAMETER_NONE;
+
+ bot_cmd_string[BOT_CMD_RESETAIM] = "resetaim";
+ bot_cmd_parm_type[BOT_CMD_RESETAIM] = BOT_CMD_PARAMETER_NONE;
+
+ bot_cmd_string[BOT_CMD_AIM] = "aim";
+ bot_cmd_parm_type[BOT_CMD_AIM] = BOT_CMD_PARAMETER_STRING;
+
+ bot_cmd_string[BOT_CMD_AIMTARGET] = "aimtarget";
+ bot_cmd_parm_type[BOT_CMD_AIMTARGET] = BOT_CMD_PARAMETER_STRING;
+
+ bot_cmd_string[BOT_CMD_PRESSKEY] = "presskey";
+ bot_cmd_parm_type[BOT_CMD_PRESSKEY] = BOT_CMD_PARAMETER_STRING;
+
+ bot_cmd_string[BOT_CMD_RELEASEKEY] = "releasekey";
+ bot_cmd_parm_type[BOT_CMD_RELEASEKEY] = BOT_CMD_PARAMETER_STRING;
+
+ bot_cmd_string[BOT_CMD_SELECTWEAPON] = "selectweapon";
+ bot_cmd_parm_type[BOT_CMD_SELECTWEAPON] = BOT_CMD_PARAMETER_FLOAT;
+
+ bot_cmd_string[BOT_CMD_IMPULSE] = "impulse";
+ bot_cmd_parm_type[BOT_CMD_IMPULSE] = BOT_CMD_PARAMETER_FLOAT;
+
+ bot_cmd_string[BOT_CMD_WAIT_UNTIL] = "wait_until";
+ bot_cmd_parm_type[BOT_CMD_WAIT_UNTIL] = BOT_CMD_PARAMETER_FLOAT;
+
+ bot_cmd_string[BOT_CMD_BARRIER] = "barrier";
+ bot_cmd_parm_type[BOT_CMD_BARRIER] = BOT_CMD_PARAMETER_NONE;
+
+ bot_cmd_string[BOT_CMD_CONSOLE] = "console";
+ bot_cmd_parm_type[BOT_CMD_CONSOLE] = BOT_CMD_PARAMETER_STRING;
+
+ bot_cmd_string[BOT_CMD_SOUND] = "sound";
+ bot_cmd_parm_type[BOT_CMD_SOUND] = BOT_CMD_PARAMETER_STRING;
+
+ bot_cmd_string[BOT_CMD_DEBUG_ASSERT_CANFIRE] = "debug_assert_canfire";
+ bot_cmd_parm_type[BOT_CMD_DEBUG_ASSERT_CANFIRE] = BOT_CMD_PARAMETER_FLOAT;
+
+ bot_cmds_initialized = true;
+}
+
+// Returns first bot with matching name
+entity find_bot_by_name(string name)
+{
+ entity bot;
+
+ bot = findchainflags(flags, FL_CLIENT);
+ while (bot)
+ {
+ if(IS_BOT_CLIENT(bot))
+ if(bot.netname==name)
+ return bot;
+
+ bot = bot.chain;
+ }
+
+ return world;
+}
+
+// Returns a bot by number on list
+entity find_bot_by_number(float number)
+{
+ entity bot;
+ float c = 0;
+
+ if(!number)
+ return world;
+
+ bot = findchainflags(flags, FL_CLIENT);
+ while (bot)
+ {
+ if(IS_BOT_CLIENT(bot))
+ {
+ if(++c==number)
+ return bot;
+ }
+ bot = bot.chain;
+ }
+
+ return world;
+}
+
+float bot_decodecommand(string cmdstring)
+{
+ float cmd_parm_type;
+ float sp;
+ string parm;
+
+ sp = strstrofs(cmdstring, " ", 0);
+ if(sp < 0)
+ {
+ parm = "";
+ }
+ else
+ {
+ parm = substring(cmdstring, sp + 1, -1);
+ cmdstring = substring(cmdstring, 0, sp);
+ }
+
+ if(!bot_cmds_initialized)
+ bot_commands_init();
+
+ int i;
+ for(i=1;i<BOT_CMD_COUNTER;++i)
+ {
+ if(bot_cmd_string[i]!=cmdstring)
+ continue;
+
+ cmd_parm_type = bot_cmd_parm_type[i];
+
+ if(cmd_parm_type!=BOT_CMD_PARAMETER_NONE&&parm=="")
+ {
+ LOG_INFO("ERROR: A parameter is required for this command\n");
+ return 0;
+ }
+
+ // Load command into queue
+ bot_cmd.bot_cmd_type = i;
+
+ // Attach parameter
+ switch(cmd_parm_type)
+ {
+ case BOT_CMD_PARAMETER_FLOAT:
+ bot_cmd.bot_cmd_parm_float = stof(parm);
+ break;
+ case BOT_CMD_PARAMETER_STRING:
+ if(bot_cmd.bot_cmd_parm_string)
+ strunzone(bot_cmd.bot_cmd_parm_string);
+ bot_cmd.bot_cmd_parm_string = strzone(parm);
+ break;
+ case BOT_CMD_PARAMETER_VECTOR:
+ bot_cmd.bot_cmd_parm_vector = stov(parm);
+ break;
+ default:
+ break;
+ }
+ return 1;
+ }
+ LOG_INFO("ERROR: No such command '", cmdstring, "'\n");
+ return 0;
+}
+
+void bot_cmdhelp(string scmd)
+{
+ int i, ntype;
+ string stype;
+
+ if(!bot_cmds_initialized)
+ bot_commands_init();
+
+ for(i=1;i<BOT_CMD_COUNTER;++i)
+ {
+ if(bot_cmd_string[i]!=scmd)
+ continue;
+
+ ntype = bot_cmd_parm_type[i];
+
+ switch(ntype)
+ {
+ case BOT_CMD_PARAMETER_FLOAT:
+ stype = "float number";
+ break;
+ case BOT_CMD_PARAMETER_STRING:
+ stype = "string";
+ break;
+ case BOT_CMD_PARAMETER_VECTOR:
+ stype = "vector";
+ break;
+ default:
+ stype = "none";
+ break;
+ }
+
+ LOG_INFO(strcat("Command: ",bot_cmd_string[i],"\nParameter: <",stype,"> \n"));
+
+ LOG_INFO("Description: ");
+ switch(i)
+ {
+ case BOT_CMD_PAUSE:
+ LOG_INFO("Stops the bot completely. Any command other than 'continue' will be ignored.");
+ break;
+ case BOT_CMD_CONTINUE:
+ LOG_INFO("Disable paused status");
+ break;
+ case BOT_CMD_WAIT:
+ LOG_INFO("Pause command parsing and bot ai for N seconds. Pressed key will remain pressed");
+ break;
+ case BOT_CMD_WAIT_UNTIL:
+ LOG_INFO("Pause command parsing and bot ai until time is N from the last barrier. Pressed key will remain pressed");
+ break;
+ case BOT_CMD_BARRIER:
+ LOG_INFO("Waits till all bots that have a command queue reach this command. Pressed key will remain pressed");
+ break;
+ case BOT_CMD_TURN:
+ LOG_INFO("Look to the right or left N degrees. For turning to the left use positive numbers.");
+ break;
+ case BOT_CMD_MOVETO:
+ LOG_INFO("Walk to an specific coordinate on the map. Usage: moveto \"x y z\"");
+ break;
+ case BOT_CMD_MOVETOTARGET:
+ LOG_INFO("Walk to the specific target on the map");
+ break;
+ case BOT_CMD_RESETGOAL:
+ LOG_INFO("Resets the goal stack");
+ break;
+ case BOT_CMD_CC:
+ LOG_INFO("Execute client command. Examples: cc \"say something\"; cc god; cc \"name newnickname\"; cc kill;");
+ break;
+ case BOT_CMD_IF:
+ LOG_INFO("Perform simple conditional execution.\n");
+ LOG_INFO("Syntax: \n");
+ LOG_INFO(" sv_cmd .. if \"condition\"\n");
+ LOG_INFO(" sv_cmd .. <instruction if true>\n");
+ LOG_INFO(" sv_cmd .. <instruction if true>\n");
+ LOG_INFO(" sv_cmd .. else\n");
+ LOG_INFO(" sv_cmd .. <instruction if false>\n");
+ LOG_INFO(" sv_cmd .. <instruction if false>\n");
+ LOG_INFO(" sv_cmd .. fi\n");
+ LOG_INFO("Conditions: a=b, a>b, a<b, a\t\t(spaces not allowed)\n");
+ LOG_INFO(" Values in conditions can be numbers, cvars in the form cvar.cvar_string or special fields\n");
+ LOG_INFO("Fields: health, speed, flagcarrier\n");
+ LOG_INFO("Examples: if health>50; if health>cvar.g_balance_laser_primary_damage; if flagcarrier;");
+ break;
+ case BOT_CMD_RESETAIM:
+ LOG_INFO("Points the aim to the coordinates x,y 0,0");
+ break;
+ case BOT_CMD_AIM:
+ LOG_INFO("Move the aim x/y (horizontal/vertical) degrees relatives to the bot\n");
+ LOG_INFO("There is a 3rd optional parameter telling in how many seconds the aim has to reach the new position\n");
+ LOG_INFO("Examples: aim \"90 0\" // Turn 90 degrees inmediately (positive numbers move to the left/up)\n");
+ LOG_INFO(" aim \"0 90 2\" // Will gradually look to the sky in the next two seconds");
+ break;
+ case BOT_CMD_AIMTARGET:
+ LOG_INFO("Points the aim to given target");
+ break;
+ case BOT_CMD_PRESSKEY:
+ LOG_INFO("Press one of the following keys: forward, backward, left, right, jump, crouch, attack1, attack2, use\n");
+ LOG_INFO("Multiple keys can be pressed at time (with many presskey calls) and it will remain pressed until the command \"releasekey\" is called");
+ LOG_INFO("Note: The script will not return the control to the bot ai until all keys are released");
+ break;
+ case BOT_CMD_RELEASEKEY:
+ LOG_INFO("Release previoulsy used keys. Use the parameter \"all\" to release all keys");
+ break;
+ case BOT_CMD_SOUND:
+ LOG_INFO("play sound file at bot location");
+ break;
+ case BOT_CMD_DEBUG_ASSERT_CANFIRE:
+ LOG_INFO("verify the state of the weapon entity");
+ break;
+ default:
+ LOG_INFO("This command has no description yet.");
+ break;
+ }
+ LOG_INFO("\n");
+ }
+}
+
+void bot_list_commands()
+{
+ int i;
+ string ptype;
+
+ if(!bot_cmds_initialized)
+ bot_commands_init();
+
+ LOG_INFO("List of all available commands:\n");
+ LOG_INFO(" Command - Parameter Type\n");
+
+ for(i=1;i<BOT_CMD_COUNTER;++i)
+ {
+ switch(bot_cmd_parm_type[i])
+ {
+ case BOT_CMD_PARAMETER_FLOAT:
+ ptype = "float number";
+ break;
+ case BOT_CMD_PARAMETER_STRING:
+ ptype = "string";
+ break;
+ case BOT_CMD_PARAMETER_VECTOR:
+ ptype = "vector";
+ break;
+ default:
+ ptype = "none";
+ break;
+ }
+ LOG_INFO(strcat(" ",bot_cmd_string[i]," - <",ptype,"> \n"));
+ }
+}
+
+// Commands code
+.int bot_exec_status;
+
+void SV_ParseClientCommand(string s);
+float bot_cmd_cc()
+{
+ SV_ParseClientCommand(bot_cmd.bot_cmd_parm_string);
+ return CMD_STATUS_FINISHED;
+}
+
+float bot_cmd_impulse()
+{SELFPARAM();
+ self.impulse = bot_cmd.bot_cmd_parm_float;
+ return CMD_STATUS_FINISHED;
+}
+
+float bot_cmd_continue()
+{SELFPARAM();
+ self.bot_exec_status &= ~BOT_EXEC_STATUS_PAUSED;
+ return CMD_STATUS_FINISHED;
+}
+
+.float bot_cmd_wait_time;
+float bot_cmd_wait()
+{SELFPARAM();
+ if(self.bot_exec_status & BOT_EXEC_STATUS_WAITING)
+ {
+ if(time>=self.bot_cmd_wait_time)
+ {
+ self.bot_exec_status &= ~BOT_EXEC_STATUS_WAITING;
+ return CMD_STATUS_FINISHED;
+ }
+ else
+ return CMD_STATUS_EXECUTING;
+ }
+
+ self.bot_cmd_wait_time = time + bot_cmd.bot_cmd_parm_float;
+ self.bot_exec_status |= BOT_EXEC_STATUS_WAITING;
+ return CMD_STATUS_EXECUTING;
+}
+
+float bot_cmd_wait_until()
+{SELFPARAM();
+ if(time < bot_cmd.bot_cmd_parm_float + bot_barriertime)
+ {
+ self.bot_exec_status |= BOT_EXEC_STATUS_WAITING;
+ return CMD_STATUS_EXECUTING;
+ }
+ self.bot_exec_status &= ~BOT_EXEC_STATUS_WAITING;
+ return CMD_STATUS_FINISHED;
+}
+
+float bot_cmd_barrier()
+{SELFPARAM();
+ entity cl;
+
+ // 0 = no barrier, 1 = waiting, 2 = waiting finished
+
+ if(self.bot_barrier == 0) // initialization
+ {
+ self.bot_barrier = 1;
+
+ //self.colormod = '4 4 0';
+ }
+
+ if(self.bot_barrier == 1) // find other bots
+ {
+ FOR_EACH_CLIENT(cl) if(cl.isbot)
+ {
+ if(cl.bot_cmdqueuebuf_allocated)
+ if(cl.bot_barrier != 1)
+ return CMD_STATUS_EXECUTING; // not all are at the barrier yet
+ }
+
+ // all bots hit the barrier!
+ FOR_EACH_CLIENT(cl) if(cl.isbot)
+ {
+ cl.bot_barrier = 2; // acknowledge barrier
+ }
+
+ bot_barriertime = time;
+ }
+
+ // if we get here, the barrier is finished
+ // so end it...
+ self.bot_barrier = 0;
+ //self.colormod = '0 0 0';
+
+ return CMD_STATUS_FINISHED;
+}
+
+float bot_cmd_turn()
+{SELFPARAM();
+ self.v_angle_y = self.v_angle.y + bot_cmd.bot_cmd_parm_float;
+ self.v_angle_y = self.v_angle.y - floor(self.v_angle.y / 360) * 360;
+ return CMD_STATUS_FINISHED;
+}
+
+float bot_cmd_select_weapon()
+{SELFPARAM();
+ float id;
+
+ id = bot_cmd.bot_cmd_parm_float;
+
+ if(id < WEP_FIRST || id > WEP_LAST)
+ return CMD_STATUS_ERROR;
+
+ if(client_hasweapon(self, id, true, false))
+ self.switchweapon = id;
+ else
+ return CMD_STATUS_ERROR;
+
+ return CMD_STATUS_FINISHED;
+}
+
+.int bot_cmd_condition_status;
+
+const int CMD_CONDITION_NONE = 0;
+const int CMD_CONDITION_true = 1;
+const int CMD_CONDITION_false = 2;
+const int CMD_CONDITION_true_BLOCK = 4;
+const int CMD_CONDITION_false_BLOCK = 8;
+
+float bot_cmd_eval(string expr)
+{SELFPARAM();
+ // Search for numbers
+ if(strstrofs("0123456789", substring(expr, 0, 1), 0) >= 0)
+ {
+ return stof(expr);
+ }
+
+ // Search for cvars
+ if(substring(expr, 0, 5)=="cvar.")
+ {
+ return cvar(substring(expr, 5, strlen(expr)));
+ }
+
+ // Search for fields
+ switch(expr)
+ {
+ case "health":
+ return self.health;
+ case "speed":
+ return vlen(self.velocity);
+ case "flagcarrier":
+ return ((self.flagcarried!=world));
+ }
+
+ LOG_INFO(strcat("ERROR: Unable to convert the expression '",expr,"' into a numeric value\n"));
+ return 0;
+}
+
+float bot_cmd_if()
+{SELFPARAM();
+ string expr, val_a, val_b;
+ float cmpofs;
+
+ if(self.bot_cmd_condition_status != CMD_CONDITION_NONE)
+ {
+ // Only one "if" block is allowed at time
+ LOG_INFO("ERROR: Only one conditional block can be processed at time");
+ bot_clearqueue(self);
+ return CMD_STATUS_ERROR;
+ }
+
+ self.bot_cmd_condition_status |= CMD_CONDITION_true_BLOCK;
+
+ // search for operators
+ expr = bot_cmd.bot_cmd_parm_string;
+
+ cmpofs = strstrofs(expr,"=",0);
+
+ if(cmpofs>0)
+ {
+ val_a = substring(expr,0,cmpofs);
+ val_b = substring(expr,cmpofs+1,strlen(expr));
+
+ if(bot_cmd_eval(val_a)==bot_cmd_eval(val_b))
+ self.bot_cmd_condition_status |= CMD_CONDITION_true;
+ else
+ self.bot_cmd_condition_status |= CMD_CONDITION_false;
+
+ return CMD_STATUS_FINISHED;
+ }
+
+ cmpofs = strstrofs(expr,">",0);
+
+ if(cmpofs>0)
+ {
+ val_a = substring(expr,0,cmpofs);
+ val_b = substring(expr,cmpofs+1,strlen(expr));
+
+ if(bot_cmd_eval(val_a)>bot_cmd_eval(val_b))
+ self.bot_cmd_condition_status |= CMD_CONDITION_true;
+ else
+ self.bot_cmd_condition_status |= CMD_CONDITION_false;
+
+ return CMD_STATUS_FINISHED;
+ }
+
+ cmpofs = strstrofs(expr,"<",0);
+
+ if(cmpofs>0)
+ {
+ val_a = substring(expr,0,cmpofs);
+ val_b = substring(expr,cmpofs+1,strlen(expr));
+
+ if(bot_cmd_eval(val_a)<bot_cmd_eval(val_b))
+ self.bot_cmd_condition_status |= CMD_CONDITION_true;
+ else
+ self.bot_cmd_condition_status |= CMD_CONDITION_false;
+
+ return CMD_STATUS_FINISHED;
+ }
+
+ if(bot_cmd_eval(expr))
+ self.bot_cmd_condition_status |= CMD_CONDITION_true;
+ else
+ self.bot_cmd_condition_status |= CMD_CONDITION_false;
+
+ return CMD_STATUS_FINISHED;
+}
+
+float bot_cmd_else()
+{SELFPARAM();
+ self.bot_cmd_condition_status &= ~CMD_CONDITION_true_BLOCK;
+ self.bot_cmd_condition_status |= CMD_CONDITION_false_BLOCK;
+ return CMD_STATUS_FINISHED;
+}
+
+float bot_cmd_fi()
+{SELFPARAM();
+ self.bot_cmd_condition_status = CMD_CONDITION_NONE;
+ return CMD_STATUS_FINISHED;
+}
+
+float bot_cmd_resetaim()
+{SELFPARAM();
+ self.v_angle = '0 0 0';
+ return CMD_STATUS_FINISHED;
+}
+
+.float bot_cmd_aim_begintime;
+.float bot_cmd_aim_endtime;
+.vector bot_cmd_aim_begin;
+.vector bot_cmd_aim_end;
+
+float bot_cmd_aim()
+{SELFPARAM();
+ // Current direction
+ if(self.bot_cmd_aim_endtime)
+ {
+ float progress;
+
+ progress = min(1 - (self.bot_cmd_aim_endtime - time) / (self.bot_cmd_aim_endtime - self.bot_cmd_aim_begintime),1);
+ self.v_angle = self.bot_cmd_aim_begin + ((self.bot_cmd_aim_end - self.bot_cmd_aim_begin) * progress);
+
+ if(time>=self.bot_cmd_aim_endtime)
+ {
+ self.bot_cmd_aim_endtime = 0;
+ return CMD_STATUS_FINISHED;
+ }
+ else
+ return CMD_STATUS_EXECUTING;
+ }
+
+ // New aiming direction
+ string parms;
+ float tokens, step;
+
+ parms = bot_cmd.bot_cmd_parm_string;
+
+ tokens = tokenizebyseparator(parms, " ");
+
+ if(tokens<2||tokens>3)
+ return CMD_STATUS_ERROR;
+
+ step = (tokens == 3) ? stof(argv(2)) : 0;
+
+ if(step == 0)
+ {
+ self.v_angle_x -= stof(argv(1));
+ self.v_angle_y += stof(argv(0));
+ return CMD_STATUS_FINISHED;
+ }
+
+ self.bot_cmd_aim_begin = self.v_angle;
+
+ self.bot_cmd_aim_end_x = self.v_angle.x - stof(argv(1));
+ self.bot_cmd_aim_end_y = self.v_angle.y + stof(argv(0));
+ self.bot_cmd_aim_end_z = 0;
+
+ self.bot_cmd_aim_begintime = time;
+ self.bot_cmd_aim_endtime = time + step;
+
+ return CMD_STATUS_EXECUTING;
+}
+
+float bot_cmd_aimtarget()
+{SELFPARAM();
+ if(self.bot_cmd_aim_endtime)
+ {
+ return bot_cmd_aim();
+ }
+
+ entity e;
+ string parms;
+ vector v;
+ float tokens, step;
+
+ parms = bot_cmd.bot_cmd_parm_string;
+
+ tokens = tokenizebyseparator(parms, " ");
+
+ e = bot_getplace(argv(0));
+ if(!e)
+ return CMD_STATUS_ERROR;
+
+ v = e.origin + (e.mins + e.maxs) * 0.5;
+
+ if(tokens==1)
+ {
+ self.v_angle = vectoangles(v - (self.origin + self.view_ofs));
+ self.v_angle_x = -self.v_angle.x;
+ return CMD_STATUS_FINISHED;
+ }
+
+ if(tokens<1||tokens>2)
+ return CMD_STATUS_ERROR;
+
+ step = stof(argv(1));
+
+ self.bot_cmd_aim_begin = self.v_angle;
+ self.bot_cmd_aim_end = vectoangles(v - (self.origin + self.view_ofs));
+ self.bot_cmd_aim_end_x = -self.bot_cmd_aim_end.x;
+
+ self.bot_cmd_aim_begintime = time;
+ self.bot_cmd_aim_endtime = time + step;
+
+ return CMD_STATUS_EXECUTING;
+}
+
+.int bot_cmd_keys;
+
+const int BOT_CMD_KEY_NONE = 0;
+const int BOT_CMD_KEY_FORWARD = 1;
+const int BOT_CMD_KEY_BACKWARD = 2;
+const int BOT_CMD_KEY_RIGHT = 4;
+const int BOT_CMD_KEY_LEFT = 8;
+const int BOT_CMD_KEY_JUMP = 16;
+const int BOT_CMD_KEY_ATTACK1 = 32;
+const int BOT_CMD_KEY_ATTACK2 = 64;
+const int BOT_CMD_KEY_USE = 128;
+const int BOT_CMD_KEY_HOOK = 256;
+const int BOT_CMD_KEY_CROUCH = 512;
+const int BOT_CMD_KEY_CHAT = 1024;
+
+float bot_presskeys()
+{SELFPARAM();
+ self.movement = '0 0 0';
+ self.BUTTON_JUMP = false;
+ self.BUTTON_CROUCH = false;
+ self.BUTTON_ATCK = false;
+ self.BUTTON_ATCK2 = false;
+ self.BUTTON_USE = false;
+ self.BUTTON_HOOK = false;
+ self.BUTTON_CHAT = false;
+
+ if(self.bot_cmd_keys == BOT_CMD_KEY_NONE)
+ return false;
+
+ if(self.bot_cmd_keys & BOT_CMD_KEY_FORWARD)
+ self.movement_x = autocvar_sv_maxspeed;
+ else if(self.bot_cmd_keys & BOT_CMD_KEY_BACKWARD)
+ self.movement_x = -autocvar_sv_maxspeed;
+
+ if(self.bot_cmd_keys & BOT_CMD_KEY_RIGHT)
+ self.movement_y = autocvar_sv_maxspeed;
+ else if(self.bot_cmd_keys & BOT_CMD_KEY_LEFT)
+ self.movement_y = -autocvar_sv_maxspeed;
+
+ if(self.bot_cmd_keys & BOT_CMD_KEY_JUMP)
+ self.BUTTON_JUMP = true;
+
+ if(self.bot_cmd_keys & BOT_CMD_KEY_CROUCH)
+ self.BUTTON_CROUCH = true;
+
+ if(self.bot_cmd_keys & BOT_CMD_KEY_ATTACK1)
+ self.BUTTON_ATCK = true;
+
+ if(self.bot_cmd_keys & BOT_CMD_KEY_ATTACK2)
+ self.BUTTON_ATCK2 = true;
+
+ if(self.bot_cmd_keys & BOT_CMD_KEY_USE)
+ self.BUTTON_USE = true;
+
+ if(self.bot_cmd_keys & BOT_CMD_KEY_HOOK)
+ self.BUTTON_HOOK = true;
+
+ if(self.bot_cmd_keys & BOT_CMD_KEY_CHAT)
+ self.BUTTON_CHAT = true;
+
+ return true;
+}
+
+
+float bot_cmd_keypress_handler(string key, float enabled)
+{SELFPARAM();
+ switch(key)
+ {
+ case "all":
+ if(enabled)
+ self.bot_cmd_keys = power2of(20) - 1; // >:)
+ else
+ self.bot_cmd_keys = BOT_CMD_KEY_NONE;
+ case "forward":
+ if(enabled)
+ {
+ self.bot_cmd_keys |= BOT_CMD_KEY_FORWARD;
+ self.bot_cmd_keys &= ~BOT_CMD_KEY_BACKWARD;
+ }
+ else
+ self.bot_cmd_keys &= ~BOT_CMD_KEY_FORWARD;
+ break;
+ case "backward":
+ if(enabled)
+ {
+ self.bot_cmd_keys |= BOT_CMD_KEY_BACKWARD;
+ self.bot_cmd_keys &= ~BOT_CMD_KEY_FORWARD;
+ }
+ else
+ self.bot_cmd_keys &= ~BOT_CMD_KEY_BACKWARD;
+ break;
+ case "left":
+ if(enabled)
+ {
+ self.bot_cmd_keys |= BOT_CMD_KEY_LEFT;
+ self.bot_cmd_keys &= ~BOT_CMD_KEY_RIGHT;
+ }
+ else
+ self.bot_cmd_keys &= ~BOT_CMD_KEY_LEFT;
+ break;
+ case "right":
+ if(enabled)
+ {
+ self.bot_cmd_keys |= BOT_CMD_KEY_RIGHT;
+ self.bot_cmd_keys &= ~BOT_CMD_KEY_LEFT;
+ }
+ else
+ self.bot_cmd_keys &= ~BOT_CMD_KEY_RIGHT;
+ break;
+ case "jump":
+ if(enabled)
+ self.bot_cmd_keys |= BOT_CMD_KEY_JUMP;
+ else
+ self.bot_cmd_keys &= ~BOT_CMD_KEY_JUMP;
+ break;
+ case "crouch":
+ if(enabled)
+ self.bot_cmd_keys |= BOT_CMD_KEY_CROUCH;
+ else
+ self.bot_cmd_keys &= ~BOT_CMD_KEY_CROUCH;
+ break;
+ case "attack1":
+ if(enabled)
+ self.bot_cmd_keys |= BOT_CMD_KEY_ATTACK1;
+ else
+ self.bot_cmd_keys &= ~BOT_CMD_KEY_ATTACK1;
+ break;
+ case "attack2":
+ if(enabled)
+ self.bot_cmd_keys |= BOT_CMD_KEY_ATTACK2;
+ else
+ self.bot_cmd_keys &= ~BOT_CMD_KEY_ATTACK2;
+ break;
+ case "use":
+ if(enabled)
+ self.bot_cmd_keys |= BOT_CMD_KEY_USE;
+ else
+ self.bot_cmd_keys &= ~BOT_CMD_KEY_USE;
+ break;
+ case "hook":
+ if(enabled)
+ self.bot_cmd_keys |= BOT_CMD_KEY_HOOK;
+ else
+ self.bot_cmd_keys &= ~BOT_CMD_KEY_HOOK;
+ break;
+ case "chat":
+ if(enabled)
+ self.bot_cmd_keys |= BOT_CMD_KEY_CHAT;
+ else
+ self.bot_cmd_keys &= ~BOT_CMD_KEY_CHAT;
+ break;
+ default:
+ break;
+ }
+
+ return CMD_STATUS_FINISHED;
+}
+
+float bot_cmd_presskey()
+{
+ string key;
+
+ key = bot_cmd.bot_cmd_parm_string;
+
+ bot_cmd_keypress_handler(key,true);
+
+ return CMD_STATUS_FINISHED;
+}
+
+float bot_cmd_releasekey()
+{
+ string key;
+
+ key = bot_cmd.bot_cmd_parm_string;
+
+ return bot_cmd_keypress_handler(key,false);
+}
+
+float bot_cmd_pause()
+{SELFPARAM();
+ self.button1 = 0;
+ self.button8 = 0;
+ self.BUTTON_USE = 0;
+ self.BUTTON_ATCK = 0;
+ self.BUTTON_JUMP = 0;
+ self.BUTTON_HOOK = 0;
+ self.BUTTON_CHAT = 0;
+ self.BUTTON_ATCK2 = 0;
+ self.BUTTON_CROUCH = 0;
+
+ self.movement = '0 0 0';
+ self.bot_cmd_keys = BOT_CMD_KEY_NONE;
+
+ self.bot_exec_status |= BOT_EXEC_STATUS_PAUSED;
+ return CMD_STATUS_FINISHED;
+}
+
+float bot_cmd_moveto()
+{SELFPARAM();
+ return self.cmd_moveto(bot_cmd.bot_cmd_parm_vector);
+}
+
+float bot_cmd_movetotarget()
+{SELFPARAM();
+ entity e;
+ e = bot_getplace(bot_cmd.bot_cmd_parm_string);
+ if(!e)
+ return CMD_STATUS_ERROR;
+ return self.cmd_moveto(e.origin + (e.mins + e.maxs) * 0.5);
+}
+
+float bot_cmd_resetgoal()
+{SELFPARAM();
+ return self.cmd_resetgoal();
+}
+
+
+float bot_cmd_sound()
+{SELFPARAM();
+ string f;
+ f = bot_cmd.bot_cmd_parm_string;
+
+ float n = tokenizebyseparator(f, " ");
+
+ string sample = f;
+ float chan = CH_WEAPON_B;
+ float vol = VOL_BASE;
+ float atten = ATTEN_MIN;
+
+ if(n >= 1)
+ sample = argv(n - 1);
+ if(n >= 2)
+ chan = stof(argv(0));
+ if(n >= 3)
+ vol = stof(argv(1));
+ if(n >= 4)
+ atten = stof(argv(2));
+
+ precache_sound(f);
+ _sound(self, chan, sample, vol, atten);
+
+ return CMD_STATUS_FINISHED;
+}
+
+.entity tuba_note;
+float bot_cmd_debug_assert_canfire()
+{SELFPARAM();
+ float f;
+ f = bot_cmd.bot_cmd_parm_float;
+
+ if(self.weaponentity.state != WS_READY)
+ {
+ if(f)
+ {
+ self.colormod = '0 8 8';
+ LOG_INFO("Bot ", self.netname, " using ", self.weaponname, " wants to fire, inhibited by weaponentity state\n");
+ }
+ }
+ else if(ATTACK_FINISHED(self) > time)
+ {
+ if(f)
+ {
+ self.colormod = '8 0 8';
+ LOG_INFO("Bot ", self.netname, " using ", self.weaponname, " wants to fire, inhibited by ATTACK_FINISHED (", ftos(ATTACK_FINISHED(self) - time), " seconds left)\n");
+ }
+ }
+ else if(self.tuba_note)
+ {
+ if(f)
+ {
+ self.colormod = '8 0 0';
+ LOG_INFO("Bot ", self.netname, " using ", self.weaponname, " wants to fire, bot still has an active tuba note\n");
+ }
+ }
+ else
+ {
+ if(!f)
+ {
+ self.colormod = '8 8 0';
+ LOG_INFO("Bot ", self.netname, " using ", self.weaponname, " thinks it has fired, but apparently did not; ATTACK_FINISHED says ", ftos(ATTACK_FINISHED(self) - time), " seconds left\n");
+ }
+ }
+
+ return CMD_STATUS_FINISHED;
+}
+
+//
+
+void bot_command_executed(float rm)
+{SELFPARAM();
+ entity cmd;
+
+ cmd = bot_cmd;
+
+ if(rm)
+ bot_dequeuecommand(self, self.bot_cmd_execution_index);
+
+ self.bot_cmd_execution_index++;
+}
+
+void bot_setcurrentcommand()
+{SELFPARAM();
+ bot_cmd = world;
+
+ if(!self.bot_cmd_current)
+ {
+ self.bot_cmd_current = spawn();
+ self.bot_cmd_current.classname = "bot_cmd";
+ self.bot_cmd_current.is_bot_cmd = 1;
+ }
+
+ bot_cmd = self.bot_cmd_current;
+ if(bot_cmd.bot_cmd_index != self.bot_cmd_execution_index || self.bot_cmd_execution_index == 0)
+ {
+ if(bot_havecommand(self, self.bot_cmd_execution_index))
+ {
+ string cmdstring;
+ cmdstring = bot_readcommand(self, self.bot_cmd_execution_index);
+ if(bot_decodecommand(cmdstring))
+ {
+ bot_cmd.owner = self;
+ bot_cmd.bot_cmd_index = self.bot_cmd_execution_index;
+ }
+ else
+ {
+ // Invalid command, remove from queue
+ bot_cmd = world;
+ bot_dequeuecommand(self, self.bot_cmd_execution_index);
+ self.bot_cmd_execution_index++;
+ }
+ }
+ else
+ bot_cmd = world;
+ }
+}
+
+void bot_resetqueues()
+{
+ entity cl;
+
+ FOR_EACH_CLIENT(cl) if(cl.isbot)
+ {
+ cl.bot_cmd_execution_index = 0;
+ bot_clearqueue(cl);
+ // also, cancel all barriers
+ cl.bot_barrier = 0;
+ for(int i = 0; i < cl.bot_places_count; ++i)
+ {
+ strunzone(cl.(bot_placenames[i]));
+ cl.(bot_placenames[i]) = string_null;
+ }
+ cl.bot_places_count = 0;
+ }
+
+ bot_barriertime = time;
+}
+
+bool autocvar_g_debug_bot_commands;
+
+// Here we map commands to functions and deal with complex interactions between commands and execution states
+// NOTE: Of course you need to include your commands here too :)
+float bot_execute_commands_once()
+{SELFPARAM();
+ float status, ispressingkey;
+
+ // Find command
+ bot_setcurrentcommand();
+
+ // if we have no bot command, better return
+ // old logic kept pressing previously pressed keys, but that has problems
+ // (namely, it means you cannot make a bot "normal" ever again)
+ // to keep a bot walking for a while, use the "wait" bot command
+ if(bot_cmd == world)
+ return false;
+
+ // Ignore all commands except continue when the bot is paused
+ if(self.bot_exec_status & BOT_EXEC_STATUS_PAUSED)
+ if(bot_cmd.bot_cmd_type!=BOT_CMD_CONTINUE)
+ {
+ if(bot_cmd.bot_cmd_type!=BOT_CMD_NULL)
+ {
+ bot_command_executed(true);
+ LOG_INFO( "WARNING: Commands are ignored while the bot is paused. Use the command 'continue' instead.\n");
+ }
+ return 1;
+ }
+
+ // Keep pressing keys raised by the "presskey" command
+ ispressingkey = !!bot_presskeys();
+
+ // Handle conditions
+ if (!(bot_cmd.bot_cmd_type==BOT_CMD_FI||bot_cmd.bot_cmd_type==BOT_CMD_ELSE))
+ if(self.bot_cmd_condition_status & CMD_CONDITION_true && self.bot_cmd_condition_status & CMD_CONDITION_false_BLOCK)
+ {
+ bot_command_executed(true);
+ return -1;
+ }
+ else if(self.bot_cmd_condition_status & CMD_CONDITION_false && self.bot_cmd_condition_status & CMD_CONDITION_true_BLOCK)
+ {
+ bot_command_executed(true);
+ return -1;
+ }
+
+ // Map commands to functions
+ switch(bot_cmd.bot_cmd_type)
+ {
+ case BOT_CMD_NULL:
+ return ispressingkey;
+ //break;
+ case BOT_CMD_PAUSE:
+ status = bot_cmd_pause();
+ break;
+ case BOT_CMD_CONTINUE:
+ status = bot_cmd_continue();
+ break;
+ case BOT_CMD_WAIT:
+ status = bot_cmd_wait();
+ break;
+ case BOT_CMD_WAIT_UNTIL:
+ status = bot_cmd_wait_until();
+ break;
+ case BOT_CMD_TURN:
+ status = bot_cmd_turn();
+ break;
+ case BOT_CMD_MOVETO:
+ status = bot_cmd_moveto();
+ break;
+ case BOT_CMD_MOVETOTARGET:
+ status = bot_cmd_movetotarget();
+ break;
+ case BOT_CMD_RESETGOAL:
+ status = bot_cmd_resetgoal();
+ break;
+ case BOT_CMD_CC:
+ status = bot_cmd_cc();
+ break;
+ case BOT_CMD_IF:
+ status = bot_cmd_if();
+ break;
+ case BOT_CMD_ELSE:
+ status = bot_cmd_else();
+ break;
+ case BOT_CMD_FI:
+ status = bot_cmd_fi();
+ break;
+ case BOT_CMD_RESETAIM:
+ status = bot_cmd_resetaim();
+ break;
+ case BOT_CMD_AIM:
+ status = bot_cmd_aim();
+ break;
+ case BOT_CMD_AIMTARGET:
+ status = bot_cmd_aimtarget();
+ break;
+ case BOT_CMD_PRESSKEY:
+ status = bot_cmd_presskey();
+ break;
+ case BOT_CMD_RELEASEKEY:
+ status = bot_cmd_releasekey();
+ break;
+ case BOT_CMD_SELECTWEAPON:
+ status = bot_cmd_select_weapon();
+ break;
+ case BOT_CMD_IMPULSE:
+ status = bot_cmd_impulse();
+ break;
+ case BOT_CMD_BARRIER:
+ status = bot_cmd_barrier();
+ break;
+ case BOT_CMD_CONSOLE:
+ localcmd(strcat(bot_cmd.bot_cmd_parm_string, "\n"));
+ status = CMD_STATUS_FINISHED;
+ break;
+ case BOT_CMD_SOUND:
+ status = bot_cmd_sound();
+ break;
+ case BOT_CMD_DEBUG_ASSERT_CANFIRE:
+ status = bot_cmd_debug_assert_canfire();
+ break;
+ default:
+ LOG_INFO(strcat("ERROR: Invalid command on queue with id '",ftos(bot_cmd.bot_cmd_type),"'\n"));
+ return 0;
+ }
+
+ if (status==CMD_STATUS_ERROR)
+ LOG_INFO(strcat("ERROR: The command '",bot_cmd_string[bot_cmd.bot_cmd_type],"' returned an error status\n"));
+
+ // Move execution pointer
+ if(status==CMD_STATUS_EXECUTING)
+ {
+ return 1;
+ }
+ else
+ {
+ if(autocvar_g_debug_bot_commands)
+ {
+ string parms;
+
+ switch(bot_cmd_parm_type[bot_cmd.bot_cmd_type])
+ {
+ case BOT_CMD_PARAMETER_FLOAT:
+ parms = ftos(bot_cmd.bot_cmd_parm_float);
+ break;
+ case BOT_CMD_PARAMETER_STRING:
+ parms = bot_cmd.bot_cmd_parm_string;
+ break;
+ case BOT_CMD_PARAMETER_VECTOR:
+ parms = vtos(bot_cmd.bot_cmd_parm_vector);
+ break;
+ default:
+ parms = "";
+ break;
+ }
+ clientcommand(self,strcat("say ^7", bot_cmd_string[bot_cmd.bot_cmd_type]," ",parms,"\n"));
+ }
+
+ bot_command_executed(true);
+ }
+
+ if(status == CMD_STATUS_FINISHED)
+ return -1;
+
+ return CMD_STATUS_ERROR;
+}
+
+// This function should be (the only) called directly from the bot ai loop
+float bot_execute_commands()
+{
+ float f;
+ do
+ {
+ f = bot_execute_commands_once();
+ }
+ while(f < 0);
+ return f;
+}
--- /dev/null
+#ifndef BOT_SCRIPTING_H
+#define BOT_SCRIPTING_H
+
+#define BOT_EXEC_STATUS_IDLE 0
+#define BOT_EXEC_STATUS_PAUSED 1
+#define BOT_EXEC_STATUS_WAITING 2
+
+#define CMD_STATUS_EXECUTING 0
+#define CMD_STATUS_FINISHED 1
+#define CMD_STATUS_ERROR 2
+
+void bot_clearqueue(entity bot);
+
+// NOTE: New commands should be added here. Do not forget to update BOT_CMD_COUNTER
+const int BOT_CMD_NULL = 0;
+const int BOT_CMD_PAUSE = 1;
+const int BOT_CMD_CONTINUE = 2;
+const int BOT_CMD_WAIT = 3;
+const int BOT_CMD_TURN = 4;
+const int BOT_CMD_MOVETO = 5;
+const int BOT_CMD_RESETGOAL = 6; // Not implemented yet
+const int BOT_CMD_CC = 7;
+const int BOT_CMD_IF = 8;
+const int BOT_CMD_ELSE = 9;
+const int BOT_CMD_FI = 10;
+const int BOT_CMD_RESETAIM = 11;
+const int BOT_CMD_AIM = 12;
+const int BOT_CMD_PRESSKEY = 13;
+const int BOT_CMD_RELEASEKEY = 14;
+const int BOT_CMD_SELECTWEAPON = 15;
+const int BOT_CMD_IMPULSE = 16;
+const int BOT_CMD_WAIT_UNTIL = 17;
+const int BOT_CMD_MOVETOTARGET = 18;
+const int BOT_CMD_AIMTARGET = 19;
+const int BOT_CMD_BARRIER = 20;
+const int BOT_CMD_CONSOLE = 21;
+const int BOT_CMD_SOUND = 22;
+const int BOT_CMD_DEBUG_ASSERT_CANFIRE = 23;
+const int BOT_CMD_WHILE = 24; // TODO: Not implemented yet
+const int BOT_CMD_WEND = 25; // TODO: Not implemented yet
+const int BOT_CMD_CHASE = 26; // TODO: Not implemented yet
+
+const int BOT_CMD_COUNTER = 24; // Update this value if you add/remove a command
+
+// NOTE: Following commands should be implemented on the bot ai
+// If a new command should be handled by the target ai(s) please declare it here
+.float(vector) cmd_moveto;
+.float() cmd_resetgoal;
+
+//
+const int BOT_CMD_PARAMETER_NONE = 0;
+const int BOT_CMD_PARAMETER_FLOAT = 1;
+const int BOT_CMD_PARAMETER_STRING = 2;
+const int BOT_CMD_PARAMETER_VECTOR = 3;
+
+float bot_cmds_initialized;
+int bot_cmd_parm_type[BOT_CMD_COUNTER];
+string bot_cmd_string[BOT_CMD_COUNTER];
+
+// Bots command queue
+entity bot_cmd; // global current command
+.entity bot_cmd_current; // current command of this bot
+
+.float is_bot_cmd; // Tells if the entity is a bot command
+.float bot_cmd_index; // Position of the command in the queue
+.int bot_cmd_type; // If of command (see the BOT_CMD_* defines)
+.float bot_cmd_parm_float; // Field to store a float parameter
+.string bot_cmd_parm_string; // Field to store a string parameter
+.vector bot_cmd_parm_vector; // Field to store a vector parameter
+
+float bot_barriertime;
+.float bot_barrier;
+
+.float bot_cmd_execution_index; // Position in the queue of the command to be executed
+
+
+void bot_resetqueues();
+void bot_queuecommand(entity bot, string cmdstring);
+void bot_cmdhelp(string scmd);
+void bot_list_commands();
+float bot_execute_commands();
+entity find_bot_by_name(string name);
+entity find_bot_by_number(float number);
+#endif
--- /dev/null
+#include "waypoints.qh"
+
+// create a new spawnfunc_waypoint and automatically link it to other waypoints, and link
+// them back to it as well
+// (suitable for spawnfunc_waypoint editor)
+entity waypoint_spawn(vector m1, vector m2, float f)
+{
+ entity w;
+ w = find(world, classname, "waypoint");
+
+ if (!(f & WAYPOINTFLAG_PERSONAL))
+ while (w)
+ {
+ // if a matching spawnfunc_waypoint already exists, don't add a duplicate
+ if (boxesoverlap(m1, m2, w.absmin, w.absmax))
+ return w;
+ w = find(w, classname, "waypoint");
+ }
+
+ w = spawn();
+ w.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP;
+ w.classname = "waypoint";
+ w.wpflags = f;
+ setorigin(w, (m1 + m2) * 0.5);
+ setsize(w, m1 - w.origin, m2 - w.origin);
+ if (vlen(w.size) > 0)
+ w.wpisbox = true;
+
+ if(!w.wpisbox)
+ {
+ setsize(w, PL_MIN - '1 1 0', PL_MAX + '1 1 0');
+ if(!move_out_of_solid(w))
+ {
+ if(!(f & WAYPOINTFLAG_GENERATED))
+ {
+ LOG_TRACE("Killed a waypoint that was stuck in solid at ", vtos(w.origin), "\n");
+ remove(w);
+ return world;
+ }
+ else
+ {
+ if(autocvar_developer)
+ {
+ LOG_INFO("A generated waypoint is stuck in solid at ", vtos(w.origin), "\n");
+ backtrace("Waypoint stuck");
+ }
+ }
+ }
+ setsize(w, '0 0 0', '0 0 0');
+ }
+
+ waypoint_clearlinks(w);
+ //waypoint_schedulerelink(w);
+
+ if (autocvar_g_waypointeditor)
+ {
+ m1 = w.mins;
+ m2 = w.maxs;
+ setmodel(w, MDL_WAYPOINT); w.effects = EF_LOWPRECISION;
+ setsize(w, m1, m2);
+ if (w.wpflags & WAYPOINTFLAG_ITEM)
+ w.colormod = '1 0 0';
+ else if (w.wpflags & WAYPOINTFLAG_GENERATED)
+ w.colormod = '1 1 0';
+ else
+ w.colormod = '1 1 1';
+ }
+ else
+ w.model = "";
+
+ return w;
+}
+
+// add a new link to the spawnfunc_waypoint, replacing the furthest link it already has
+void waypoint_addlink(entity from, entity to)
+{
+ float c;
+
+ if (from == to)
+ return;
+ if (from.wpflags & WAYPOINTFLAG_NORELINK)
+ return;
+
+ if (from.wp00 == to) return;if (from.wp01 == to) return;if (from.wp02 == to) return;if (from.wp03 == to) return;
+ if (from.wp04 == to) return;if (from.wp05 == to) return;if (from.wp06 == to) return;if (from.wp07 == to) return;
+ if (from.wp08 == to) return;if (from.wp09 == to) return;if (from.wp10 == to) return;if (from.wp11 == to) return;
+ if (from.wp12 == to) return;if (from.wp13 == to) return;if (from.wp14 == to) return;if (from.wp15 == to) return;
+ if (from.wp16 == to) return;if (from.wp17 == to) return;if (from.wp18 == to) return;if (from.wp19 == to) return;
+ if (from.wp20 == to) return;if (from.wp21 == to) return;if (from.wp22 == to) return;if (from.wp23 == to) return;
+ if (from.wp24 == to) return;if (from.wp25 == to) return;if (from.wp26 == to) return;if (from.wp27 == to) return;
+ if (from.wp28 == to) return;if (from.wp29 == to) return;if (from.wp30 == to) return;if (from.wp31 == to) return;
+
+ if (to.wpisbox || from.wpisbox)
+ {
+ // if either is a box we have to find the nearest points on them to
+ // calculate the distance properly
+ vector v1, v2, m1, m2;
+ v1 = from.origin;
+ m1 = to.absmin;
+ m2 = to.absmax;
+ v1_x = bound(m1_x, v1_x, m2_x);
+ v1_y = bound(m1_y, v1_y, m2_y);
+ v1_z = bound(m1_z, v1_z, m2_z);
+ v2 = to.origin;
+ m1 = from.absmin;
+ m2 = from.absmax;
+ v2_x = bound(m1_x, v2_x, m2_x);
+ v2_y = bound(m1_y, v2_y, m2_y);
+ v2_z = bound(m1_z, v2_z, m2_z);
+ v2 = to.origin;
+ c = vlen(v2 - v1);
+ }
+ else
+ c = vlen(to.origin - from.origin);
+
+ if (from.wp31mincost < c) return;
+ if (from.wp30mincost < c) {from.wp31 = to;from.wp31mincost = c;return;} from.wp31 = from.wp30;from.wp31mincost = from.wp30mincost;
+ if (from.wp29mincost < c) {from.wp30 = to;from.wp30mincost = c;return;} from.wp30 = from.wp29;from.wp30mincost = from.wp29mincost;
+ if (from.wp28mincost < c) {from.wp29 = to;from.wp29mincost = c;return;} from.wp29 = from.wp28;from.wp29mincost = from.wp28mincost;
+ if (from.wp27mincost < c) {from.wp28 = to;from.wp28mincost = c;return;} from.wp28 = from.wp27;from.wp28mincost = from.wp27mincost;
+ if (from.wp26mincost < c) {from.wp27 = to;from.wp27mincost = c;return;} from.wp27 = from.wp26;from.wp27mincost = from.wp26mincost;
+ if (from.wp25mincost < c) {from.wp26 = to;from.wp26mincost = c;return;} from.wp26 = from.wp25;from.wp26mincost = from.wp25mincost;
+ if (from.wp24mincost < c) {from.wp25 = to;from.wp25mincost = c;return;} from.wp25 = from.wp24;from.wp25mincost = from.wp24mincost;
+ if (from.wp23mincost < c) {from.wp24 = to;from.wp24mincost = c;return;} from.wp24 = from.wp23;from.wp24mincost = from.wp23mincost;
+ if (from.wp22mincost < c) {from.wp23 = to;from.wp23mincost = c;return;} from.wp23 = from.wp22;from.wp23mincost = from.wp22mincost;
+ if (from.wp21mincost < c) {from.wp22 = to;from.wp22mincost = c;return;} from.wp22 = from.wp21;from.wp22mincost = from.wp21mincost;
+ if (from.wp20mincost < c) {from.wp21 = to;from.wp21mincost = c;return;} from.wp21 = from.wp20;from.wp21mincost = from.wp20mincost;
+ if (from.wp19mincost < c) {from.wp20 = to;from.wp20mincost = c;return;} from.wp20 = from.wp19;from.wp20mincost = from.wp19mincost;
+ if (from.wp18mincost < c) {from.wp19 = to;from.wp19mincost = c;return;} from.wp19 = from.wp18;from.wp19mincost = from.wp18mincost;
+ if (from.wp17mincost < c) {from.wp18 = to;from.wp18mincost = c;return;} from.wp18 = from.wp17;from.wp18mincost = from.wp17mincost;
+ if (from.wp16mincost < c) {from.wp17 = to;from.wp17mincost = c;return;} from.wp17 = from.wp16;from.wp17mincost = from.wp16mincost;
+ if (from.wp15mincost < c) {from.wp16 = to;from.wp16mincost = c;return;} from.wp16 = from.wp15;from.wp16mincost = from.wp15mincost;
+ if (from.wp14mincost < c) {from.wp15 = to;from.wp15mincost = c;return;} from.wp15 = from.wp14;from.wp15mincost = from.wp14mincost;
+ if (from.wp13mincost < c) {from.wp14 = to;from.wp14mincost = c;return;} from.wp14 = from.wp13;from.wp14mincost = from.wp13mincost;
+ if (from.wp12mincost < c) {from.wp13 = to;from.wp13mincost = c;return;} from.wp13 = from.wp12;from.wp13mincost = from.wp12mincost;
+ if (from.wp11mincost < c) {from.wp12 = to;from.wp12mincost = c;return;} from.wp12 = from.wp11;from.wp12mincost = from.wp11mincost;
+ if (from.wp10mincost < c) {from.wp11 = to;from.wp11mincost = c;return;} from.wp11 = from.wp10;from.wp11mincost = from.wp10mincost;
+ if (from.wp09mincost < c) {from.wp10 = to;from.wp10mincost = c;return;} from.wp10 = from.wp09;from.wp10mincost = from.wp09mincost;
+ if (from.wp08mincost < c) {from.wp09 = to;from.wp09mincost = c;return;} from.wp09 = from.wp08;from.wp09mincost = from.wp08mincost;
+ if (from.wp07mincost < c) {from.wp08 = to;from.wp08mincost = c;return;} from.wp08 = from.wp07;from.wp08mincost = from.wp07mincost;
+ if (from.wp06mincost < c) {from.wp07 = to;from.wp07mincost = c;return;} from.wp07 = from.wp06;from.wp07mincost = from.wp06mincost;
+ if (from.wp05mincost < c) {from.wp06 = to;from.wp06mincost = c;return;} from.wp06 = from.wp05;from.wp06mincost = from.wp05mincost;
+ if (from.wp04mincost < c) {from.wp05 = to;from.wp05mincost = c;return;} from.wp05 = from.wp04;from.wp05mincost = from.wp04mincost;
+ if (from.wp03mincost < c) {from.wp04 = to;from.wp04mincost = c;return;} from.wp04 = from.wp03;from.wp04mincost = from.wp03mincost;
+ if (from.wp02mincost < c) {from.wp03 = to;from.wp03mincost = c;return;} from.wp03 = from.wp02;from.wp03mincost = from.wp02mincost;
+ if (from.wp01mincost < c) {from.wp02 = to;from.wp02mincost = c;return;} from.wp02 = from.wp01;from.wp02mincost = from.wp01mincost;
+ if (from.wp00mincost < c) {from.wp01 = to;from.wp01mincost = c;return;} from.wp01 = from.wp00;from.wp01mincost = from.wp00mincost;
+ from.wp00 = to;from.wp00mincost = c;return;
+}
+
+// relink this spawnfunc_waypoint
+// (precompile a list of all reachable waypoints from this spawnfunc_waypoint)
+// (SLOW!)
+void waypoint_think()
+{SELFPARAM();
+ entity e;
+ vector sv, sm1, sm2, ev, em1, em2, dv;
+
+ bot_calculate_stepheightvec();
+
+ bot_navigation_movemode = ((autocvar_bot_navigation_ignoreplayers) ? MOVE_NOMONSTERS : MOVE_NORMAL);
+
+ //dprint("waypoint_think wpisbox = ", ftos(self.wpisbox), "\n");
+ sm1 = self.origin + self.mins;
+ sm2 = self.origin + self.maxs;
+ for(e = world; (e = find(e, classname, "waypoint")); )
+ {
+ if (boxesoverlap(self.absmin, self.absmax, e.absmin, e.absmax))
+ {
+ waypoint_addlink(self, e);
+ waypoint_addlink(e, self);
+ }
+ else
+ {
+ ++relink_total;
+ if(!checkpvs(self.origin, e))
+ {
+ ++relink_pvsculled;
+ continue;
+ }
+ sv = e.origin;
+ sv.x = bound(sm1_x, sv.x, sm2_x);
+ sv.y = bound(sm1_y, sv.y, sm2_y);
+ sv.z = bound(sm1_z, sv.z, sm2_z);
+ ev = self.origin;
+ em1 = e.origin + e.mins;
+ em2 = e.origin + e.maxs;
+ ev.x = bound(em1_x, ev.x, em2_x);
+ ev.y = bound(em1_y, ev.y, em2_y);
+ ev.z = bound(em1_z, ev.z, em2_z);
+ dv = ev - sv;
+ dv.z = 0;
+ if (vlen(dv) >= 1050) // max search distance in XY
+ {
+ ++relink_lengthculled;
+ continue;
+ }
+ navigation_testtracewalk = 0;
+ if (!self.wpisbox)
+ {
+ tracebox(sv - PL_MIN.z * '0 0 1', PL_MIN, PL_MAX, sv, false, self);
+ if (!trace_startsolid)
+ {
+ //dprint("sv deviation", vtos(trace_endpos - sv), "\n");
+ sv = trace_endpos + '0 0 1';
+ }
+ }
+ if (!e.wpisbox)
+ {
+ tracebox(ev - PL_MIN.z * '0 0 1', PL_MIN, PL_MAX, ev, false, e);
+ if (!trace_startsolid)
+ {
+ //dprint("ev deviation", vtos(trace_endpos - ev), "\n");
+ ev = trace_endpos + '0 0 1';
+ }
+ }
+ //traceline(self.origin, e.origin, false, world);
+ //if (trace_fraction == 1)
+ if (!self.wpisbox && tracewalk(self, sv, PL_MIN, PL_MAX, ev, MOVE_NOMONSTERS))
+ waypoint_addlink(self, e);
+ else
+ relink_walkculled += 0.5;
+ if (!e.wpisbox && tracewalk(e, ev, PL_MIN, PL_MAX, sv, MOVE_NOMONSTERS))
+ waypoint_addlink(e, self);
+ else
+ relink_walkculled += 0.5;
+ }
+ }
+ navigation_testtracewalk = 0;
+ self.wplinked = true;
+}
+
+void waypoint_clearlinks(entity wp)
+{
+ // clear links to other waypoints
+ float f;
+ f = 10000000;
+ wp.wp00 = wp.wp01 = wp.wp02 = wp.wp03 = wp.wp04 = wp.wp05 = wp.wp06 = wp.wp07 = world;
+ wp.wp08 = wp.wp09 = wp.wp10 = wp.wp11 = wp.wp12 = wp.wp13 = wp.wp14 = wp.wp15 = world;
+ wp.wp16 = wp.wp17 = wp.wp18 = wp.wp19 = wp.wp20 = wp.wp21 = wp.wp22 = wp.wp23 = world;
+ wp.wp24 = wp.wp25 = wp.wp26 = wp.wp27 = wp.wp28 = wp.wp29 = wp.wp30 = wp.wp31 = world;
+
+ wp.wp00mincost = wp.wp01mincost = wp.wp02mincost = wp.wp03mincost = wp.wp04mincost = wp.wp05mincost = wp.wp06mincost = wp.wp07mincost = f;
+ wp.wp08mincost = wp.wp09mincost = wp.wp10mincost = wp.wp11mincost = wp.wp12mincost = wp.wp13mincost = wp.wp14mincost = wp.wp15mincost = f;
+ wp.wp16mincost = wp.wp17mincost = wp.wp18mincost = wp.wp19mincost = wp.wp20mincost = wp.wp21mincost = wp.wp22mincost = wp.wp23mincost = f;
+ wp.wp24mincost = wp.wp25mincost = wp.wp26mincost = wp.wp27mincost = wp.wp28mincost = wp.wp29mincost = wp.wp30mincost = wp.wp31mincost = f;
+
+ wp.wplinked = false;
+}
+
+// tell a spawnfunc_waypoint to relink
+void waypoint_schedulerelink(entity wp)
+{
+ if (wp == world)
+ return;
+ // TODO: add some sort of visible box in edit mode for box waypoints
+ if (autocvar_g_waypointeditor)
+ {
+ vector m1, m2;
+ m1 = wp.mins;
+ m2 = wp.maxs;
+ setmodel(wp, MDL_WAYPOINT); wp.effects = EF_LOWPRECISION;
+ setsize(wp, m1, m2);
+ if (wp.wpflags & WAYPOINTFLAG_ITEM)
+ wp.colormod = '1 0 0';
+ else if (wp.wpflags & WAYPOINTFLAG_GENERATED)
+ wp.colormod = '1 1 0';
+ else
+ wp.colormod = '1 1 1';
+ }
+ else
+ wp.model = "";
+ wp.wpisbox = vlen(wp.size) > 0;
+ wp.enemy = world;
+ if (!(wp.wpflags & WAYPOINTFLAG_PERSONAL))
+ wp.owner = world;
+ if (!(wp.wpflags & WAYPOINTFLAG_NORELINK))
+ waypoint_clearlinks(wp);
+ // schedule an actual relink on next frame
+ wp.think = waypoint_think;
+ wp.nextthink = time;
+ wp.effects = EF_LOWPRECISION;
+}
+
+// spawnfunc_waypoint map entity
+spawnfunc(waypoint)
+{
+ setorigin(self, self.origin);
+ // schedule a relink after other waypoints have had a chance to spawn
+ waypoint_clearlinks(self);
+ //waypoint_schedulerelink(self);
+}
+
+// remove a spawnfunc_waypoint, and schedule all neighbors to relink
+void waypoint_remove(entity e)
+{
+ // tell all linked waypoints that they need to relink
+ waypoint_schedulerelink(e.wp00);
+ waypoint_schedulerelink(e.wp01);
+ waypoint_schedulerelink(e.wp02);
+ waypoint_schedulerelink(e.wp03);
+ waypoint_schedulerelink(e.wp04);
+ waypoint_schedulerelink(e.wp05);
+ waypoint_schedulerelink(e.wp06);
+ waypoint_schedulerelink(e.wp07);
+ waypoint_schedulerelink(e.wp08);
+ waypoint_schedulerelink(e.wp09);
+ waypoint_schedulerelink(e.wp10);
+ waypoint_schedulerelink(e.wp11);
+ waypoint_schedulerelink(e.wp12);
+ waypoint_schedulerelink(e.wp13);
+ waypoint_schedulerelink(e.wp14);
+ waypoint_schedulerelink(e.wp15);
+ waypoint_schedulerelink(e.wp16);
+ waypoint_schedulerelink(e.wp17);
+ waypoint_schedulerelink(e.wp18);
+ waypoint_schedulerelink(e.wp19);
+ waypoint_schedulerelink(e.wp20);
+ waypoint_schedulerelink(e.wp21);
+ waypoint_schedulerelink(e.wp22);
+ waypoint_schedulerelink(e.wp23);
+ waypoint_schedulerelink(e.wp24);
+ waypoint_schedulerelink(e.wp25);
+ waypoint_schedulerelink(e.wp26);
+ waypoint_schedulerelink(e.wp27);
+ waypoint_schedulerelink(e.wp28);
+ waypoint_schedulerelink(e.wp29);
+ waypoint_schedulerelink(e.wp30);
+ waypoint_schedulerelink(e.wp31);
+ // and now remove the spawnfunc_waypoint
+ remove(e);
+}
+
+// empties the map of waypoints
+void waypoint_removeall()
+{
+ entity head, next;
+ head = findchain(classname, "waypoint");
+ while (head)
+ {
+ next = head.chain;
+ remove(head);
+ head = next;
+ }
+}
+
+// tell all waypoints to relink
+// (is this useful at all?)
+void waypoint_schedulerelinkall()
+{
+ entity head;
+ relink_total = relink_walkculled = relink_pvsculled = relink_lengthculled = 0;
+ head = findchain(classname, "waypoint");
+ while (head)
+ {
+ waypoint_schedulerelink(head);
+ head = head.chain;
+ }
+}
+
+// Load waypoint links from file
+float waypoint_load_links()
+{
+ string filename, s;
+ float file, tokens, c = 0, found;
+ entity wp_from = world, wp_to;
+ vector wp_to_pos, wp_from_pos;
+ filename = strcat("maps/", mapname);
+ filename = strcat(filename, ".waypoints.cache");
+ file = fopen(filename, FILE_READ);
+
+ if (file < 0)
+ {
+ LOG_TRACE("waypoint links load from ");
+ LOG_TRACE(filename);
+ LOG_TRACE(" failed\n");
+ return false;
+ }
+
+ while ((s = fgets(file)))
+ {
+ tokens = tokenizebyseparator(s, "*");
+
+ if (tokens!=2)
+ {
+ // bad file format
+ fclose(file);
+ return false;
+ }
+
+ wp_from_pos = stov(argv(0));
+ wp_to_pos = stov(argv(1));
+
+ // Search "from" waypoint
+ if(!wp_from || wp_from.origin!=wp_from_pos)
+ {
+ wp_from = findradius(wp_from_pos, 1);
+ found = false;
+ while(wp_from)
+ {
+ if(vlen(wp_from.origin-wp_from_pos)<1)
+ if(wp_from.classname == "waypoint")
+ {
+ found = true;
+ break;
+ }
+ wp_from = wp_from.chain;
+ }
+
+ if(!found)
+ {
+ LOG_TRACE("waypoint_load_links: couldn't find 'from' waypoint at ", vtos(wp_from.origin),"\n");
+ continue;
+ }
+
+ }
+
+ // Search "to" waypoint
+ wp_to = findradius(wp_to_pos, 1);
+ found = false;
+ while(wp_to)
+ {
+ if(vlen(wp_to.origin-wp_to_pos)<1)
+ if(wp_to.classname == "waypoint")
+ {
+ found = true;
+ break;
+ }
+ wp_to = wp_to.chain;
+ }
+
+ if(!found)
+ {
+ LOG_TRACE("waypoint_load_links: couldn't find 'to' waypoint at ", vtos(wp_to.origin),"\n");
+ continue;
+ }
+
+ ++c;
+ waypoint_addlink(wp_from, wp_to);
+ }
+
+ fclose(file);
+
+ LOG_TRACE("loaded ");
+ LOG_TRACE(ftos(c));
+ LOG_TRACE(" waypoint links from maps/");
+ LOG_TRACE(mapname);
+ LOG_TRACE(".waypoints.cache\n");
+
+ botframe_cachedwaypointlinks = true;
+ return true;
+}
+
+void waypoint_load_links_hardwired()
+{
+ string filename, s;
+ float file, tokens, c = 0, found;
+ entity wp_from = world, wp_to;
+ vector wp_to_pos, wp_from_pos;
+ filename = strcat("maps/", mapname);
+ filename = strcat(filename, ".waypoints.hardwired");
+ file = fopen(filename, FILE_READ);
+
+ botframe_loadedforcedlinks = true;
+
+ if (file < 0)
+ {
+ LOG_TRACE("waypoint links load from ");
+ LOG_TRACE(filename);
+ LOG_TRACE(" failed\n");
+ return;
+ }
+
+ while ((s = fgets(file)))
+ {
+ if(substring(s, 0, 2)=="//")
+ continue;
+
+ if(substring(s, 0, 1)=="#")
+ continue;
+
+ tokens = tokenizebyseparator(s, "*");
+
+ if (tokens!=2)
+ continue;
+
+ wp_from_pos = stov(argv(0));
+ wp_to_pos = stov(argv(1));
+
+ // Search "from" waypoint
+ if(!wp_from || wp_from.origin!=wp_from_pos)
+ {
+ wp_from = findradius(wp_from_pos, 5);
+ found = false;
+ while(wp_from)
+ {
+ if(vlen(wp_from.origin-wp_from_pos)<5)
+ if(wp_from.classname == "waypoint")
+ {
+ found = true;
+ break;
+ }
+ wp_from = wp_from.chain;
+ }
+
+ if(!found)
+ {
+ LOG_INFO(strcat("NOTICE: Can not find waypoint at ", vtos(wp_from_pos), ". Path skipped\n"));
+ continue;
+ }
+ }
+
+ // Search "to" waypoint
+ wp_to = findradius(wp_to_pos, 5);
+ found = false;
+ while(wp_to)
+ {
+ if(vlen(wp_to.origin-wp_to_pos)<5)
+ if(wp_to.classname == "waypoint")
+ {
+ found = true;
+ break;
+ }
+ wp_to = wp_to.chain;
+ }
+
+ if(!found)
+ {
+ LOG_INFO(strcat("NOTICE: Can not find waypoint at ", vtos(wp_to_pos), ". Path skipped\n"));
+ continue;
+ }
+
+ ++c;
+ waypoint_addlink(wp_from, wp_to);
+ wp_from.wphardwired = true;
+ wp_to.wphardwired = true;
+ }
+
+ fclose(file);
+
+ LOG_TRACE("loaded ");
+ LOG_TRACE(ftos(c));
+ LOG_TRACE(" waypoint links from maps/");
+ LOG_TRACE(mapname);
+ LOG_TRACE(".waypoints.hardwired\n");
+}
+
+entity waypoint_get_link(entity w, float i)
+{
+ switch(i)
+ {
+ case 0:return w.wp00;
+ case 1:return w.wp01;
+ case 2:return w.wp02;
+ case 3:return w.wp03;
+ case 4:return w.wp04;
+ case 5:return w.wp05;
+ case 6:return w.wp06;
+ case 7:return w.wp07;
+ case 8:return w.wp08;
+ case 9:return w.wp09;
+ case 10:return w.wp10;
+ case 11:return w.wp11;
+ case 12:return w.wp12;
+ case 13:return w.wp13;
+ case 14:return w.wp14;
+ case 15:return w.wp15;
+ case 16:return w.wp16;
+ case 17:return w.wp17;
+ case 18:return w.wp18;
+ case 19:return w.wp19;
+ case 20:return w.wp20;
+ case 21:return w.wp21;
+ case 22:return w.wp22;
+ case 23:return w.wp23;
+ case 24:return w.wp24;
+ case 25:return w.wp25;
+ case 26:return w.wp26;
+ case 27:return w.wp27;
+ case 28:return w.wp28;
+ case 29:return w.wp29;
+ case 30:return w.wp30;
+ case 31:return w.wp31;
+ default:return world;
+ }
+}
+
+// Save all waypoint links to a file
+void waypoint_save_links()
+{
+ string filename, s;
+ float file, c, i;
+ entity w, link;
+ filename = strcat("maps/", mapname);
+ filename = strcat(filename, ".waypoints.cache");
+ file = fopen(filename, FILE_WRITE);
+ if (file < 0)
+ {
+ LOG_INFO("waypoint links save to ");
+ LOG_INFO(filename);
+ LOG_INFO(" failed\n");
+ }
+ c = 0;
+ w = findchain(classname, "waypoint");
+ while (w)
+ {
+ for(i=0;i<32;++i)
+ {
+ // :S
+ link = waypoint_get_link(w, i);
+ if(link==world)
+ continue;
+
+ s = strcat(vtos(w.origin), "*", vtos(link.origin), "\n");
+ fputs(file, s);
+ ++c;
+ }
+ w = w.chain;
+ }
+ fclose(file);
+ botframe_cachedwaypointlinks = true;
+
+ LOG_INFO("saved ");
+ LOG_INFO(ftos(c));
+ LOG_INFO(" waypoints links to maps/");
+ LOG_INFO(mapname);
+ LOG_INFO(".waypoints.cache\n");
+}
+
+// save waypoints to gamedir/data/maps/mapname.waypoints
+void waypoint_saveall()
+{
+ string filename, s;
+ float file, c;
+ entity w;
+ filename = strcat("maps/", mapname);
+ filename = strcat(filename, ".waypoints");
+ file = fopen(filename, FILE_WRITE);
+ if (file >= 0)
+ {
+ c = 0;
+ w = findchain(classname, "waypoint");
+ while (w)
+ {
+ if (!(w.wpflags & WAYPOINTFLAG_GENERATED))
+ {
+ s = strcat(vtos(w.origin + w.mins), "\n");
+ s = strcat(s, vtos(w.origin + w.maxs));
+ s = strcat(s, "\n");
+ s = strcat(s, ftos(w.wpflags));
+ s = strcat(s, "\n");
+ fputs(file, s);
+ c = c + 1;
+ }
+ w = w.chain;
+ }
+ fclose(file);
+ bprint("saved ");
+ bprint(ftos(c));
+ bprint(" waypoints to maps/");
+ bprint(mapname);
+ bprint(".waypoints\n");
+ }
+ else
+ {
+ bprint("waypoint save to ");
+ bprint(filename);
+ bprint(" failed\n");
+ }
+ waypoint_save_links();
+ botframe_loadedforcedlinks = false;
+}
+
+// load waypoints from file
+float waypoint_loadall()
+{
+ string filename, s;
+ float file, cwp, cwb, fl;
+ vector m1, m2;
+ cwp = 0;
+ cwb = 0;
+ filename = strcat("maps/", mapname);
+ filename = strcat(filename, ".waypoints");
+ file = fopen(filename, FILE_READ);
+ if (file >= 0)
+ {
+ while ((s = fgets(file)))
+ {
+ m1 = stov(s);
+ s = fgets(file);
+ if (!s)
+ break;
+ m2 = stov(s);
+ s = fgets(file);
+ if (!s)
+ break;
+ fl = stof(s);
+ waypoint_spawn(m1, m2, fl);
+ if (m1 == m2)
+ cwp = cwp + 1;
+ else
+ cwb = cwb + 1;
+ }
+ fclose(file);
+ LOG_TRACE("loaded ");
+ LOG_TRACE(ftos(cwp));
+ LOG_TRACE(" waypoints and ");
+ LOG_TRACE(ftos(cwb));
+ LOG_TRACE(" wayboxes from maps/");
+ LOG_TRACE(mapname);
+ LOG_TRACE(".waypoints\n");
+ }
+ else
+ {
+ LOG_TRACE("waypoint load from ");
+ LOG_TRACE(filename);
+ LOG_TRACE(" failed\n");
+ }
+ return cwp + cwb;
+}
+
+vector waypoint_fixorigin(vector position)
+{
+ tracebox(position + '0 0 1' * (1 - PL_MIN.z), PL_MIN, PL_MAX, position + '0 0 -512', MOVE_NOMONSTERS, world);
+ if(trace_fraction < 1)
+ position = trace_endpos;
+ //traceline(position, position + '0 0 -512', MOVE_NOMONSTERS, world);
+ //print("position is ", ftos(trace_endpos_z - position_z), " above solid\n");
+ return position;
+}
+
+void waypoint_spawnforitem_force(entity e, vector org)
+{
+ entity w;
+
+ // Fix the waypoint altitude if necessary
+ org = waypoint_fixorigin(org);
+
+ // don't spawn an item spawnfunc_waypoint if it already exists
+ w = findchain(classname, "waypoint");
+ while (w)
+ {
+ if (w.wpisbox)
+ {
+ if (boxesoverlap(org, org, w.absmin, w.absmax))
+ {
+ e.nearestwaypoint = w;
+ return;
+ }
+ }
+ else
+ {
+ if (vlen(w.origin - org) < 16)
+ {
+ e.nearestwaypoint = w;
+ return;
+ }
+ }
+ w = w.chain;
+ }
+ e.nearestwaypoint = waypoint_spawn(org, org, WAYPOINTFLAG_GENERATED | WAYPOINTFLAG_ITEM);
+}
+
+void waypoint_spawnforitem(entity e)
+{
+ if(!bot_waypoints_for_items)
+ return;
+
+ waypoint_spawnforitem_force(e, e.origin);
+}
+
+void waypoint_spawnforteleporter_boxes(entity e, vector org1, vector org2, vector destination1, vector destination2, float timetaken)
+{
+ entity w;
+ entity dw;
+ w = waypoint_spawn(org1, org2, WAYPOINTFLAG_GENERATED | WAYPOINTFLAG_TELEPORT | WAYPOINTFLAG_NORELINK);
+ dw = waypoint_spawn(destination1, destination2, WAYPOINTFLAG_GENERATED);
+ // one way link to the destination
+ w.wp00 = dw;
+ w.wp00mincost = timetaken; // this is just for jump pads
+ // the teleporter's nearest spawnfunc_waypoint is this one
+ // (teleporters are not goals, so this is probably useless)
+ e.nearestwaypoint = w;
+ e.nearestwaypointtimeout = time + 1000000000;
+}
+
+void waypoint_spawnforteleporter_v(entity e, vector org, vector destination, float timetaken)
+{
+ org = waypoint_fixorigin(org);
+ destination = waypoint_fixorigin(destination);
+ waypoint_spawnforteleporter_boxes(e, org, org, destination, destination, timetaken);
+}
+
+void waypoint_spawnforteleporter(entity e, vector destination, float timetaken)
+{
+ destination = waypoint_fixorigin(destination);
+ waypoint_spawnforteleporter_boxes(e, e.absmin, e.absmax, destination, destination, timetaken);
+}
+
+entity waypoint_spawnpersonal(vector position)
+{SELFPARAM();
+ entity w;
+
+ // drop the waypoint to a proper location:
+ // first move it up by a player height
+ // then move it down to hit the floor with player bbox size
+ position = waypoint_fixorigin(position);
+
+ w = waypoint_spawn(position, position, WAYPOINTFLAG_GENERATED | WAYPOINTFLAG_PERSONAL);
+ w.nearestwaypoint = world;
+ w.nearestwaypointtimeout = 0;
+ w.owner = self;
+
+ waypoint_schedulerelink(w);
+
+ return w;
+}
+
+void botframe_showwaypointlinks()
+{
+ entity player, head, w;
+ if (time < botframe_waypointeditorlightningtime)
+ return;
+ botframe_waypointeditorlightningtime = time + 0.5;
+ player = find(world, classname, "player");
+ while (player)
+ {
+ if (!player.isbot)
+ if (player.flags & FL_ONGROUND || player.waterlevel > WATERLEVEL_NONE)
+ {
+ //navigation_testtracewalk = true;
+ head = navigation_findnearestwaypoint(player, false);
+ // print("currently selected WP is ", etos(head), "\n");
+ //navigation_testtracewalk = false;
+ if (head)
+ {
+ w = head ;if (w) te_lightning2(world, w.origin, player.origin);
+ w = head.wp00;if (w) te_lightning2(world, w.origin, head.origin);
+ w = head.wp01;if (w) te_lightning2(world, w.origin, head.origin);
+ w = head.wp02;if (w) te_lightning2(world, w.origin, head.origin);
+ w = head.wp03;if (w) te_lightning2(world, w.origin, head.origin);
+ w = head.wp04;if (w) te_lightning2(world, w.origin, head.origin);
+ w = head.wp05;if (w) te_lightning2(world, w.origin, head.origin);
+ w = head.wp06;if (w) te_lightning2(world, w.origin, head.origin);
+ w = head.wp07;if (w) te_lightning2(world, w.origin, head.origin);
+ w = head.wp08;if (w) te_lightning2(world, w.origin, head.origin);
+ w = head.wp09;if (w) te_lightning2(world, w.origin, head.origin);
+ w = head.wp10;if (w) te_lightning2(world, w.origin, head.origin);
+ w = head.wp11;if (w) te_lightning2(world, w.origin, head.origin);
+ w = head.wp12;if (w) te_lightning2(world, w.origin, head.origin);
+ w = head.wp13;if (w) te_lightning2(world, w.origin, head.origin);
+ w = head.wp14;if (w) te_lightning2(world, w.origin, head.origin);
+ w = head.wp15;if (w) te_lightning2(world, w.origin, head.origin);
+ w = head.wp16;if (w) te_lightning2(world, w.origin, head.origin);
+ w = head.wp17;if (w) te_lightning2(world, w.origin, head.origin);
+ w = head.wp18;if (w) te_lightning2(world, w.origin, head.origin);
+ w = head.wp19;if (w) te_lightning2(world, w.origin, head.origin);
+ w = head.wp20;if (w) te_lightning2(world, w.origin, head.origin);
+ w = head.wp21;if (w) te_lightning2(world, w.origin, head.origin);
+ w = head.wp22;if (w) te_lightning2(world, w.origin, head.origin);
+ w = head.wp23;if (w) te_lightning2(world, w.origin, head.origin);
+ w = head.wp24;if (w) te_lightning2(world, w.origin, head.origin);
+ w = head.wp25;if (w) te_lightning2(world, w.origin, head.origin);
+ w = head.wp26;if (w) te_lightning2(world, w.origin, head.origin);
+ w = head.wp27;if (w) te_lightning2(world, w.origin, head.origin);
+ w = head.wp28;if (w) te_lightning2(world, w.origin, head.origin);
+ w = head.wp29;if (w) te_lightning2(world, w.origin, head.origin);
+ w = head.wp30;if (w) te_lightning2(world, w.origin, head.origin);
+ w = head.wp31;if (w) te_lightning2(world, w.origin, head.origin);
+ }
+ }
+ player = find(player, classname, "player");
+ }
+}
+
+float botframe_autowaypoints_fixdown(vector v)
+{
+ tracebox(v, PL_MIN, PL_MAX, v + '0 0 -64', MOVE_NOMONSTERS, world);
+ if(trace_fraction >= 1)
+ return 0;
+ return 1;
+}
+
+float botframe_autowaypoints_createwp(vector v, entity p, .entity fld, float f)
+{
+ entity w;
+
+ w = find(world, classname, "waypoint");
+ while (w)
+ {
+ // if a matching spawnfunc_waypoint already exists, don't add a duplicate
+ if (boxesoverlap(v - '32 32 32', v + '32 32 32', w.absmin, w.absmax))
+ //if (boxesoverlap(v - '4 4 4', v + '4 4 4', w.absmin, w.absmax))
+ return 0;
+ w = find(w, classname, "waypoint");
+ }
+
+ waypoint_schedulerelink(p.(fld) = waypoint_spawn(v, v, f));
+ return 1;
+}
+
+// return value:
+// 1 = WP created
+// 0 = no action needed
+// -1 = temp fail, try from world too
+// -2 = permanent fail, do not retry
+float botframe_autowaypoints_fix_from(entity p, float walkfromwp, entity wp, .entity fld)
+{
+ // make it possible to go from p to wp, if we can
+ // if wp is world, nearest is chosen
+
+ entity w;
+ vector porg;
+ float t, tmin, tmax;
+ vector o;
+ vector save;
+
+ if(!botframe_autowaypoints_fixdown(p.origin))
+ return -2;
+ porg = trace_endpos;
+
+ if(wp)
+ {
+ // if any WP w fulfills wp -> w -> porg and w is closer than wp, then switch from wp to w
+
+ // if wp -> porg, then OK
+ float maxdist;
+ if(navigation_waypoint_will_link(wp.origin, porg, p, walkfromwp, 1050))
+ {
+ // we may find a better one
+ maxdist = vlen(wp.origin - porg);
+ }
+ else
+ {
+ // accept any "good"
+ maxdist = 2100;
+ }
+
+ float bestdist;
+ bestdist = maxdist;
+ w = find(world, classname, "waypoint");
+ while (w)
+ {
+ if(w != wp && !(w.wpflags & WAYPOINTFLAG_NORELINK))
+ {
+ float d;
+ d = vlen(wp.origin - w.origin) + vlen(w.origin - porg);
+ if(d < bestdist)
+ if(navigation_waypoint_will_link(wp.origin, w.origin, p, walkfromwp, 1050))
+ if(navigation_waypoint_will_link(w.origin, porg, p, walkfromwp, 1050))
+ {
+ bestdist = d;
+ p.(fld) = w;
+ }
+ }
+ w = find(w, classname, "waypoint");
+ }
+ if(bestdist < maxdist)
+ {
+ LOG_INFO("update chain to new nearest WP ", etos(p.(fld)), "\n");
+ return 0;
+ }
+
+ if(bestdist < 2100)
+ {
+ // we know maxdist < 2100
+ // so wp -> porg is still valid
+ // all is good
+ p.(fld) = wp;
+ return 0;
+ }
+
+ // otherwise, no existing WP can fix our issues
+ }
+ else
+ {
+ save = p.origin;
+ setorigin(p, porg);
+ w = navigation_findnearestwaypoint(p, walkfromwp);
+ setorigin(p, save);
+ if(w)
+ {
+ p.(fld) = w;
+ return 0;
+ }
+ }
+
+ tmin = 0;
+ tmax = 1;
+ for (;;)
+ {
+ if(tmax - tmin < 0.001)
+ {
+ // did not get a good candidate
+ return -1;
+ }
+
+ t = (tmin + tmax) * 0.5;
+ o = antilag_takebackorigin(p, time - t);
+ if(!botframe_autowaypoints_fixdown(o))
+ return -2;
+ o = trace_endpos;
+
+ if(wp)
+ {
+ if(!navigation_waypoint_will_link(wp.origin, o, p, walkfromwp, 1050))
+ {
+ // we cannot walk from wp.origin to o
+ // get closer to tmax
+ tmin = t;
+ continue;
+ }
+ }
+ else
+ {
+ save = p.origin;
+ setorigin(p, o);
+ w = navigation_findnearestwaypoint(p, walkfromwp);
+ setorigin(p, save);
+ if(!w)
+ {
+ // we cannot walk from any WP to o
+ // get closer to tmax
+ tmin = t;
+ continue;
+ }
+ }
+
+ // if we get here, o is valid regarding waypoints
+ // check if o is connected right to the player
+ // we break if it succeeds, as that means o is a good waypoint location
+ if(navigation_waypoint_will_link(o, porg, p, walkfromwp, 1050))
+ break;
+
+ // o is no good, we need to get closer to the player
+ tmax = t;
+ }
+
+ LOG_INFO("spawning a waypoint for connecting to ", etos(wp), "\n");
+ botframe_autowaypoints_createwp(o, p, fld, 0);
+ return 1;
+}
+
+// automatically create missing waypoints
+.entity botframe_autowaypoints_lastwp0, botframe_autowaypoints_lastwp1;
+void botframe_autowaypoints_fix(entity p, float walkfromwp, .entity fld)
+{
+ float r = botframe_autowaypoints_fix_from(p, walkfromwp, p.(fld), fld);
+ if(r != -1)
+ return;
+ r = botframe_autowaypoints_fix_from(p, walkfromwp, world, fld);
+ if(r != -1)
+ return;
+
+ LOG_INFO("emergency: got no good nearby WP to build a link from, starting a new chain\n");
+ if(!botframe_autowaypoints_fixdown(p.origin))
+ return; // shouldn't happen, caught above
+ botframe_autowaypoints_createwp(trace_endpos, p, fld, WAYPOINTFLAG_PROTECTED);
+}
+
+void botframe_deleteuselesswaypoints()
+{
+ entity w, w1, w2;
+ float i, j, k;
+ for (w = world; (w = findfloat(w, bot_pickup, true)); )
+ {
+ // NOTE: this protects waypoints if they're the ONLY nearest
+ // waypoint. That's the intention.
+ navigation_findnearestwaypoint(w, false); // Walk TO item.
+ navigation_findnearestwaypoint(w, true); // Walk FROM item.
+ }
+ for (w = world; (w = find(w, classname, "waypoint")); )
+ {
+ w.wpflags |= WAYPOINTFLAG_DEAD_END;
+ w.wpflags &= ~WAYPOINTFLAG_USEFUL;
+ // WP is useful if:
+ if (w.wpflags & WAYPOINTFLAG_ITEM)
+ w.wpflags |= WAYPOINTFLAG_USEFUL;
+ if (w.wpflags & WAYPOINTFLAG_TELEPORT)
+ w.wpflags |= WAYPOINTFLAG_USEFUL;
+ if (w.wpflags & WAYPOINTFLAG_PROTECTED)
+ w.wpflags |= WAYPOINTFLAG_USEFUL;
+ // b) WP is closest WP for an item/spawnpoint/other entity
+ // This has been done above by protecting these WPs.
+ }
+ // c) There are w1, w, w2 so that w1 -> w, w -> w2 and not w1 -> w2.
+ for (w1 = world; (w1 = find(w1, classname, "waypoint")); )
+ {
+ if (w1.wpflags & WAYPOINTFLAG_PERSONAL)
+ continue;
+ for (i = 0; i < 32; ++i)
+ {
+ w = waypoint_get_link(w1, i);
+ if (!w)
+ break;
+ if (w.wpflags & WAYPOINTFLAG_PERSONAL)
+ continue;
+ if (w.wpflags & WAYPOINTFLAG_USEFUL)
+ continue;
+ for (j = 0; j < 32; ++j)
+ {
+ w2 = waypoint_get_link(w, j);
+ if (!w2)
+ break;
+ if (w1 == w2)
+ continue;
+ if (w2.wpflags & WAYPOINTFLAG_PERSONAL)
+ continue;
+ // If we got here, w1 != w2 exist with w1 -> w
+ // and w -> w2. That means the waypoint is not
+ // a dead end.
+ w.wpflags &= ~WAYPOINTFLAG_DEAD_END;
+ for (k = 0; k < 32; ++k)
+ {
+ if (waypoint_get_link(w1, k) == w2)
+ continue;
+ // IF WE GET HERE, w is proven useful
+ // to get from w1 to w2!
+ w.wpflags |= WAYPOINTFLAG_USEFUL;
+ goto next;
+ }
+ }
+:next
+ }
+ }
+ // d) The waypoint is a dead end. Dead end waypoints must be kept as
+ // they are needed to complete routes while autowaypointing.
+
+ for (w = world; (w = find(w, classname, "waypoint")); )
+ {
+ if (!(w.wpflags & (WAYPOINTFLAG_USEFUL | WAYPOINTFLAG_DEAD_END)))
+ {
+ LOG_INFOF("Removed a waypoint at %v. Try again for more!\n", w.origin);
+ te_explosion(w.origin);
+ waypoint_remove(w);
+ break;
+ }
+ }
+ for (w = world; (w = find(w, classname, "waypoint")); )
+ w.wpflags &= ~(WAYPOINTFLAG_USEFUL | WAYPOINTFLAG_DEAD_END); // temp flag
+}
+
+void botframe_autowaypoints()
+{
+ entity p;
+ FOR_EACH_REALPLAYER(p)
+ {
+ if(p.deadflag)
+ continue;
+ // going back is broken, so only fix waypoints to walk TO the player
+ //botframe_autowaypoints_fix(p, false, botframe_autowaypoints_lastwp0);
+ botframe_autowaypoints_fix(p, true, botframe_autowaypoints_lastwp1);
+ //te_explosion(p.botframe_autowaypoints_lastwp0.origin);
+ }
+
+ if (autocvar_g_waypointeditor_auto >= 2) {
+ botframe_deleteuselesswaypoints();
+ }
+}
+
--- /dev/null
+#ifndef WAYPOINTS_H
+#define WAYPOINTS_H
+/*
+ * Globals and Fields
+ */
+// const int WAYPOINTFLAG_GENERATED = 0x800000;
+const int WAYPOINTFLAG_ITEM = 0x400000;
+const int WAYPOINTFLAG_TELEPORT = 0x200000;
+const int WAYPOINTFLAG_NORELINK = 0x100000;
+const int WAYPOINTFLAG_PERSONAL = 0x80000;
+const int WAYPOINTFLAG_PROTECTED = 0x40000; // Useless WP detection never kills these.
+const int WAYPOINTFLAG_USEFUL = 0x20000; // Useless WP detection temporary flag.
+const int WAYPOINTFLAG_DEAD_END = 0x10000; // Useless WP detection temporary flag.
+
+// fields you can query using prvm_global server to get some statistics about waypoint linking culling
+float relink_total, relink_walkculled, relink_pvsculled, relink_lengthculled;
+
+float botframe_waypointeditorlightningtime;
+float botframe_loadedforcedlinks;
+float botframe_cachedwaypointlinks;
+
+.entity wp00, wp01, wp02, wp03, wp04, wp05, wp06, wp07, wp08, wp09, wp10, wp11, wp12, wp13, wp14, wp15;
+.entity wp16, wp17, wp18, wp19, wp20, wp21, wp22, wp23, wp24, wp25, wp26, wp27, wp28, wp29, wp30, wp31;
+
+// itemscore = (howmuchmoreIwant / howmuchIcanwant) / itemdistance
+.float wp00mincost, wp01mincost, wp02mincost, wp03mincost, wp04mincost, wp05mincost, wp06mincost, wp07mincost;
+.float wp08mincost, wp09mincost, wp10mincost, wp11mincost, wp12mincost, wp13mincost, wp14mincost, wp15mincost;
+.float wp16mincost, wp17mincost, wp18mincost, wp19mincost, wp20mincost, wp21mincost, wp22mincost, wp23mincost;
+.float wp24mincost, wp25mincost, wp26mincost, wp27mincost, wp28mincost, wp29mincost, wp30mincost, wp31mincost;
+
+.float wpfire, wpconsidered, wpisbox, wplinked, wphardwired;
+.int wpflags;
+
+.vector wpnearestpoint;
+
+/*
+ * Functions
+ */
+
+spawnfunc(waypoint);
+void waypoint_addlink(entity from, entity to);
+void waypoint_think();
+void waypoint_clearlinks(entity wp);
+void waypoint_schedulerelink(entity wp);
+
+void waypoint_remove(entity e);
+void waypoint_removeall();
+void waypoint_schedulerelinkall();
+void waypoint_load_links_hardwired();
+void waypoint_save_links();
+void waypoint_saveall();
+
+void waypoint_spawnforitem_force(entity e, vector org);
+void waypoint_spawnforitem(entity e);
+void waypoint_spawnforteleporter(entity e, vector destination, float timetaken);
+void waypoint_spawnforteleporter_v(entity e, vector org, vector destination, float timetaken);
+void botframe_showwaypointlinks();
+
+float waypoint_loadall();
+float waypoint_load_links();
+
+entity waypoint_spawn(vector m1, vector m2, float f);
+entity waypoint_spawnpersonal(vector position);
+
+vector waypoint_fixorigin(vector position);
+
+void botframe_autowaypoints();
+#endif
+++ /dev/null
-#include "havocbot.qh"
-#include "../../_all.qh"
-
-#include "../aim.qh"
-#include "../bot.qh"
-#include "../navigation.qh"
-#include "../scripting.qh"
-#include "../waypoints.qh"
-
-#include "../../../common/constants.qh"
-
-#include "../../../common/triggers/trigger/jumppads.qh"
-
-#include "../../../warpzonelib/common.qh"
-
-void havocbot_ai()
-{SELFPARAM();
- if(self.draggedby)
- return;
-
- if(bot_execute_commands())
- return;
-
- if (bot_strategytoken == self)
- if (!bot_strategytoken_taken)
- {
- if(self.havocbot_blockhead)
- {
- self.havocbot_blockhead = false;
- }
- else
- {
- if (!self.jumppadcount)
- self.havocbot_role();
- }
-
- // TODO: tracewalk() should take care of this job (better path finding under water)
- // if we don't have a goal and we're under water look for a waypoint near the "shore" and push it
- if(self.deadflag != DEAD_NO)
- if(self.goalcurrent==world)
- if(self.waterlevel==WATERLEVEL_SWIMMING || (self.aistatus & AI_STATUS_OUT_WATER))
- {
- // Look for the closest waypoint out of water
- entity newgoal, head;
- float bestdistance, distance;
-
- newgoal = world;
- bestdistance = 10000;
- for (head = findchain(classname, "waypoint"); head; head = head.chain)
- {
- distance = vlen(head.origin - self.origin);
- if(distance>10000)
- continue;
-
- if(head.origin.z < self.origin.z)
- continue;
-
- if(head.origin.z - self.origin.z - self.view_ofs.z > 100)
- continue;
-
- if (pointcontents(head.origin + head.maxs + '0 0 1') != CONTENT_EMPTY)
- continue;
-
- traceline(self.origin + self.view_ofs , head.origin, true, head);
-
- if(trace_fraction<1)
- continue;
-
- if(distance<bestdistance)
- {
- newgoal = head;
- bestdistance = distance;
- }
- }
-
- if(newgoal)
- {
- // te_wizspike(newgoal.origin);
- navigation_pushroute(newgoal);
- }
- }
-
- // token has been used this frame
- bot_strategytoken_taken = true;
- }
-
- if(self.deadflag != DEAD_NO)
- return;
-
- havocbot_chooseenemy();
- if (self.bot_chooseweapontime < time )
- {
- self.bot_chooseweapontime = time + autocvar_bot_ai_chooseweaponinterval;
- havocbot_chooseweapon();
- }
- havocbot_aim();
- lag_update();
- if (self.bot_aimtarg)
- {
- self.aistatus |= AI_STATUS_ATTACKING;
- self.aistatus &= ~AI_STATUS_ROAMING;
-
- if(self.weapons)
- {
- Weapon w = get_weaponinfo(self.weapon);
- w.wr_aim(w);
- if (autocvar_bot_nofire || IS_INDEPENDENT_PLAYER(self))
- {
- self.BUTTON_ATCK = false;
- self.BUTTON_ATCK2 = false;
- }
- else
- {
- if(self.BUTTON_ATCK||self.BUTTON_ATCK2)
- self.lastfiredweapon = self.weapon;
- }
- }
- else
- {
- if(IS_PLAYER(self.bot_aimtarg))
- bot_aimdir(self.bot_aimtarg.origin + self.bot_aimtarg.view_ofs - self.origin - self.view_ofs , -1);
- }
- }
- else if (self.goalcurrent)
- {
- self.aistatus |= AI_STATUS_ROAMING;
- self.aistatus &= ~AI_STATUS_ATTACKING;
-
- vector now,v,next;//,heading;
- float aimdistance,skillblend,distanceblend,blend;
- next = now = ( (self.goalcurrent.absmin + self.goalcurrent.absmax) * 0.5) - (self.origin + self.view_ofs);
- aimdistance = vlen(now);
- //heading = self.velocity;
- //dprint(self.goalstack01.classname,etos(self.goalstack01),"\n");
- if(
- self.goalstack01 != self && self.goalstack01 != world && ((self.aistatus & AI_STATUS_RUNNING) == 0) &&
- !(self.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT)
- )
- next = ((self.goalstack01.absmin + self.goalstack01.absmax) * 0.5) - (self.origin + self.view_ofs);
-
- skillblend=bound(0,(skill+self.bot_moveskill-2.5)*0.5,1); //lower skill player can't preturn
- distanceblend=bound(0,aimdistance/autocvar_bot_ai_keyboard_distance,1);
- blend = skillblend * (1-distanceblend);
- //v = (now * (distanceblend) + next * (1-distanceblend)) * (skillblend) + now * (1-skillblend);
- //v = now * (distanceblend) * (skillblend) + next * (1-distanceblend) * (skillblend) + now * (1-skillblend);
- //v = now * ((1-skillblend) + (distanceblend) * (skillblend)) + next * (1-distanceblend) * (skillblend);
- v = now + blend * (next - now);
- //dprint(etos(self), " ");
- //dprint(vtos(now), ":", vtos(next), "=", vtos(v), " (blend ", ftos(blend), ")\n");
- //v = now * (distanceblend) + next * (1-distanceblend);
- if (self.waterlevel < WATERLEVEL_SWIMMING)
- v.z = 0;
- //dprint("walk at:", vtos(v), "\n");
- //te_lightning2(world, self.origin, self.goalcurrent.origin);
- bot_aimdir(v, -1);
- }
- havocbot_movetogoal();
-
- // if the bot is not attacking, consider reloading weapons
- if (!(self.aistatus & AI_STATUS_ATTACKING))
- {
- // we are currently holding a weapon that's not fully loaded, reload it
- if(skill >= 2) // bots can only reload the held weapon on purpose past this skill
- if(self.clip_load < self.clip_size)
- self.impulse = 20; // "press" the reload button, not sure if this is done right
-
- // if we're not reloading a weapon, switch to any weapon in our invnetory that's not fully loaded to reload it next
- // the code above executes next frame, starting the reloading then
- if(skill >= 5) // bots can only look for unloaded weapons past this skill
- if(self.clip_load >= 0) // only if we're not reloading a weapon already
- {
- for (int i = WEP_FIRST; i <= WEP_LAST; ++i)
- {
- entity e = get_weaponinfo(i);
- if ((self.weapons & WepSet_FromWeapon(i)) && (e.spawnflags & WEP_FLAG_RELOADABLE) && (self.weapon_load[i] < e.reloading_ammo))
- self.switchweapon = i;
- }
- }
- }
-}
-
-void havocbot_keyboard_movement(vector destorg)
-{SELFPARAM();
- vector keyboard;
- float blend, maxspeed;
- float sk;
-
- sk = skill + self.bot_moveskill;
-
- maxspeed = autocvar_sv_maxspeed;
-
- if (time < self.havocbot_keyboardtime)
- return;
-
- self.havocbot_keyboardtime =
- max(
- self.havocbot_keyboardtime
- + 0.05/max(1, sk+self.havocbot_keyboardskill)
- + random()*0.025/max(0.00025, skill+self.havocbot_keyboardskill)
- , time);
- keyboard = self.movement * (1.0 / maxspeed);
-
- float trigger, trigger1;
- blend = bound(0,sk*0.1,1);
- trigger = autocvar_bot_ai_keyboard_threshold;
- trigger1 = 0 - trigger;
-
- // categorize forward movement
- // at skill < 1.5 only forward
- // at skill < 2.5 only individual directions
- // at skill < 4.5 only individual directions, and forward diagonals
- // at skill >= 4.5, all cases allowed
- if (keyboard.x > trigger)
- {
- keyboard.x = 1;
- if (sk < 2.5)
- keyboard.y = 0;
- }
- else if (keyboard.x < trigger1 && sk > 1.5)
- {
- keyboard.x = -1;
- if (sk < 4.5)
- keyboard.y = 0;
- }
- else
- {
- keyboard.x = 0;
- if (sk < 1.5)
- keyboard.y = 0;
- }
- if (sk < 4.5)
- keyboard.z = 0;
-
- if (keyboard.y > trigger)
- keyboard.y = 1;
- else if (keyboard.y < trigger1)
- keyboard.y = -1;
- else
- keyboard.y = 0;
-
- if (keyboard.z > trigger)
- keyboard.z = 1;
- else if (keyboard.z < trigger1)
- keyboard.z = -1;
- else
- keyboard.z = 0;
-
- self.havocbot_keyboard = keyboard * maxspeed;
- if (self.havocbot_ducktime>time) self.BUTTON_CROUCH=true;
-
- keyboard = self.havocbot_keyboard;
- blend = bound(0,vlen(destorg-self.origin)/autocvar_bot_ai_keyboard_distance,1); // When getting close move with 360 degree
- //dprint("movement ", vtos(self.movement), " keyboard ", vtos(keyboard), " blend ", ftos(blend), "\n");
- self.movement = self.movement + (keyboard - self.movement) * blend;
-}
-
-void havocbot_bunnyhop(vector dir)
-{SELFPARAM();
- float bunnyhopdistance;
- vector deviation;
- float maxspeed;
- vector gco, gno;
-
- // Don't jump when attacking
- if(self.aistatus & AI_STATUS_ATTACKING)
- return;
-
- if(IS_PLAYER(self.goalcurrent))
- return;
-
- maxspeed = autocvar_sv_maxspeed;
-
- if(self.aistatus & AI_STATUS_DANGER_AHEAD)
- {
- self.aistatus &= ~AI_STATUS_RUNNING;
- self.BUTTON_JUMP = false;
- self.bot_canruntogoal = 0;
- self.bot_timelastseengoal = 0;
- return;
- }
-
- if(self.waterlevel > WATERLEVEL_WETFEET)
- {
- self.aistatus &= ~AI_STATUS_RUNNING;
- return;
- }
-
- if(self.bot_lastseengoal != self.goalcurrent && !(self.aistatus & AI_STATUS_RUNNING))
- {
- self.bot_canruntogoal = 0;
- self.bot_timelastseengoal = 0;
- }
-
- gco = (self.goalcurrent.absmin + self.goalcurrent.absmax) * 0.5;
- bunnyhopdistance = vlen(self.origin - gco);
-
- // Run only to visible goals
- if(self.flags & FL_ONGROUND)
- if(self.speed==maxspeed)
- if(checkpvs(self.origin + self.view_ofs, self.goalcurrent))
- {
- self.bot_lastseengoal = self.goalcurrent;
-
- // seen it before
- if(self.bot_timelastseengoal)
- {
- // for a period of time
- if(time - self.bot_timelastseengoal > autocvar_bot_ai_bunnyhop_firstjumpdelay)
- {
- float checkdistance;
- checkdistance = true;
-
- // don't run if it is too close
- if(self.bot_canruntogoal==0)
- {
- if(bunnyhopdistance > autocvar_bot_ai_bunnyhop_startdistance)
- self.bot_canruntogoal = 1;
- else
- self.bot_canruntogoal = -1;
- }
-
- if(self.bot_canruntogoal != 1)
- return;
-
- if(self.aistatus & AI_STATUS_ROAMING)
- if(self.goalcurrent.classname=="waypoint")
- if (!(self.goalcurrent.wpflags & WAYPOINTFLAG_PERSONAL))
- if(fabs(gco.z - self.origin.z) < self.maxs.z - self.mins.z)
- if(self.goalstack01!=world)
- {
- gno = (self.goalstack01.absmin + self.goalstack01.absmax) * 0.5;
- deviation = vectoangles(gno - self.origin) - vectoangles(gco - self.origin);
- while (deviation.y < -180) deviation.y = deviation.y + 360;
- while (deviation.y > 180) deviation.y = deviation.y - 360;
-
- if(fabs(deviation.y) < 20)
- if(bunnyhopdistance < vlen(self.origin - gno))
- if(fabs(gno.z - gco.z) < self.maxs.z - self.mins.z)
- {
- if(vlen(gco - gno) > autocvar_bot_ai_bunnyhop_startdistance)
- if(checkpvs(self.origin + self.view_ofs, self.goalstack01))
- {
- checkdistance = false;
- }
- }
- }
-
- if(checkdistance)
- {
- self.aistatus &= ~AI_STATUS_RUNNING;
- if(bunnyhopdistance > autocvar_bot_ai_bunnyhop_stopdistance)
- self.BUTTON_JUMP = true;
- }
- else
- {
- self.aistatus |= AI_STATUS_RUNNING;
- self.BUTTON_JUMP = true;
- }
- }
- }
- else
- {
- self.bot_timelastseengoal = time;
- }
- }
- else
- {
- self.bot_timelastseengoal = 0;
- }
-
-#if 0
- // Release jump button
- if(!cvar("sv_pogostick"))
- if((self.flags & FL_ONGROUND) == 0)
- {
- if(self.velocity.z < 0 || vlen(self.velocity)<maxspeed)
- self.BUTTON_JUMP = false;
-
- // Strafe
- if(self.aistatus & AI_STATUS_RUNNING)
- if(vlen(self.velocity)>maxspeed)
- {
- deviation = vectoangles(dir) - vectoangles(self.velocity);
- while (deviation.y < -180) deviation.y = deviation.y + 360;
- while (deviation.y > 180) deviation.y = deviation.y - 360;
-
- if(fabs(deviation.y)>10)
- self.movement_x = 0;
-
- if(deviation.y>10)
- self.movement_y = maxspeed * -1;
- else if(deviation.y<10)
- self.movement_y = maxspeed;
-
- }
- }
-#endif
-}
-
-void havocbot_movetogoal()
-{SELFPARAM();
- vector destorg;
- vector diff;
- vector dir;
- vector flatdir;
- vector m1;
- vector m2;
- vector evadeobstacle;
- vector evadelava;
- float s;
- float maxspeed;
- vector gco;
- //float dist;
- vector dodge;
- //if (self.goalentity)
- // te_lightning2(self, self.origin, (self.goalentity.absmin + self.goalentity.absmax) * 0.5);
- self.movement = '0 0 0';
- maxspeed = autocvar_sv_maxspeed;
-
- // Jetpack navigation
- if(self.goalcurrent)
- if(self.navigation_jetpack_goal)
- if(self.goalcurrent==self.navigation_jetpack_goal)
- if(self.ammo_fuel)
- {
- if(autocvar_bot_debug_goalstack)
- {
- debuggoalstack();
- te_wizspike(self.navigation_jetpack_point);
- }
-
- // Take off
- if (!(self.aistatus & AI_STATUS_JETPACK_FLYING))
- {
- // Brake almost completely so it can get a good direction
- if(vlen(self.velocity)>10)
- return;
- self.aistatus |= AI_STATUS_JETPACK_FLYING;
- }
-
- makevectors(self.v_angle.y * '0 1 0');
- dir = normalize(self.navigation_jetpack_point - self.origin);
-
- // Landing
- if(self.aistatus & AI_STATUS_JETPACK_LANDING)
- {
- // Calculate brake distance in xy
- float db, v, d;
- vector dxy;
-
- dxy = self.origin - ( ( self.goalcurrent.absmin + self.goalcurrent.absmax ) * 0.5 ); dxy.z = 0;
- d = vlen(dxy);
- v = vlen(self.velocity - self.velocity.z * '0 0 1');
- db = (pow(v,2) / (autocvar_g_jetpack_acceleration_side * 2)) + 100;
- // dprint("distance ", ftos(ceil(d)), " velocity ", ftos(ceil(v)), " brake at ", ftos(ceil(db)), "\n");
- if(d < db || d < 500)
- {
- // Brake
- if(fabs(self.velocity.x)>maxspeed*0.3)
- {
- self.movement_x = dir * v_forward * -maxspeed;
- return;
- }
- // Switch to normal mode
- self.navigation_jetpack_goal = world;
- self.aistatus &= ~AI_STATUS_JETPACK_LANDING;
- self.aistatus &= ~AI_STATUS_JETPACK_FLYING;
- return;
- }
- }
- else if(checkpvs(self.origin,self.goalcurrent))
- {
- // If I can see the goal switch to landing code
- self.aistatus &= ~AI_STATUS_JETPACK_FLYING;
- self.aistatus |= AI_STATUS_JETPACK_LANDING;
- return;
- }
-
- // Flying
- self.BUTTON_HOOK = true;
- if(self.navigation_jetpack_point.z - PL_MAX.z + PL_MIN.z < self.origin.z)
- {
- self.movement_x = dir * v_forward * maxspeed;
- self.movement_y = dir * v_right * maxspeed;
- }
- return;
- }
-
- // Handling of jump pads
- if(self.jumppadcount)
- {
- // If got stuck on the jump pad try to reach the farthest visible waypoint
- if(self.aistatus & AI_STATUS_OUT_JUMPPAD)
- {
- if(fabs(self.velocity.z)<50)
- {
- entity head, newgoal = world;
- float distance, bestdistance = 0;
-
- for (head = findchain(classname, "waypoint"); head; head = head.chain)
- {
-
- distance = vlen(head.origin - self.origin);
- if(distance>1000)
- continue;
-
- traceline(self.origin + self.view_ofs , ( ( head.absmin + head.absmax ) * 0.5 ), true, world);
-
- if(trace_fraction<1)
- continue;
-
- if(distance>bestdistance)
- {
- newgoal = head;
- bestdistance = distance;
- }
- }
-
- if(newgoal)
- {
- self.ignoregoal = self.goalcurrent;
- self.ignoregoaltime = time + autocvar_bot_ai_ignoregoal_timeout;
- navigation_clearroute();
- navigation_routetogoal(newgoal, self.origin);
- self.aistatus &= ~AI_STATUS_OUT_JUMPPAD;
- }
- }
- else
- return;
- }
- else
- {
- if(self.velocity.z>0)
- {
- float threshold, sxy;
- vector velxy = self.velocity; velxy_z = 0;
- sxy = vlen(velxy);
- threshold = maxspeed * 0.2;
- if(sxy < threshold)
- {
- LOG_TRACE("Warning: ", self.netname, " got stuck on a jumppad (velocity in xy is ", ftos(sxy), "), trying to get out of it now\n");
- self.aistatus |= AI_STATUS_OUT_JUMPPAD;
- }
- return;
- }
-
- // Don't chase players while using a jump pad
- if(IS_PLAYER(self.goalcurrent) || IS_PLAYER(self.goalstack01))
- return;
- }
- }
- else if(self.aistatus & AI_STATUS_OUT_JUMPPAD)
- self.aistatus &= ~AI_STATUS_OUT_JUMPPAD;
-
- // If there is a trigger_hurt right below try to use the jetpack or make a rocketjump
- if(skill>6)
- if (!(self.flags & FL_ONGROUND))
- {
- tracebox(self.origin, self.mins, self.maxs, self.origin + '0 0 -65536', MOVE_NOMONSTERS, self);
- if(tracebox_hits_trigger_hurt(self.origin, self.mins, self.maxs, trace_endpos ))
- if(self.items & IT_JETPACK)
- {
- tracebox(self.origin, self.mins, self.maxs, self.origin + '0 0 65536', MOVE_NOMONSTERS, self);
- if(tracebox_hits_trigger_hurt(self.origin, self.mins, self.maxs, trace_endpos + '0 0 1' ))
- {
- if(self.velocity.z<0)
- {
- self.BUTTON_HOOK = true;
- }
- }
- else
- self.BUTTON_HOOK = true;
-
- // If there is no goal try to move forward
-
- if(self.goalcurrent==world)
- dir = v_forward;
- else
- dir = normalize(( ( self.goalcurrent.absmin + self.goalcurrent.absmax ) * 0.5 ) - self.origin);
-
- vector xyvelocity = self.velocity; xyvelocity_z = 0;
- float xyspeed = xyvelocity * dir;
-
- if(xyspeed < (maxspeed / 2))
- {
- makevectors(self.v_angle.y * '0 1 0');
- tracebox(self.origin, self.mins, self.maxs, self.origin + (dir * maxspeed * 3), MOVE_NOMONSTERS, self);
- if(trace_fraction==1)
- {
- self.movement_x = dir * v_forward * maxspeed;
- self.movement_y = dir * v_right * maxspeed;
- if (skill < 10)
- havocbot_keyboard_movement(self.origin + dir * 100);
- }
- }
-
- self.havocbot_blockhead = true;
-
- return;
- }
- else if(self.health>WEP_CVAR(devastator, damage)*0.5)
- {
- if(self.velocity.z < 0)
- if(client_hasweapon(self, WEP_DEVASTATOR.m_id, true, false))
- {
- self.movement_x = maxspeed;
-
- if(self.rocketjumptime)
- {
- if(time > self.rocketjumptime)
- {
- self.BUTTON_ATCK2 = true;
- self.rocketjumptime = 0;
- }
- return;
- }
-
- self.switchweapon = WEP_DEVASTATOR.m_id;
- self.v_angle_x = 90;
- self.BUTTON_ATCK = true;
- self.rocketjumptime = time + WEP_CVAR(devastator, detonatedelay);
- return;
- }
- }
- else
- {
- // If there is no goal try to move forward
- if(self.goalcurrent==world)
- self.movement_x = maxspeed;
- }
- }
-
- // If we are under water with no goals, swim up
- if(self.waterlevel)
- if(self.goalcurrent==world)
- {
- dir = '0 0 0';
- if(self.waterlevel>WATERLEVEL_SWIMMING)
- dir.z = 1;
- else if(self.velocity.z >= 0 && !(self.waterlevel == WATERLEVEL_WETFEET && self.watertype == CONTENT_WATER))
- self.BUTTON_JUMP = true;
- else
- self.BUTTON_JUMP = false;
- makevectors(self.v_angle.y * '0 1 0');
- self.movement_x = dir * v_forward * maxspeed;
- self.movement_y = dir * v_right * maxspeed;
- self.movement_z = dir * v_up * maxspeed;
- }
-
- // if there is nowhere to go, exit
- if (self.goalcurrent == world)
- return;
-
- if (self.goalcurrent)
- navigation_poptouchedgoals();
-
- // if ran out of goals try to use an alternative goal or get a new strategy asap
- if(self.goalcurrent == world)
- {
- self.bot_strategytime = 0;
- return;
- }
-
-
- if(autocvar_bot_debug_goalstack)
- debuggoalstack();
-
- m1 = self.goalcurrent.origin + self.goalcurrent.mins;
- m2 = self.goalcurrent.origin + self.goalcurrent.maxs;
- destorg = self.origin;
- destorg.x = bound(m1_x, destorg.x, m2_x);
- destorg.y = bound(m1_y, destorg.y, m2_y);
- destorg.z = bound(m1_z, destorg.z, m2_z);
- diff = destorg - self.origin;
- //dist = vlen(diff);
- dir = normalize(diff);
- flatdir = diff;flatdir.z = 0;
- flatdir = normalize(flatdir);
- gco = (self.goalcurrent.absmin + self.goalcurrent.absmax) * 0.5;
-
- //if (self.bot_dodgevector_time < time)
- {
- // self.bot_dodgevector_time = time + cvar("bot_ai_dodgeupdateinterval");
- // self.bot_dodgevector_jumpbutton = 1;
- evadeobstacle = '0 0 0';
- evadelava = '0 0 0';
-
- if (self.waterlevel)
- {
- if(self.waterlevel>WATERLEVEL_SWIMMING)
- {
- // flatdir_z = 1;
- self.aistatus |= AI_STATUS_OUT_WATER;
- }
- else
- {
- if(self.velocity.z >= 0 && !(self.watertype == CONTENT_WATER && gco.z < self.origin.z) &&
- ( !(self.waterlevel == WATERLEVEL_WETFEET && self.watertype == CONTENT_WATER) || self.aistatus & AI_STATUS_OUT_WATER))
- self.BUTTON_JUMP = true;
- else
- self.BUTTON_JUMP = false;
- }
- dir = normalize(flatdir);
- makevectors(self.v_angle.y * '0 1 0');
- }
- else
- {
- if(self.aistatus & AI_STATUS_OUT_WATER)
- self.aistatus &= ~AI_STATUS_OUT_WATER;
-
- // jump if going toward an obstacle that doesn't look like stairs we
- // can walk up directly
- tracebox(self.origin, self.mins, self.maxs, self.origin + self.velocity * 0.2, false, self);
- if (trace_fraction < 1)
- if (trace_plane_normal.z < 0.7)
- {
- s = trace_fraction;
- tracebox(self.origin + stepheightvec, self.mins, self.maxs, self.origin + self.velocity * 0.2 + stepheightvec, false, self);
- if (trace_fraction < s + 0.01)
- if (trace_plane_normal.z < 0.7)
- {
- s = trace_fraction;
- tracebox(self.origin + jumpstepheightvec, self.mins, self.maxs, self.origin + self.velocity * 0.2 + jumpstepheightvec, false, self);
- if (trace_fraction > s)
- self.BUTTON_JUMP = 1;
- }
- }
-
- // avoiding dangers and obstacles
- vector dst_ahead, dst_down;
- makevectors(self.v_angle.y * '0 1 0');
- dst_ahead = self.origin + self.view_ofs + (self.velocity * 0.4) + (v_forward * 32 * 3);
- dst_down = dst_ahead - '0 0 1500';
-
- // Look ahead
- traceline(self.origin + self.view_ofs, dst_ahead, true, world);
-
- // Check head-banging against walls
- if(vlen(self.origin + self.view_ofs - trace_endpos) < 25 && !(self.aistatus & AI_STATUS_OUT_WATER))
- {
- self.BUTTON_JUMP = true;
- if(self.facingwalltime && time > self.facingwalltime)
- {
- self.ignoregoal = self.goalcurrent;
- self.ignoregoaltime = time + autocvar_bot_ai_ignoregoal_timeout;
- self.bot_strategytime = 0;
- return;
- }
- else
- {
- self.facingwalltime = time + 0.05;
- }
- }
- else
- {
- self.facingwalltime = 0;
-
- if(self.ignoregoal != world && time > self.ignoregoaltime)
- {
- self.ignoregoal = world;
- self.ignoregoaltime = 0;
- }
- }
-
- // Check for water/slime/lava and dangerous edges
- // (only when the bot is on the ground or jumping intentionally)
- self.aistatus &= ~AI_STATUS_DANGER_AHEAD;
-
- if(trace_fraction == 1 && self.jumppadcount == 0 && !self.goalcurrent.wphardwired )
- if((self.flags & FL_ONGROUND) || (self.aistatus & AI_STATUS_RUNNING) || self.BUTTON_JUMP == true)
- {
- // Look downwards
- traceline(dst_ahead , dst_down, true, world);
- // te_lightning2(world, self.origin, dst_ahead); // Draw "ahead" look
- // te_lightning2(world, dst_ahead, dst_down); // Draw "downwards" look
- if(trace_endpos.z < self.origin.z + self.mins.z)
- {
- s = pointcontents(trace_endpos + '0 0 1');
- if (s != CONTENT_SOLID)
- if (s == CONTENT_LAVA || s == CONTENT_SLIME)
- evadelava = normalize(self.velocity) * -1;
- else if (s == CONTENT_SKY)
- evadeobstacle = normalize(self.velocity) * -1;
- else if (!boxesoverlap(dst_ahead - self.view_ofs + self.mins, dst_ahead - self.view_ofs + self.maxs,
- self.goalcurrent.absmin, self.goalcurrent.absmax))
- {
- // if ain't a safe goal with "holes" (like the jumpad on soylent)
- // and there is a trigger_hurt below
- if(tracebox_hits_trigger_hurt(dst_ahead, self.mins, self.maxs, trace_endpos))
- {
- // Remove dangerous dynamic goals from stack
- LOG_TRACE("bot ", self.netname, " avoided the goal ", self.goalcurrent.classname, " ", etos(self.goalcurrent), " because it led to a dangerous path; goal stack cleared\n");
- navigation_clearroute();
- return;
- }
- }
- }
- }
-
- dir = flatdir;
- evadeobstacle.z = 0;
- evadelava.z = 0;
- makevectors(self.v_angle.y * '0 1 0');
-
- if(evadeobstacle!='0 0 0'||evadelava!='0 0 0')
- self.aistatus |= AI_STATUS_DANGER_AHEAD;
- }
-
- dodge = havocbot_dodge();
- dodge = dodge * bound(0,0.5+(skill+self.bot_dodgeskill)*0.1,1);
- evadelava = evadelava * bound(1,3-(skill+self.bot_dodgeskill),3); //Noobs fear lava a lot and take more distance from it
- traceline(self.origin, ( ( self.enemy.absmin + self.enemy.absmax ) * 0.5 ), true, world);
- if(IS_PLAYER(trace_ent))
- dir = dir * bound(0,(skill+self.bot_dodgeskill)/7,1);
-
- dir = normalize(dir + dodge + evadeobstacle + evadelava);
- // self.bot_dodgevector = dir;
- // self.bot_dodgevector_jumpbutton = self.BUTTON_JUMP;
- }
-
- if(time < self.ladder_time)
- {
- if(self.goalcurrent.origin.z + self.goalcurrent.mins.z > self.origin.z + self.mins.z)
- {
- if(self.origin.z + self.mins.z < self.ladder_entity.origin.z + self.ladder_entity.maxs.z)
- dir.z = 1;
- }
- else
- {
- if(self.origin.z + self.mins.z > self.ladder_entity.origin.z + self.ladder_entity.mins.z)
- dir.z = -1;
- }
- }
-
- //dir = self.bot_dodgevector;
- //if (self.bot_dodgevector_jumpbutton)
- // self.BUTTON_JUMP = 1;
- self.movement_x = dir * v_forward * maxspeed;
- self.movement_y = dir * v_right * maxspeed;
- self.movement_z = dir * v_up * maxspeed;
-
- // Emulate keyboard interface
- if (skill < 10)
- havocbot_keyboard_movement(destorg);
-
- // Bunnyhop!
-// if(self.aistatus & AI_STATUS_ROAMING)
- if(self.goalcurrent)
- if(skill+self.bot_moveskill >= autocvar_bot_ai_bunnyhop_skilloffset)
- havocbot_bunnyhop(dir);
-
- if ((dir * v_up) >= autocvar_sv_jumpvelocity*0.5 && (self.flags & FL_ONGROUND)) self.BUTTON_JUMP=1;
- if (((dodge * v_up) > 0) && random()*frametime >= 0.2*bound(0,(10-skill-self.bot_dodgeskill)*0.1,1)) self.BUTTON_JUMP=true;
- if (((dodge * v_up) < 0) && random()*frametime >= 0.5*bound(0,(10-skill-self.bot_dodgeskill)*0.1,1)) self.havocbot_ducktime=time+0.3/bound(0.1,skill+self.bot_dodgeskill,10);
-}
-
-void havocbot_chooseenemy()
-{SELFPARAM();
- entity head, best, head2;
- float rating, bestrating, hf;
- vector eye, v;
- if (autocvar_bot_nofire || IS_INDEPENDENT_PLAYER(self))
- {
- self.enemy = world;
- return;
- }
- if (self.enemy)
- {
- if (!bot_shouldattack(self.enemy))
- {
- // enemy died or something, find a new target
- self.enemy = world;
- self.havocbot_chooseenemy_finished = time;
- }
- else if (self.havocbot_stickenemy)
- {
- // tracking last chosen enemy
- // if enemy is visible
- // and not really really far away
- // and we're not severely injured
- // then keep tracking for a half second into the future
- traceline(self.origin+self.view_ofs, ( self.enemy.absmin + self.enemy.absmax ) * 0.5,false,world);
- if (trace_ent == self.enemy || trace_fraction == 1)
- if (vlen((( self.enemy.absmin + self.enemy.absmax ) * 0.5) - self.origin) < 1000)
- if (self.health > 30)
- {
- // remain tracking him for a shot while (case he went after a small corner or pilar
- self.havocbot_chooseenemy_finished = time + 0.5;
- return;
- }
- // enemy isn't visible, or is far away, or we're injured severely
- // so stop preferring this enemy
- // (it will still take a half second until a new one is chosen)
- self.havocbot_stickenemy = 0;
- }
- }
- if (time < self.havocbot_chooseenemy_finished)
- return;
- self.havocbot_chooseenemy_finished = time + autocvar_bot_ai_enemydetectioninterval;
- eye = self.origin + self.view_ofs;
- best = world;
- bestrating = 100000000;
- head = head2 = findchainfloat(bot_attack, true);
-
- // Backup hit flags
- hf = self.dphitcontentsmask;
-
- // Search for enemies, if no enemy can be seen directly try to look through transparent objects
-
- self.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_CORPSE;
-
- bool scan_transparent = false;
- bool scan_secondary_targets = false;
- bool have_secondary_targets = false;
- while(true)
- {
- scan_secondary_targets = false;
- :scan_targets
- for( ; head; head = head.chain)
- {
- if(!scan_secondary_targets)
- {
- if(head.classname == "misc_breakablemodel")
- {
- have_secondary_targets = true;
- continue;
- }
- }
- else
- {
- if(head.classname != "misc_breakablemodel")
- continue;
- }
-
- v = (head.absmin + head.absmax) * 0.5;
- rating = vlen(v - eye);
- if (rating<autocvar_bot_ai_enemydetectionradius)
- if (bestrating > rating)
- if (bot_shouldattack(head))
- {
- traceline(eye, v, true, self);
- if (trace_ent == head || trace_fraction >= 1)
- {
- best = head;
- bestrating = rating;
- }
- }
- }
-
- if(!best && have_secondary_targets && !scan_secondary_targets)
- {
- scan_secondary_targets = true;
- // restart the loop
- head = head2;
- bestrating = 100000000;
- goto scan_targets;
- }
-
- // I want to do a second scan if no enemy was found or I don't have weapons
- // TODO: Perform the scan when using the rifle (requires changes on the rifle code)
- if(best || self.weapons) // || self.weapon == WEP_RIFLE.m_id
- break;
- if(scan_transparent)
- break;
-
- // Set flags to see through transparent objects
- self.dphitcontentsmask |= DPCONTENTS_OPAQUE;
-
- head = head2;
- scan_transparent = true;
- }
-
- // Restore hit flags
- self.dphitcontentsmask = hf;
-
- self.enemy = best;
- self.havocbot_stickenemy = true;
- if(best && best.classname == "misc_breakablemodel")
- self.havocbot_stickenemy = false;
-}
-
-float havocbot_chooseweapon_checkreload(int new_weapon)
-{SELFPARAM();
- // bots under this skill cannot find unloaded weapons to reload idly when not in combat,
- // so skip this for them, or they'll never get to reload their weapons at all.
- // this also allows bots under this skill to be more stupid, and reload more often during combat :)
- if(skill < 5)
- return false;
-
- // if this weapon is scheduled for reloading, don't switch to it during combat
- if (self.weapon_load[new_weapon] < 0)
- {
- float i, other_weapon_available = false;
- for(i = WEP_FIRST; i <= WEP_LAST; ++i)
- {
- Weapon w = get_weaponinfo(i);
- // if we are out of ammo for all other weapons, it's an emergency to switch to anything else
- if (w.wr_checkammo1(w) + w.wr_checkammo2(w))
- other_weapon_available = true;
- }
- if(other_weapon_available)
- return true;
- }
-
- return false;
-}
-
-void havocbot_chooseweapon()
-{SELFPARAM();
- int i;
-
- // ;)
- if(g_weaponarena_weapons == WEPSET(TUBA))
- {
- self.switchweapon = WEP_TUBA.m_id;
- return;
- }
-
- // TODO: clean this up by moving it to weapon code
- if(self.enemy==world)
- {
- // If no weapon was chosen get the first available weapon
- if(self.weapon==0)
- for(i = WEP_FIRST; i <= WEP_LAST; ++i) if(i != WEP_BLASTER.m_id)
- {
- if(client_hasweapon(self, i, true, false))
- {
- self.switchweapon = i;
- return;
- }
- }
- return;
- }
-
- // Do not change weapon during the next second after a combo
- float f = time - self.lastcombotime;
- if(f < 1)
- return;
-
- float w;
- float distance; distance=bound(10,vlen(self.origin-self.enemy.origin)-200,10000);
-
- // Should it do a weapon combo?
- float af, ct, combo_time, combo;
-
- af = ATTACK_FINISHED(self);
- ct = autocvar_bot_ai_weapon_combo_threshold;
-
- // Bots with no skill will be 4 times more slower than "godlike" bots when doing weapon combos
- // Ideally this 4 should be calculated as longest_weapon_refire / bot_ai_weapon_combo_threshold
- combo_time = time + ct + (ct * ((-0.3*(skill+self.bot_weaponskill))+3));
-
- combo = false;
-
- if(autocvar_bot_ai_weapon_combo)
- if(self.weapon == self.lastfiredweapon)
- if(af > combo_time)
- {
- combo = true;
- self.lastcombotime = time;
- }
-
- distance *= pow(2, self.bot_rangepreference);
-
- // Custom weapon list based on distance to the enemy
- if(bot_custom_weapon){
-
- // Choose weapons for far distance
- if ( distance > bot_distance_far ) {
- for(i=0; i < Weapons_COUNT && bot_weapons_far[i] != -1 ; ++i){
- w = bot_weapons_far[i];
- if ( client_hasweapon(self, w, true, false) )
- {
- if ((self.weapon == w && combo) || havocbot_chooseweapon_checkreload(w))
- continue;
- self.switchweapon = w;
- return;
- }
- }
- }
-
- // Choose weapons for mid distance
- if ( distance > bot_distance_close) {
- for(i=0; i < Weapons_COUNT && bot_weapons_mid[i] != -1 ; ++i){
- w = bot_weapons_mid[i];
- if ( client_hasweapon(self, w, true, false) )
- {
- if ((self.weapon == w && combo) || havocbot_chooseweapon_checkreload(w))
- continue;
- self.switchweapon = w;
- return;
- }
- }
- }
-
- // Choose weapons for close distance
- for(i=0; i < Weapons_COUNT && bot_weapons_close[i] != -1 ; ++i){
- w = bot_weapons_close[i];
- if ( client_hasweapon(self, w, true, false) )
- {
- if ((self.weapon == w && combo) || havocbot_chooseweapon_checkreload(w))
- continue;
- self.switchweapon = w;
- return;
- }
- }
- }
-}
-
-void havocbot_aim()
-{SELFPARAM();
- vector selfvel, enemyvel;
-// if(self.flags & FL_INWATER)
-// return;
- if (time < self.nextaim)
- return;
- self.nextaim = time + 0.1;
- selfvel = self.velocity;
- if (!self.waterlevel)
- selfvel.z = 0;
- if (self.enemy)
- {
- enemyvel = self.enemy.velocity;
- if (!self.enemy.waterlevel)
- enemyvel.z = 0;
- lag_additem(time + self.ping, 0, 0, self.enemy, self.origin, selfvel, (self.enemy.absmin + self.enemy.absmax) * 0.5, enemyvel);
- }
- else
- lag_additem(time + self.ping, 0, 0, world, self.origin, selfvel, ( self.goalcurrent.absmin + self.goalcurrent.absmax ) * 0.5, '0 0 0');
-}
-
-float havocbot_moveto_refresh_route()
-{SELFPARAM();
- // Refresh path to goal if necessary
- entity wp;
- wp = self.havocbot_personal_waypoint;
- navigation_goalrating_start();
- navigation_routerating(wp, 10000, 10000);
- navigation_goalrating_end();
- return self.navigation_hasgoals;
-}
-
-float havocbot_moveto(vector pos)
-{SELFPARAM();
- entity wp;
-
- if(self.aistatus & AI_STATUS_WAYPOINT_PERSONAL_GOING)
- {
- // Step 4: Move to waypoint
- if(self.havocbot_personal_waypoint==world)
- {
- LOG_TRACE("Error: ", self.netname, " trying to walk to a non existent personal waypoint\n");
- self.aistatus &= ~AI_STATUS_WAYPOINT_PERSONAL_GOING;
- return CMD_STATUS_ERROR;
- }
-
- if (!bot_strategytoken_taken)
- if(self.havocbot_personal_waypoint_searchtime<time)
- {
- bot_strategytoken_taken = true;
- if(havocbot_moveto_refresh_route())
- {
- LOG_TRACE(self.netname, " walking to its personal waypoint (after ", ftos(self.havocbot_personal_waypoint_failcounter), " failed attempts)\n");
- self.havocbot_personal_waypoint_searchtime = time + 10;
- self.havocbot_personal_waypoint_failcounter = 0;
- }
- else
- {
- self.havocbot_personal_waypoint_failcounter += 1;
- self.havocbot_personal_waypoint_searchtime = time + 2;
- if(self.havocbot_personal_waypoint_failcounter >= 30)
- {
- LOG_TRACE("Warning: can't walk to the personal waypoint located at ", vtos(self.havocbot_personal_waypoint.origin),"\n");
- self.aistatus &= ~AI_STATUS_WAYPOINT_PERSONAL_LINKING;
- remove(self.havocbot_personal_waypoint);
- return CMD_STATUS_ERROR;
- }
- else
- LOG_TRACE(self.netname, " can't walk to its personal waypoint (after ", ftos(self.havocbot_personal_waypoint_failcounter), " failed attempts), trying later\n");
- }
- }
-
- if(autocvar_bot_debug_goalstack)
- debuggoalstack();
-
- // Heading
- vector dir = ( ( self.goalcurrent.absmin + self.goalcurrent.absmax ) * 0.5 ) - (self.origin + self.view_ofs);
- dir.z = 0;
- bot_aimdir(dir, -1);
-
- // Go!
- havocbot_movetogoal();
-
- if(self.aistatus & AI_STATUS_WAYPOINT_PERSONAL_REACHED)
- {
- // Step 5: Waypoint reached
- LOG_TRACE(self.netname, "'s personal waypoint reached\n");
- remove(self.havocbot_personal_waypoint);
- self.aistatus &= ~AI_STATUS_WAYPOINT_PERSONAL_REACHED;
- return CMD_STATUS_FINISHED;
- }
-
- return CMD_STATUS_EXECUTING;
- }
-
- // Step 2: Linking waypoint
- if(self.aistatus & AI_STATUS_WAYPOINT_PERSONAL_LINKING)
- {
- // Wait until it is linked
- if(!self.havocbot_personal_waypoint.wplinked)
- {
- LOG_TRACE(self.netname, " waiting for personal waypoint to be linked\n");
- return CMD_STATUS_EXECUTING;
- }
-
- self.havocbot_personal_waypoint_searchtime = time; // so we set the route next frame
- self.aistatus &= ~AI_STATUS_WAYPOINT_PERSONAL_LINKING;
- self.aistatus |= AI_STATUS_WAYPOINT_PERSONAL_GOING;
-
- // Step 3: Route to waypoint
- LOG_TRACE(self.netname, " walking to its personal waypoint\n");
-
- return CMD_STATUS_EXECUTING;
- }
-
- // Step 1: Spawning waypoint
- wp = waypoint_spawnpersonal(pos);
- if(wp==world)
- {
- LOG_TRACE("Error: Can't spawn personal waypoint at ",vtos(pos),"\n");
- return CMD_STATUS_ERROR;
- }
-
- self.havocbot_personal_waypoint = wp;
- self.havocbot_personal_waypoint_failcounter = 0;
- self.aistatus |= AI_STATUS_WAYPOINT_PERSONAL_LINKING;
-
- // if pos is inside a teleport, then let's mark it as teleport waypoint
- entity head;
- for(head = world; (head = find(head, classname, "trigger_teleport")); )
- {
- if(WarpZoneLib_BoxTouchesBrush(pos, pos, head, world))
- {
- wp.wpflags |= WAYPOINTFLAG_TELEPORT;
- self.lastteleporttime = 0;
- }
- }
-
-/*
- if(wp.wpflags & WAYPOINTFLAG_TELEPORT)
- print("routing to a teleporter\n");
- else
- print("routing to a non-teleporter\n");
-*/
-
- return CMD_STATUS_EXECUTING;
-}
-
-float havocbot_resetgoal()
-{
- navigation_clearroute();
- return CMD_STATUS_FINISHED;
-}
-
-void havocbot_setupbot()
-{SELFPARAM();
- self.bot_ai = havocbot_ai;
- self.cmd_moveto = havocbot_moveto;
- self.cmd_resetgoal = havocbot_resetgoal;
-
- havocbot_chooserole();
-}
-
-vector havocbot_dodge()
-{SELFPARAM();
- // LordHavoc: disabled because this is too expensive
- return '0 0 0';
-#if 0
- entity head;
- vector dodge, v, n;
- float danger, bestdanger, vl, d;
- dodge = '0 0 0';
- bestdanger = -20;
- // check for dangerous objects near bot or approaching bot
- head = findchainfloat(bot_dodge, true);
- while(head)
- {
- if (head.owner != self)
- {
- vl = vlen(head.velocity);
- if (vl > autocvar_sv_maxspeed * 0.3)
- {
- n = normalize(head.velocity);
- v = self.origin - head.origin;
- d = v * n;
- if (d > (0 - head.bot_dodgerating))
- if (d < (vl * 0.2 + head.bot_dodgerating))
- {
- // calculate direction and distance from the flight path, by removing the forward axis
- v = v - (n * (v * n));
- danger = head.bot_dodgerating - vlen(v);
- if (bestdanger < danger)
- {
- bestdanger = danger;
- // dodge to the side of the object
- dodge = normalize(v);
- }
- }
- }
- else
- {
- danger = head.bot_dodgerating - vlen(head.origin - self.origin);
- if (bestdanger < danger)
- {
- bestdanger = danger;
- dodge = normalize(self.origin - head.origin);
- }
- }
- }
- head = head.chain;
- }
- return dodge;
-#endif
-}
+++ /dev/null
-#ifndef HAVOCBOT_H
-#define HAVOCBOT_H
-
-/*
- * Globals and Fields
- */
-
-.float havocbot_keyboardskill;
-.float facingwalltime, ignoregoaltime;
-.float lastfiredweapon;
-.float lastcombotime;
-.float havocbot_blockhead;
-
-.float havocbot_keyboardtime;
-.float havocbot_ducktime;
-.float bot_timelastseengoal;
-.float bot_canruntogoal;
-.float bot_chooseweapontime;
-.float rocketjumptime;
-.float nextaim;
-.float havocbot_personal_waypoint_searchtime;
-.float havocbot_personal_waypoint_failcounter;
-.float havocbot_chooseenemy_finished;
-.float havocbot_stickenemy;
-.float havocbot_role_timeout;
-
-.entity ignoregoal;
-.entity bot_lastseengoal;
-.entity havocbot_personal_waypoint;
-
-.vector havocbot_keyboard;
-
-/*
- * Functions
- */
-
-void havocbot_ai();
-void havocbot_aim();
-void havocbot_setupbot();
-void havocbot_movetogoal();
-void havocbot_chooserole();
-void havocbot_chooseenemy();
-void havocbot_chooseweapon();
-void havocbot_bunnyhop(vector dir);
-void havocbot_keyboard_movement(vector destorg);
-
-float havocbot_resetgoal();
-float havocbot_moveto(vector pos);
-float havocbot_moveto_refresh_route();
-
-vector havocbot_dodge();
-
-.void() havocbot_role;
-.void() havocbot_previous_role;
-
-void(float ratingscale, vector org, float sradius) havocbot_goalrating_items;
-void(float ratingscale, vector org, float sradius) havocbot_goalrating_enemyplayers;
-
-/*
- * Imports
- */
-
-.entity draggedby;
-.float ladder_time;
-.entity ladder_entity;
-#endif
+++ /dev/null
-#include "role_keyhunt.qh"
-#include "../../_all.qh"
-
-#include "havocbot.qh"
-
-#include "../bot.qh"
-#include "../navigation.qh"
-
-#include "../../mutators/mutators_include.qh"
-
-void() havocbot_role_kh_carrier;
-void() havocbot_role_kh_defense;
-void() havocbot_role_kh_offense;
-void() havocbot_role_kh_freelancer;
-
-
-void havocbot_goalrating_kh(float ratingscale_team, float ratingscale_dropped, float ratingscale_enemy)
-{SELFPARAM();
- entity head;
- for (head = kh_worldkeylist; head; head = head.kh_worldkeynext)
- {
- if(head.owner == self)
- continue;
- if(!kh_tracking_enabled)
- {
- // if it's carried by our team we know about it
- // otherwise we have to see it to know about it
- if(!head.owner || head.team != self.team)
- {
- traceline(self.origin + self.view_ofs, head.origin, MOVE_NOMONSTERS, self);
- if (trace_fraction < 1 && trace_ent != head)
- continue; // skip what I can't see
- }
- }
- if(!head.owner)
- navigation_routerating(head, ratingscale_dropped * BOT_PICKUP_RATING_HIGH, 100000);
- else if(head.team == self.team)
- navigation_routerating(head.owner, ratingscale_team * BOT_PICKUP_RATING_HIGH, 100000);
- else
- navigation_routerating(head.owner, ratingscale_enemy * BOT_PICKUP_RATING_HIGH, 100000);
- }
-
- havocbot_goalrating_items(1, self.origin, 10000);
-}
-
-void havocbot_role_kh_carrier()
-{SELFPARAM();
- if(self.deadflag != DEAD_NO)
- return;
-
- if (!(self.kh_next))
- {
- LOG_TRACE("changing role to freelancer\n");
- self.havocbot_role = havocbot_role_kh_freelancer;
- self.havocbot_role_timeout = 0;
- return;
- }
-
- if (self.bot_strategytime < time)
- {
- self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
- navigation_goalrating_start();
-
- if(kh_Key_AllOwnedByWhichTeam() == self.team)
- havocbot_goalrating_kh(10, 0.1, 0.1); // bring home
- else
- havocbot_goalrating_kh(4, 4, 1); // play defensively
-
- navigation_goalrating_end();
- }
-}
-
-void havocbot_role_kh_defense()
-{SELFPARAM();
- if(self.deadflag != DEAD_NO)
- return;
-
- if (self.kh_next)
- {
- LOG_TRACE("changing role to carrier\n");
- self.havocbot_role = havocbot_role_kh_carrier;
- self.havocbot_role_timeout = 0;
- return;
- }
-
- if (!self.havocbot_role_timeout)
- self.havocbot_role_timeout = time + random() * 10 + 20;
- if (time > self.havocbot_role_timeout)
- {
- LOG_TRACE("changing role to freelancer\n");
- self.havocbot_role = havocbot_role_kh_freelancer;
- self.havocbot_role_timeout = 0;
- return;
- }
-
- if (self.bot_strategytime < time)
- {
- float key_owner_team;
- self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
- navigation_goalrating_start();
-
- key_owner_team = kh_Key_AllOwnedByWhichTeam();
- if(key_owner_team == self.team)
- havocbot_goalrating_kh(10, 0.1, 0.1); // defend key carriers
- else if(key_owner_team == -1)
- havocbot_goalrating_kh(4, 1, 0.1); // play defensively
- else
- havocbot_goalrating_kh(0.1, 0.1, 10); // ATTACK ANYWAY
-
- navigation_goalrating_end();
- }
-}
-
-void havocbot_role_kh_offense()
-{SELFPARAM();
- if(self.deadflag != DEAD_NO)
- return;
-
- if (self.kh_next)
- {
- LOG_TRACE("changing role to carrier\n");
- self.havocbot_role = havocbot_role_kh_carrier;
- self.havocbot_role_timeout = 0;
- return;
- }
-
- if (!self.havocbot_role_timeout)
- self.havocbot_role_timeout = time + random() * 10 + 20;
- if (time > self.havocbot_role_timeout)
- {
- LOG_TRACE("changing role to freelancer\n");
- self.havocbot_role = havocbot_role_kh_freelancer;
- self.havocbot_role_timeout = 0;
- return;
- }
-
- if (self.bot_strategytime < time)
- {
- float key_owner_team;
-
- self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
- navigation_goalrating_start();
-
- key_owner_team = kh_Key_AllOwnedByWhichTeam();
- if(key_owner_team == self.team)
- havocbot_goalrating_kh(10, 0.1, 0.1); // defend anyway
- else if(key_owner_team == -1)
- havocbot_goalrating_kh(0.1, 1, 4); // play offensively
- else
- havocbot_goalrating_kh(0.1, 0.1, 10); // ATTACK! EMERGENCY!
-
- navigation_goalrating_end();
- }
-}
-
-void havocbot_role_kh_freelancer()
-{SELFPARAM();
- if(self.deadflag != DEAD_NO)
- return;
-
- if (self.kh_next)
- {
- LOG_TRACE("changing role to carrier\n");
- self.havocbot_role = havocbot_role_kh_carrier;
- self.havocbot_role_timeout = 0;
- return;
- }
-
- if (!self.havocbot_role_timeout)
- self.havocbot_role_timeout = time + random() * 10 + 10;
- if (time > self.havocbot_role_timeout)
- {
- if (random() < 0.5)
- {
- LOG_TRACE("changing role to offense\n");
- self.havocbot_role = havocbot_role_kh_offense;
- }
- else
- {
- LOG_TRACE("changing role to defense\n");
- self.havocbot_role = havocbot_role_kh_defense;
- }
- self.havocbot_role_timeout = 0;
- return;
- }
-
- if (self.bot_strategytime < time)
- {
- float key_owner_team;
-
- self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
- navigation_goalrating_start();
-
- key_owner_team = kh_Key_AllOwnedByWhichTeam();
- if(key_owner_team == self.team)
- havocbot_goalrating_kh(10, 0.1, 0.1); // defend anyway
- else if(key_owner_team == -1)
- havocbot_goalrating_kh(1, 10, 4); // prefer dropped keys
- else
- havocbot_goalrating_kh(0.1, 0.1, 10); // ATTACK ANYWAY
-
- navigation_goalrating_end();
- }
-}
-
-void havocbot_chooserole_kh()
-{SELFPARAM();
- float r;
-
- if(self.deadflag != DEAD_NO)
- return;
-
- r = random() * 3;
- if (r < 1)
- self.havocbot_role = havocbot_role_kh_offense;
- else if (r < 2)
- self.havocbot_role = havocbot_role_kh_defense;
- else
- self.havocbot_role = havocbot_role_kh_freelancer;
-}
+++ /dev/null
-#ifndef ROLE_KEYHUNT_H
-#define ROLE_KEYHUNT_H
-
-entity kh_worldkeylist;
-.entity kh_worldkeynext;
-
-void havocbot_chooserole_kh();
-#endif
+++ /dev/null
-#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();
-}
+++ /dev/null
-#ifndef ROLES_H
-#define ROLES_H
-void havocbot_goalrating_controlpoints(float ratingscale, vector org, float sradius);
-#endif
+++ /dev/null
-#ifndef HAVOCBOT_SCRIPTING_H
-#define HAVOCBOT_SCRIPTING_H
-
-void bot_clearqueue(entity bot);
-#endif
--- /dev/null
+#include "api.qh"
+
+#include "default/all.inc"
+++ /dev/null
-#include "navigation.qh"
-#include "../_all.qh"
-
-#include "bot.qh"
-#include "waypoints.qh"
-
-#include "../t_items.qh"
-
-#include "../../common/constants.qh"
-#include "../../common/triggers/trigger/jumppads.qh"
-
-void bot_debug(string input)
-{
- switch(autocvar_bot_debug)
- {
- case 1: LOG_TRACE(input); break;
- case 2: LOG_INFO(input); break;
- }
-}
-
-// rough simulation of walking from one point to another to test if a path
-// can be traveled, used for waypoint linking and havocbot
-
-float tracewalk(entity e, vector start, vector m1, vector m2, vector end, float movemode)
-{SELFPARAM();
- vector org;
- vector move;
- vector dir;
- float dist;
- float totaldist;
- float stepdist;
- float yaw;
- float ignorehazards;
- float swimming;
-
- if(autocvar_bot_debug_tracewalk)
- {
- debugresetnodes();
- debugnode(start);
- }
-
- move = end - start;
- move.z = 0;
- org = start;
- dist = totaldist = vlen(move);
- dir = normalize(move);
- stepdist = 32;
- ignorehazards = false;
- swimming = false;
-
- // Analyze starting point
- traceline(start, start, MOVE_NORMAL, e);
- if (trace_dpstartcontents & (DPCONTENTS_SLIME | DPCONTENTS_LAVA))
- ignorehazards = true;
- else
- {
- traceline( start, start + '0 0 -65536', MOVE_NORMAL, e);
- if (trace_dpstartcontents & (DPCONTENTS_SLIME | DPCONTENTS_LAVA))
- {
- ignorehazards = true;
- swimming = true;
- }
- }
- tracebox(start, m1, m2, start, MOVE_NOMONSTERS, e);
- if (trace_startsolid)
- {
- // Bad start
- if(autocvar_bot_debug_tracewalk)
- debugnodestatus(start, DEBUG_NODE_FAIL);
-
- //print("tracewalk: ", vtos(start), " is a bad start\n");
- return false;
- }
-
- // Movement loop
- yaw = vectoyaw(move);
- move = end - org;
- for (;;)
- {
- if (boxesoverlap(end, end, org + m1 + '-1 -1 -1', org + m2 + '1 1 1'))
- {
- // Succeeded
- if(autocvar_bot_debug_tracewalk)
- debugnodestatus(org, DEBUG_NODE_SUCCESS);
-
- //print("tracewalk: ", vtos(start), " can reach ", vtos(end), "\n");
- return true;
- }
- if(autocvar_bot_debug_tracewalk)
- debugnode(org);
-
- if (dist <= 0)
- break;
- if (stepdist > dist)
- stepdist = dist;
- dist = dist - stepdist;
- traceline(org, org, MOVE_NORMAL, e);
- if (!ignorehazards)
- {
- if (trace_dpstartcontents & (DPCONTENTS_SLIME | DPCONTENTS_LAVA))
- {
- // hazards blocking path
- if(autocvar_bot_debug_tracewalk)
- debugnodestatus(org, DEBUG_NODE_FAIL);
-
- //print("tracewalk: ", vtos(start), " hits a hazard when trying to reach ", vtos(end), "\n");
- return false;
- }
- }
- if (trace_dpstartcontents & DPCONTENTS_LIQUIDSMASK)
- {
- move = normalize(end - org);
- tracebox(org, m1, m2, org + move * stepdist, movemode, e);
-
- if(autocvar_bot_debug_tracewalk)
- debugnode(trace_endpos);
-
- if (trace_fraction < 1)
- {
- swimming = true;
- org = trace_endpos - normalize(org - trace_endpos) * stepdist;
- for (; org.z < end.z + self.maxs.z; org.z += stepdist)
- {
- if(autocvar_bot_debug_tracewalk)
- debugnode(org);
-
- if(pointcontents(org) == CONTENT_EMPTY)
- break;
- }
-
- if(pointcontents(org + '0 0 1') != CONTENT_EMPTY)
- {
- if(autocvar_bot_debug_tracewalk)
- debugnodestatus(org, DEBUG_NODE_FAIL);
-
- return false;
- //print("tracewalk: ", vtos(start), " failed under water\n");
- }
- continue;
-
- }
- else
- org = trace_endpos;
- }
- else
- {
- move = dir * stepdist + org;
- tracebox(org, m1, m2, move, movemode, e);
-
- if(autocvar_bot_debug_tracewalk)
- debugnode(trace_endpos);
-
- // hit something
- if (trace_fraction < 1)
- {
- // check if we can walk over this obstacle, possibly by jumpstepping
- tracebox(org + stepheightvec, m1, m2, move + stepheightvec, movemode, e);
- if (trace_fraction < 1 || trace_startsolid)
- {
- tracebox(org + jumpstepheightvec, m1, m2, move + jumpstepheightvec, movemode, e);
- if (trace_fraction < 1 || trace_startsolid)
- {
- if(autocvar_bot_debug_tracewalk)
- debugnodestatus(trace_endpos, DEBUG_NODE_WARNING);
-
- // check for doors
- traceline( org, move, movemode, e);
- if ( trace_ent.classname == "door_rotating" || trace_ent.classname == "door")
- {
- vector nextmove;
- move = trace_endpos;
- while(trace_ent.classname == "door_rotating" || trace_ent.classname == "door")
- {
- nextmove = move + (dir * stepdist);
- traceline( move, nextmove, movemode, e);
- move = nextmove;
- }
- }
- else
- {
- if(autocvar_bot_debug_tracewalk)
- debugnodestatus(trace_endpos, DEBUG_NODE_FAIL);
-
- //print("tracewalk: ", vtos(start), " hit something when trying to reach ", vtos(end), "\n");
- //te_explosion(trace_endpos);
- //print(ftos(e.dphitcontentsmask), "\n");
- return false; // failed
- }
- }
- else
- move = trace_endpos;
- }
- else
- move = trace_endpos;
- }
- else
- move = trace_endpos;
-
- // trace down from stepheight as far as possible and move there,
- // if this starts in solid we try again without the stepup, and
- // if that also fails we assume it is a wall
- // (this is the same logic as the Quake walkmove function used)
- tracebox(move, m1, m2, move + '0 0 -65536', movemode, e);
-
- // moved successfully
- if(swimming)
- {
- float c;
- c = pointcontents(org + '0 0 1');
- if (!(c == CONTENT_WATER || c == CONTENT_LAVA || c == CONTENT_SLIME))
- swimming = false;
- else
- continue;
- }
-
- org = trace_endpos;
- }
- }
-
- //print("tracewalk: ", vtos(start), " did not arrive at ", vtos(end), " but at ", vtos(org), "\n");
-
- // moved but didn't arrive at the intended destination
- if(autocvar_bot_debug_tracewalk)
- debugnodestatus(org, DEBUG_NODE_FAIL);
-
- return false;
-}
-
-/////////////////////////////////////////////////////////////////////////////
-// goal stack
-/////////////////////////////////////////////////////////////////////////////
-
-// completely empty the goal stack, used when deciding where to go
-void navigation_clearroute()
-{SELFPARAM();
- //print("bot ", etos(self), " clear\n");
- self.navigation_hasgoals = false;
- self.goalcurrent = world;
- self.goalstack01 = world;
- self.goalstack02 = world;
- self.goalstack03 = world;
- self.goalstack04 = world;
- self.goalstack05 = world;
- self.goalstack06 = world;
- self.goalstack07 = world;
- self.goalstack08 = world;
- self.goalstack09 = world;
- self.goalstack10 = world;
- self.goalstack11 = world;
- self.goalstack12 = world;
- self.goalstack13 = world;
- self.goalstack14 = world;
- self.goalstack15 = world;
- self.goalstack16 = world;
- self.goalstack17 = world;
- self.goalstack18 = world;
- self.goalstack19 = world;
- self.goalstack20 = world;
- self.goalstack21 = world;
- self.goalstack22 = world;
- self.goalstack23 = world;
- self.goalstack24 = world;
- self.goalstack25 = world;
- self.goalstack26 = world;
- self.goalstack27 = world;
- self.goalstack28 = world;
- self.goalstack29 = world;
- self.goalstack30 = world;
- self.goalstack31 = world;
-}
-
-// add a new goal at the beginning of the stack
-// (in other words: add a new prerequisite before going to the later goals)
-// NOTE: when a waypoint is added, the WP gets pushed first, then the
-// next-closest WP on the shortest path to the WP
-// That means, if the stack overflows, the bot will know how to do the FIRST 32
-// steps to the goal, and then recalculate the path.
-void navigation_pushroute(entity e)
-{SELFPARAM();
- //print("bot ", etos(self), " push ", etos(e), "\n");
- self.goalstack31 = self.goalstack30;
- self.goalstack30 = self.goalstack29;
- self.goalstack29 = self.goalstack28;
- self.goalstack28 = self.goalstack27;
- self.goalstack27 = self.goalstack26;
- self.goalstack26 = self.goalstack25;
- self.goalstack25 = self.goalstack24;
- self.goalstack24 = self.goalstack23;
- self.goalstack23 = self.goalstack22;
- self.goalstack22 = self.goalstack21;
- self.goalstack21 = self.goalstack20;
- self.goalstack20 = self.goalstack19;
- self.goalstack19 = self.goalstack18;
- self.goalstack18 = self.goalstack17;
- self.goalstack17 = self.goalstack16;
- self.goalstack16 = self.goalstack15;
- self.goalstack15 = self.goalstack14;
- self.goalstack14 = self.goalstack13;
- self.goalstack13 = self.goalstack12;
- self.goalstack12 = self.goalstack11;
- self.goalstack11 = self.goalstack10;
- self.goalstack10 = self.goalstack09;
- self.goalstack09 = self.goalstack08;
- self.goalstack08 = self.goalstack07;
- self.goalstack07 = self.goalstack06;
- self.goalstack06 = self.goalstack05;
- self.goalstack05 = self.goalstack04;
- self.goalstack04 = self.goalstack03;
- self.goalstack03 = self.goalstack02;
- self.goalstack02 = self.goalstack01;
- self.goalstack01 = self.goalcurrent;
- self.goalcurrent = e;
-}
-
-// remove first goal from stack
-// (in other words: remove a prerequisite for reaching the later goals)
-// (used when a spawnfunc_waypoint is reached)
-void navigation_poproute()
-{SELFPARAM();
- //print("bot ", etos(self), " pop\n");
- self.goalcurrent = self.goalstack01;
- self.goalstack01 = self.goalstack02;
- self.goalstack02 = self.goalstack03;
- self.goalstack03 = self.goalstack04;
- self.goalstack04 = self.goalstack05;
- self.goalstack05 = self.goalstack06;
- self.goalstack06 = self.goalstack07;
- self.goalstack07 = self.goalstack08;
- self.goalstack08 = self.goalstack09;
- self.goalstack09 = self.goalstack10;
- self.goalstack10 = self.goalstack11;
- self.goalstack11 = self.goalstack12;
- self.goalstack12 = self.goalstack13;
- self.goalstack13 = self.goalstack14;
- self.goalstack14 = self.goalstack15;
- self.goalstack15 = self.goalstack16;
- self.goalstack16 = self.goalstack17;
- self.goalstack17 = self.goalstack18;
- self.goalstack18 = self.goalstack19;
- self.goalstack19 = self.goalstack20;
- self.goalstack20 = self.goalstack21;
- self.goalstack21 = self.goalstack22;
- self.goalstack22 = self.goalstack23;
- self.goalstack23 = self.goalstack24;
- self.goalstack24 = self.goalstack25;
- self.goalstack25 = self.goalstack26;
- self.goalstack26 = self.goalstack27;
- self.goalstack27 = self.goalstack28;
- self.goalstack28 = self.goalstack29;
- self.goalstack29 = self.goalstack30;
- self.goalstack30 = self.goalstack31;
- self.goalstack31 = world;
-}
-
-float navigation_waypoint_will_link(vector v, vector org, entity ent, float walkfromwp, float bestdist)
-{
- float dist;
- dist = vlen(v - org);
- if (bestdist > dist)
- {
- traceline(v, org, true, ent);
- if (trace_fraction == 1)
- {
- if (walkfromwp)
- {
- if (tracewalk(ent, v, PL_MIN, PL_MAX, org, bot_navigation_movemode))
- return true;
- }
- else
- {
- if (tracewalk(ent, org, PL_MIN, PL_MAX, v, bot_navigation_movemode))
- return true;
- }
- }
- }
- return false;
-}
-
-// find the spawnfunc_waypoint near a dynamic goal such as a dropped weapon
-entity navigation_findnearestwaypoint_withdist_except(entity ent, float walkfromwp, float bestdist, entity except)
-{
- entity waylist, w, best;
- vector v, org, pm1, pm2;
-
- pm1 = ent.origin + ent.mins;
- pm2 = ent.origin + ent.maxs;
- waylist = findchain(classname, "waypoint");
-
- // do two scans, because box test is cheaper
- w = waylist;
- while (w)
- {
- // if object is touching spawnfunc_waypoint
- if(w != ent && w != except)
- if (boxesoverlap(pm1, pm2, w.absmin, w.absmax))
- return w;
- w = w.chain;
- }
-
- org = ent.origin + 0.5 * (ent.mins + ent.maxs);
- org.z = ent.origin.z + ent.mins.z - PL_MIN.z; // player height
- // TODO possibly make other code have the same support for bboxes
- if(ent.tag_entity)
- org = org + ent.tag_entity.origin;
- if (navigation_testtracewalk)
- te_plasmaburn(org);
-
- best = world;
-
- // box check failed, try walk
- w = waylist;
- while (w)
- {
- // if object can walk from spawnfunc_waypoint
- if(w != ent)
- {
- if (w.wpisbox)
- {
- vector wm1, wm2;
- wm1 = w.origin + w.mins;
- wm2 = w.origin + w.maxs;
- v.x = bound(wm1_x, org.x, wm2_x);
- v.y = bound(wm1_y, org.y, wm2_y);
- v.z = bound(wm1_z, org.z, wm2_z);
- }
- else
- v = w.origin;
- if(navigation_waypoint_will_link(v, org, ent, walkfromwp, bestdist))
- {
- bestdist = vlen(v - org);
- best = w;
- }
- }
- w = w.chain;
- }
- return best;
-}
-entity navigation_findnearestwaypoint(entity ent, float walkfromwp)
-{
- entity wp = navigation_findnearestwaypoint_withdist_except(ent, walkfromwp, 1050, world);
- if (autocvar_g_waypointeditor_auto)
- {
- entity wp2 = navigation_findnearestwaypoint_withdist_except(ent, walkfromwp, 1050, wp);
- if (wp && !wp2)
- wp.wpflags |= WAYPOINTFLAG_PROTECTED;
- }
- return wp;
-}
-
-// finds the waypoints near the bot initiating a navigation query
-float navigation_markroutes_nearestwaypoints(entity waylist, float maxdist)
-{SELFPARAM();
- entity head;
- vector v, m1, m2, diff;
- float c;
-// navigation_testtracewalk = true;
- c = 0;
- head = waylist;
- while (head)
- {
- if (!head.wpconsidered)
- {
- if (head.wpisbox)
- {
- m1 = head.origin + head.mins;
- m2 = head.origin + head.maxs;
- v = self.origin;
- v.x = bound(m1_x, v.x, m2_x);
- v.y = bound(m1_y, v.y, m2_y);
- v.z = bound(m1_z, v.z, m2_z);
- }
- else
- v = head.origin;
- diff = v - self.origin;
- diff.z = max(0, diff.z);
- if (vlen(diff) < maxdist)
- {
- head.wpconsidered = true;
- if (tracewalk(self, self.origin, self.mins, self.maxs, v, bot_navigation_movemode))
- {
- head.wpnearestpoint = v;
- head.wpcost = vlen(v - self.origin) + head.dmg;
- head.wpfire = 1;
- head.enemy = world;
- c = c + 1;
- }
- }
- }
- head = head.chain;
- }
- //navigation_testtracewalk = false;
- return c;
-}
-
-// updates a path link if a spawnfunc_waypoint link is better than the current one
-void navigation_markroutes_checkwaypoint(entity w, entity wp, float cost2, vector p)
-{
- vector m1;
- vector m2;
- vector v;
- if (wp.wpisbox)
- {
- m1 = wp.absmin;
- m2 = wp.absmax;
- v.x = bound(m1_x, p.x, m2_x);
- v.y = bound(m1_y, p.y, m2_y);
- v.z = bound(m1_z, p.z, m2_z);
- }
- else
- v = wp.origin;
- cost2 = cost2 + vlen(v - p);
- if (wp.wpcost > cost2)
- {
- wp.wpcost = cost2;
- wp.enemy = w;
- wp.wpfire = 1;
- wp.wpnearestpoint = v;
- }
-}
-
-// queries the entire spawnfunc_waypoint network for pathes leading away from the bot
-void navigation_markroutes(entity fixed_source_waypoint)
-{SELFPARAM();
- entity w, wp, waylist;
- float searching, cost, cost2;
- vector p;
- w = waylist = findchain(classname, "waypoint");
- while (w)
- {
- w.wpconsidered = false;
- w.wpnearestpoint = '0 0 0';
- w.wpcost = 10000000;
- w.wpfire = 0;
- w.enemy = world;
- w = w.chain;
- }
-
- if(fixed_source_waypoint)
- {
- fixed_source_waypoint.wpconsidered = true;
- fixed_source_waypoint.wpnearestpoint = fixed_source_waypoint.origin + 0.5 * (fixed_source_waypoint.mins + fixed_source_waypoint.maxs);
- fixed_source_waypoint.wpcost = fixed_source_waypoint.dmg;
- fixed_source_waypoint.wpfire = 1;
- fixed_source_waypoint.enemy = world;
- }
- else
- {
- // try a short range search for the nearest waypoints, and expand the search repeatedly if none are found
- // as this search is expensive we will use lower values if the bot is on the air
- float i, increment, maxdistance;
- if(self.flags & FL_ONGROUND)
- {
- increment = 750;
- maxdistance = 50000;
- }
- else
- {
- increment = 500;
- maxdistance = 1500;
- }
-
- for(i=increment;!navigation_markroutes_nearestwaypoints(waylist, i)&&i<maxdistance;i+=increment);
- }
-
- searching = true;
- while (searching)
- {
- searching = false;
- w = waylist;
- while (w)
- {
- if (w.wpfire)
- {
- searching = true;
- w.wpfire = 0;
- cost = w.wpcost;
- p = w.wpnearestpoint;
- wp = w.wp00;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp00mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
- wp = w.wp01;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp01mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
- wp = w.wp02;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp02mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
- wp = w.wp03;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp03mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
- wp = w.wp04;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp04mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
- wp = w.wp05;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp05mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
- wp = w.wp06;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp06mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
- wp = w.wp07;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp07mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
- wp = w.wp08;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp08mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
- wp = w.wp09;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp09mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
- wp = w.wp10;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp10mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
- wp = w.wp11;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp11mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
- wp = w.wp12;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp12mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
- wp = w.wp13;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp13mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
- wp = w.wp14;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp14mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
- wp = w.wp15;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp15mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
- wp = w.wp16;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp16mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
- wp = w.wp17;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp17mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
- wp = w.wp18;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp18mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
- wp = w.wp19;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp19mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
- wp = w.wp20;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp20mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
- wp = w.wp21;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp21mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
- wp = w.wp22;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp22mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
- wp = w.wp23;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp23mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
- wp = w.wp24;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp24mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
- wp = w.wp25;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp25mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
- wp = w.wp26;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp26mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
- wp = w.wp27;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp27mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
- wp = w.wp28;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp28mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
- wp = w.wp29;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp29mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
- wp = w.wp30;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp30mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
- wp = w.wp31;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp31mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
- }}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
- }
- w = w.chain;
- }
- }
-}
-
-// queries the entire spawnfunc_waypoint network for pathes leading to the bot
-void navigation_markroutes_inverted(entity fixed_source_waypoint)
-{
- entity w, wp, waylist;
- float searching, cost, cost2;
- vector p;
- w = waylist = findchain(classname, "waypoint");
- while (w)
- {
- w.wpconsidered = false;
- w.wpnearestpoint = '0 0 0';
- w.wpcost = 10000000;
- w.wpfire = 0;
- w.enemy = world;
- w = w.chain;
- }
-
- if(fixed_source_waypoint)
- {
- fixed_source_waypoint.wpconsidered = true;
- fixed_source_waypoint.wpnearestpoint = fixed_source_waypoint.origin + 0.5 * (fixed_source_waypoint.mins + fixed_source_waypoint.maxs);
- fixed_source_waypoint.wpcost = fixed_source_waypoint.dmg; // the cost to get from X to fixed_source_waypoint
- fixed_source_waypoint.wpfire = 1;
- fixed_source_waypoint.enemy = world;
- }
- else
- {
- error("need to start with a waypoint\n");
- }
-
- searching = true;
- while (searching)
- {
- searching = false;
- w = waylist;
- while (w)
- {
- if (w.wpfire)
- {
- searching = true;
- w.wpfire = 0;
- cost = w.wpcost; // cost to walk from w to home
- p = w.wpnearestpoint;
- for(wp = waylist; wp; wp = wp.chain)
- {
- if(w != wp.wp00) if(w != wp.wp01) if(w != wp.wp02) if(w != wp.wp03)
- if(w != wp.wp04) if(w != wp.wp05) if(w != wp.wp06) if(w != wp.wp07)
- if(w != wp.wp08) if(w != wp.wp09) if(w != wp.wp10) if(w != wp.wp11)
- if(w != wp.wp12) if(w != wp.wp13) if(w != wp.wp14) if(w != wp.wp15)
- if(w != wp.wp16) if(w != wp.wp17) if(w != wp.wp18) if(w != wp.wp19)
- if(w != wp.wp20) if(w != wp.wp21) if(w != wp.wp22) if(w != wp.wp23)
- if(w != wp.wp24) if(w != wp.wp25) if(w != wp.wp26) if(w != wp.wp27)
- if(w != wp.wp28) if(w != wp.wp29) if(w != wp.wp30) if(w != wp.wp31)
- continue;
- cost2 = cost + wp.dmg;
- navigation_markroutes_checkwaypoint(w, wp, cost2, p);
- }
- }
- w = w.chain;
- }
- }
-}
-
-// updates the best goal according to a weighted calculation of travel cost and item value of a new proposed item
-void navigation_routerating(entity e, float f, float rangebias)
-{SELFPARAM();
- entity nwp;
- vector o;
- if (!e)
- return;
-
- if(e.blacklisted)
- return;
-
- o = (e.absmin + e.absmax) * 0.5;
-
- //print("routerating ", etos(e), " = ", ftos(f), " - ", ftos(rangebias), "\n");
-
- // Evaluate path using jetpack
- if(g_jetpack)
- if(self.items & IT_JETPACK)
- if(autocvar_bot_ai_navigation_jetpack)
- if(vlen(self.origin - o) > autocvar_bot_ai_navigation_jetpack_mindistance)
- {
- vector pointa, pointb;
-
- bot_debug(strcat("jetpack ai: evaluating path for ", e.classname, "\n"));
-
- // Point A
- traceline(self.origin, self.origin + '0 0 65535', MOVE_NORMAL, self);
- pointa = trace_endpos - '0 0 1';
-
- // Point B
- traceline(o, o + '0 0 65535', MOVE_NORMAL, e);
- pointb = trace_endpos - '0 0 1';
-
- // Can I see these two points from the sky?
- traceline(pointa, pointb, MOVE_NORMAL, self);
-
- if(trace_fraction==1)
- {
- bot_debug("jetpack ai: can bridge these two points\n");
-
- // Lower the altitude of these points as much as possible
- float zdistance, xydistance, cost, t, fuel;
- vector down, npa, npb;
-
- down = '0 0 -1' * (PL_MAX.z - PL_MIN.z) * 10;
-
- do{
- npa = pointa + down;
- npb = pointb + down;
-
- if(npa.z<=self.absmax.z)
- break;
-
- if(npb.z<=e.absmax.z)
- break;
-
- traceline(npa, npb, MOVE_NORMAL, self);
- if(trace_fraction==1)
- {
- pointa = npa;
- pointb = npb;
- }
- }
- while(trace_fraction == 1);
-
-
- // Rough estimation of fuel consumption
- // (ignores acceleration and current xyz velocity)
- xydistance = vlen(pointa - pointb);
- zdistance = fabs(pointa.z - self.origin.z);
-
- t = zdistance / autocvar_g_jetpack_maxspeed_up;
- t += xydistance / autocvar_g_jetpack_maxspeed_side;
- fuel = t * autocvar_g_jetpack_fuel * 0.8;
-
- bot_debug(strcat("jetpack ai: required fuel ", ftos(fuel), " self.ammo_fuel ", ftos(self.ammo_fuel), "\n"));
-
- // enough fuel ?
- if(self.ammo_fuel>fuel)
- {
- // Estimate cost
- // (as onground costs calculation is mostly based on distances, here we do the same establishing some relationship
- // - between air and ground speeds)
-
- cost = xydistance / (autocvar_g_jetpack_maxspeed_side/autocvar_sv_maxspeed);
- cost += zdistance / (autocvar_g_jetpack_maxspeed_up/autocvar_sv_maxspeed);
- cost *= 1.5;
-
- // Compare against other goals
- f = f * rangebias / (rangebias + cost);
-
- if (navigation_bestrating < f)
- {
- bot_debug(strcat("jetpack path: added goal ", e.classname, " (with rating ", ftos(f), ")\n"));
- navigation_bestrating = f;
- navigation_bestgoal = e;
- self.navigation_jetpack_goal = e;
- self.navigation_jetpack_point = pointb;
- }
- return;
- }
- }
- }
-
- //te_wizspike(e.origin);
- //bprint(etos(e));
- //bprint("\n");
- // update the cached spawnfunc_waypoint link on a dynamic item entity
- if(e.classname == "waypoint" && !(e.wpflags & WAYPOINTFLAG_PERSONAL))
- {
- nwp = e;
- }
- else
- {
- float search;
-
- search = true;
-
- if(e.flags & FL_ITEM)
- {
- if (!(e.flags & FL_WEAPON))
- if(e.nearestwaypoint)
- search = false;
- }
- else if (e.flags & FL_WEAPON)
- {
- if(e.classname != "droppedweapon")
- if(e.nearestwaypoint)
- search = false;
- }
-
- if(search)
- if (time > e.nearestwaypointtimeout)
- {
- nwp = navigation_findnearestwaypoint(e, true);
- if(nwp)
- e.nearestwaypoint = nwp;
- else
- {
- bot_debug(strcat("FAILED to find a nearest waypoint to '", e.classname, "' #", etos(e), "\n"));
-
- if(e.flags & FL_ITEM)
- e.blacklisted = true;
- else if (e.flags & FL_WEAPON)
- {
- if(e.classname != "droppedweapon")
- e.blacklisted = true;
- }
-
- if(e.blacklisted)
- {
- bot_debug(strcat("The entity '", e.classname, "' is going to be excluded from path finding during this match\n"));
- return;
- }
- }
-
- // TODO: Cleaner solution, probably handling this timeout from ctf.qc
- if(e.classname=="item_flag_team")
- e.nearestwaypointtimeout = time + 2;
- else
- e.nearestwaypointtimeout = time + random() * 3 + 5;
- }
- nwp = e.nearestwaypoint;
- }
-
- bot_debug(strcat("-- checking ", e.classname, " (with cost ", ftos(nwp.wpcost), ")\n"));
- if (nwp)
- if (nwp.wpcost < 10000000)
- {
- //te_wizspike(nwp.wpnearestpoint);
- bot_debug(strcat(e.classname, " ", ftos(f), "/(1+", ftos((nwp.wpcost + vlen(e.origin - nwp.wpnearestpoint))), "/", ftos(rangebias), ") = "));
- f = f * rangebias / (rangebias + (nwp.wpcost + vlen(o - nwp.wpnearestpoint)));
- bot_debug(strcat("considering ", e.classname, " (with rating ", ftos(f), ")\n"));
- if (navigation_bestrating < f)
- {
- bot_debug(strcat("ground path: added goal ", e.classname, " (with rating ", ftos(f), ")\n"));
- navigation_bestrating = f;
- navigation_bestgoal = e;
- }
- }
-}
-
-// adds an item to the the goal stack with the path to a given item
-float navigation_routetogoal(entity e, vector startposition)
-{SELFPARAM();
- self.goalentity = e;
-
- // if there is no goal, just exit
- if (!e)
- return false;
-
- self.navigation_hasgoals = true;
-
- // put the entity on the goal stack
- //print("routetogoal ", etos(e), "\n");
- navigation_pushroute(e);
-
- if(g_jetpack)
- if(e==self.navigation_jetpack_goal)
- return true;
-
- // if it can reach the goal there is nothing more to do
- if (tracewalk(self, startposition, PL_MIN, PL_MAX, (e.absmin + e.absmax) * 0.5, bot_navigation_movemode))
- return true;
-
- // see if there are waypoints describing a path to the item
- if(e.classname != "waypoint" || (e.wpflags & WAYPOINTFLAG_PERSONAL))
- e = e.nearestwaypoint;
- else
- e = e.enemy; // we already have added it, so...
-
- if(e == world)
- return false;
-
- for (;;)
- {
- // add the spawnfunc_waypoint to the path
- navigation_pushroute(e);
- e = e.enemy;
-
- if(e==world)
- break;
- }
-
- return false;
-}
-
-// removes any currently touching waypoints from the goal stack
-// (this is how bots detect if they reached a goal)
-void navigation_poptouchedgoals()
-{SELFPARAM();
- vector org, m1, m2;
- org = self.origin;
- m1 = org + self.mins;
- m2 = org + self.maxs;
-
- if(self.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT)
- {
- if(self.lastteleporttime>0)
- if(time-self.lastteleporttime<(self.goalcurrent.wpflags & WAYPOINTFLAG_PERSONAL)?2:0.15)
- {
- if(self.aistatus & AI_STATUS_WAYPOINT_PERSONAL_GOING)
- if(self.goalcurrent.wpflags & WAYPOINTFLAG_PERSONAL && self.goalcurrent.owner==self)
- {
- self.aistatus &= ~AI_STATUS_WAYPOINT_PERSONAL_GOING;
- self.aistatus |= AI_STATUS_WAYPOINT_PERSONAL_REACHED;
- }
- navigation_poproute();
- return;
- }
- }
-
- // If for some reason the bot is closer to the next goal, pop the current one
- if(self.goalstack01)
- if(vlen(self.goalcurrent.origin - self.origin) > vlen(self.goalstack01.origin - self.origin))
- if(checkpvs(self.origin + self.view_ofs, self.goalstack01))
- if(tracewalk(self, self.origin, self.mins, self.maxs, (self.goalstack01.absmin + self.goalstack01.absmax) * 0.5, bot_navigation_movemode))
- {
- bot_debug(strcat("path optimized for ", self.netname, ", removed a goal from the queue\n"));
- navigation_poproute();
- // TODO this may also be a nice idea to do "early" (e.g. by
- // manipulating the vlen() comparisons) to shorten paths in
- // general - this would make bots walk more "on rails" than
- // "zigzagging" which they currently do with sufficiently
- // random-like waypoints, and thus can make a nice bot
- // personality property
- }
-
- // HACK: remove players/bots as goals, they can lead a bot to unexpected places (cliffs, lava, etc)
- // TODO: rate waypoints near the targetted player at that moment, instead of the player itself
- if(IS_PLAYER(self.goalcurrent))
- navigation_poproute();
-
- // aid for detecting jump pads better (distance based check fails sometimes)
- if(self.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT && self.jumppadcount > 0 )
- navigation_poproute();
-
- // Loose goal touching check when running
- if(self.aistatus & AI_STATUS_RUNNING)
- if(self.speed >= autocvar_sv_maxspeed) // if -really- running
- if(self.goalcurrent.classname=="waypoint")
- {
- if(vlen(self.origin - self.goalcurrent.origin)<150)
- {
- traceline(self.origin + self.view_ofs , self.goalcurrent.origin, true, world);
- if(trace_fraction==1)
- {
- // Detect personal waypoints
- if(self.aistatus & AI_STATUS_WAYPOINT_PERSONAL_GOING)
- if(self.goalcurrent.wpflags & WAYPOINTFLAG_PERSONAL && self.goalcurrent.owner==self)
- {
- self.aistatus &= ~AI_STATUS_WAYPOINT_PERSONAL_GOING;
- self.aistatus |= AI_STATUS_WAYPOINT_PERSONAL_REACHED;
- }
-
- navigation_poproute();
- }
- }
- }
-
- while (self.goalcurrent && boxesoverlap(m1, m2, self.goalcurrent.absmin, self.goalcurrent.absmax))
- {
- // Detect personal waypoints
- if(self.aistatus & AI_STATUS_WAYPOINT_PERSONAL_GOING)
- if(self.goalcurrent.wpflags & WAYPOINTFLAG_PERSONAL && self.goalcurrent.owner==self)
- {
- self.aistatus &= ~AI_STATUS_WAYPOINT_PERSONAL_GOING;
- self.aistatus |= AI_STATUS_WAYPOINT_PERSONAL_REACHED;
- }
-
- navigation_poproute();
- }
-}
-
-// begin a goal selection session (queries spawnfunc_waypoint network)
-void navigation_goalrating_start()
-{SELFPARAM();
- if(self.aistatus & AI_STATUS_STUCK)
- return;
-
- self.navigation_jetpack_goal = world;
- navigation_bestrating = -1;
- self.navigation_hasgoals = false;
- navigation_clearroute();
- navigation_bestgoal = world;
- navigation_markroutes(world);
-}
-
-// ends a goal selection session (updates goal stack to the best goal)
-void navigation_goalrating_end()
-{SELFPARAM();
- if(self.aistatus & AI_STATUS_STUCK)
- return;
-
- navigation_routetogoal(navigation_bestgoal, self.origin);
- bot_debug(strcat("best goal ", self.goalcurrent.classname , "\n"));
-
- // If the bot got stuck then try to reach the farthest waypoint
- if (!self.navigation_hasgoals)
- if (autocvar_bot_wander_enable)
- {
- if (!(self.aistatus & AI_STATUS_STUCK))
- {
- bot_debug(strcat(self.netname, " cannot walk to any goal\n"));
- self.aistatus |= AI_STATUS_STUCK;
- }
-
- self.navigation_hasgoals = false; // Reset this value
- }
-}
-
-void botframe_updatedangerousobjects(float maxupdate)
-{
- entity head, bot_dodgelist;
- vector m1, m2, v, o;
- float c, d, danger;
- c = 0;
- bot_dodgelist = findchainfloat(bot_dodge, true);
- botframe_dangerwaypoint = find(botframe_dangerwaypoint, classname, "waypoint");
- while (botframe_dangerwaypoint != world)
- {
- danger = 0;
- m1 = botframe_dangerwaypoint.mins;
- m2 = botframe_dangerwaypoint.maxs;
- head = bot_dodgelist;
- while (head)
- {
- v = head.origin;
- v.x = bound(m1_x, v.x, m2_x);
- v.y = bound(m1_y, v.y, m2_y);
- v.z = bound(m1_z, v.z, m2_z);
- o = (head.absmin + head.absmax) * 0.5;
- d = head.bot_dodgerating - vlen(o - v);
- if (d > 0)
- {
- traceline(o, v, true, world);
- if (trace_fraction == 1)
- danger = danger + d;
- }
- head = head.chain;
- }
- botframe_dangerwaypoint.dmg = danger;
- c = c + 1;
- if (c >= maxupdate)
- break;
- botframe_dangerwaypoint = find(botframe_dangerwaypoint, classname, "waypoint");
- }
-}
-
-void navigation_unstuck()
-{SELFPARAM();
- float search_radius = 1000;
-
- if (!autocvar_bot_wander_enable)
- return;
-
- if (!bot_waypoint_queue_owner)
- {
- bot_debug(strcat(self.netname, " sutck, taking over the waypoints queue\n"));
- bot_waypoint_queue_owner = self;
- bot_waypoint_queue_bestgoal = world;
- bot_waypoint_queue_bestgoalrating = 0;
- }
-
- if(bot_waypoint_queue_owner!=self)
- return;
-
- if (bot_waypoint_queue_goal)
- {
- // evaluate the next goal on the queue
- float d = vlen(self.origin - bot_waypoint_queue_goal.origin);
- bot_debug(strcat(self.netname, " evaluating ", bot_waypoint_queue_goal.classname, " with distance ", ftos(d), "\n"));
- if(tracewalk(bot_waypoint_queue_goal, self.origin, PL_MIN, PL_MAX, bot_waypoint_queue_goal.origin, bot_navigation_movemode))
- {
- if( d > bot_waypoint_queue_bestgoalrating)
- {
- bot_waypoint_queue_bestgoalrating = d;
- bot_waypoint_queue_bestgoal = bot_waypoint_queue_goal;
- }
- }
- bot_waypoint_queue_goal = bot_waypoint_queue_goal.bot_waypoint_queue_nextgoal;
-
- if (!bot_waypoint_queue_goal)
- {
- if (bot_waypoint_queue_bestgoal)
- {
- bot_debug(strcat(self.netname, " stuck, reachable waypoint found, heading to it\n"));
- navigation_routetogoal(bot_waypoint_queue_bestgoal, self.origin);
- self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
- self.aistatus &= ~AI_STATUS_STUCK;
- }
- else
- {
- bot_debug(strcat(self.netname, " stuck, cannot walk to any waypoint at all\n"));
- }
-
- bot_waypoint_queue_owner = world;
- }
- }
- else
- {
- if(bot_strategytoken!=self)
- return;
-
- // build a new queue
- bot_debug(strcat(self.netname, " stuck, scanning reachable waypoints within ", ftos(search_radius)," qu\n"));
-
- entity head, first;
-
- first = world;
- head = findradius(self.origin, search_radius);
-
- while(head)
- {
- if(head.classname=="waypoint")
- // if(!(head.wpflags & WAYPOINTFLAG_GENERATED))
- {
- if(bot_waypoint_queue_goal)
- bot_waypoint_queue_goal.bot_waypoint_queue_nextgoal = head;
- else
- first = head;
-
- bot_waypoint_queue_goal = head;
- bot_waypoint_queue_goal.bot_waypoint_queue_nextgoal = world;
- }
-
- head = head.chain;
- }
-
- if (first)
- bot_waypoint_queue_goal = first;
- else
- {
- bot_debug(strcat(self.netname, " stuck, cannot walk to any waypoint at all\n"));
- bot_waypoint_queue_owner = world;
- }
- }
-}
-
-// Support for debugging tracewalk visually
-
-void debugresetnodes()
-{
- debuglastnode = '0 0 0';
-}
-
-void debugnode(vector node)
-{SELFPARAM();
- if (!IS_PLAYER(self))
- return;
-
- if(debuglastnode=='0 0 0')
- {
- debuglastnode = node;
- return;
- }
-
- te_lightning2(world, node, debuglastnode);
- debuglastnode = node;
-}
-
-void debugnodestatus(vector position, float status)
-{
- vector c;
-
- switch (status)
- {
- case DEBUG_NODE_SUCCESS:
- c = '0 15 0';
- break;
- case DEBUG_NODE_WARNING:
- c = '15 15 0';
- break;
- case DEBUG_NODE_FAIL:
- c = '15 0 0';
- break;
- default:
- c = '15 15 15';
- }
-
- te_customflash(position, 40, 2, c);
-}
-
-// Support for debugging the goal stack visually
-
-.float goalcounter;
-.vector lastposition;
-
-// Debug the goal stack visually
-void debuggoalstack()
-{SELFPARAM();
- entity goal;
- vector org, go;
-
- if(self.goalcounter==0)goal=self.goalcurrent;
- else if(self.goalcounter==1)goal=self.goalstack01;
- else if(self.goalcounter==2)goal=self.goalstack02;
- else if(self.goalcounter==3)goal=self.goalstack03;
- else if(self.goalcounter==4)goal=self.goalstack04;
- else if(self.goalcounter==5)goal=self.goalstack05;
- else if(self.goalcounter==6)goal=self.goalstack06;
- else if(self.goalcounter==7)goal=self.goalstack07;
- else if(self.goalcounter==8)goal=self.goalstack08;
- else if(self.goalcounter==9)goal=self.goalstack09;
- else if(self.goalcounter==10)goal=self.goalstack10;
- else if(self.goalcounter==11)goal=self.goalstack11;
- else if(self.goalcounter==12)goal=self.goalstack12;
- else if(self.goalcounter==13)goal=self.goalstack13;
- else if(self.goalcounter==14)goal=self.goalstack14;
- else if(self.goalcounter==15)goal=self.goalstack15;
- else if(self.goalcounter==16)goal=self.goalstack16;
- else if(self.goalcounter==17)goal=self.goalstack17;
- else if(self.goalcounter==18)goal=self.goalstack18;
- else if(self.goalcounter==19)goal=self.goalstack19;
- else if(self.goalcounter==20)goal=self.goalstack20;
- else if(self.goalcounter==21)goal=self.goalstack21;
- else if(self.goalcounter==22)goal=self.goalstack22;
- else if(self.goalcounter==23)goal=self.goalstack23;
- else if(self.goalcounter==24)goal=self.goalstack24;
- else if(self.goalcounter==25)goal=self.goalstack25;
- else if(self.goalcounter==26)goal=self.goalstack26;
- else if(self.goalcounter==27)goal=self.goalstack27;
- else if(self.goalcounter==28)goal=self.goalstack28;
- else if(self.goalcounter==29)goal=self.goalstack29;
- else if(self.goalcounter==30)goal=self.goalstack30;
- else if(self.goalcounter==31)goal=self.goalstack31;
- else goal=world;
-
- if(goal==world)
- {
- self.goalcounter = 0;
- self.lastposition='0 0 0';
- return;
- }
-
- if(self.lastposition=='0 0 0')
- org = self.origin;
- else
- org = self.lastposition;
-
-
- go = ( goal.absmin + goal.absmax ) * 0.5;
- te_lightning2(world, org, go);
- self.lastposition = go;
-
- self.goalcounter++;
-}
+++ /dev/null
-#ifndef NAVIGATION_H
-#define NAVIGATION_H
-/*
- * Globals and Fields
- */
-
-float navigation_bestrating;
-float bot_navigation_movemode;
-float navigation_testtracewalk;
-
-vector jumpstepheightvec;
-vector stepheightvec;
-
-entity botframe_dangerwaypoint;
-entity navigation_bestgoal;
-
-// stack of current goals (the last one of which may be an item or other
-// desirable object, the rest are typically waypoints to reach it)
-.entity goalcurrent, goalstack01, goalstack02, goalstack03;
-.entity goalstack04, goalstack05, goalstack06, goalstack07;
-.entity goalstack08, goalstack09, goalstack10, goalstack11;
-.entity goalstack12, goalstack13, goalstack14, goalstack15;
-.entity goalstack16, goalstack17, goalstack18, goalstack19;
-.entity goalstack20, goalstack21, goalstack22, goalstack23;
-.entity goalstack24, goalstack25, goalstack26, goalstack27;
-.entity goalstack28, goalstack29, goalstack30, goalstack31;
-.entity nearestwaypoint;
-
-.float nearestwaypointtimeout;
-.float navigation_hasgoals;
-.float lastteleporttime;
-
-.float blacklisted;
-
-.entity navigation_jetpack_goal;
-.vector navigation_jetpack_point;
-
-const float DEBUG_NODE_SUCCESS = 1;
-const float DEBUG_NODE_WARNING = 2;
-const float DEBUG_NODE_FAIL = 3;
-vector debuglastnode;
-
-entity bot_waypoint_queue_owner; // Owner of the temporary list of goals
-entity bot_waypoint_queue_goal; // Head of the temporary list of goals
-.entity bot_waypoint_queue_nextgoal;
-entity bot_waypoint_queue_bestgoal;
-float bot_waypoint_queue_bestgoalrating;
-
-/*
- * Functions
- */
-
-void debugresetnodes();
-void debugnode(vector node);
-void debugnodestatus(vector position, float status);
-
-void debuggoalstack();
-
-float tracewalk(entity e, vector start, vector m1, vector m2, vector end, float movemode);
-
-float navigation_markroutes_nearestwaypoints(entity waylist, float maxdist);
-float navigation_routetogoal(entity e, vector startposition);
-
-void navigation_clearroute();
-void navigation_pushroute(entity e);
-void navigation_poproute();
-void navigation_markroutes_checkwaypoint(entity w, entity wp, float cost2, vector p);
-void navigation_markroutes(entity fixed_source_waypoint);
-void navigation_markroutes_inverted(entity fixed_source_waypoint);
-void navigation_routerating(entity e, float f, float rangebias);
-void navigation_poptouchedgoals();
-void navigation_goalrating_start();
-void navigation_goalrating_end();
-void navigation_unstuck();
-
-void botframe_updatedangerousobjects(float maxupdate);
-
-entity navigation_findnearestwaypoint(entity ent, float walkfromwp);
-float navigation_waypoint_will_link(vector v, vector org, entity ent, float walkfromwp, float bestdist);
-#endif
--- /dev/null
+bool bot_aim(float shotspeed, float shotspeedupward, float maxshottime, float applygravity) { return false; }
+void bot_clientconnect() { }
+void bot_clientdisconnect() { }
+void bot_cmdhelp(string scmd) { }
+void bot_endgame() { }
+bool bot_fixcount() { return true; }
+void bot_list_commands() { }
+void bot_queuecommand(entity bot, string cmdstring) { }
+void bot_relinkplayerlist() { }
+void bot_resetqueues() { }
+void bot_serverframe() { }
+bool bot_shouldattack(entity e) { return false; }
+void bot_think() { }
+
+entity find_bot_by_name(string name) { return NULL; }
+entity find_bot_by_number(float number) { return NULL; }
+
+void havocbot_goalrating_controlpoints(float ratingscale, vector org, float sradius) { }
+void havocbot_goalrating_enemyplayers(float ratingscale, vector org, float sradius) { }
+void havocbot_goalrating_items(float ratingscale, vector org, float sradius) { }
+
+entity navigation_findnearestwaypoint(entity ent, float walkfromwp) { return NULL; }
+void navigation_goalrating_end() { }
+void navigation_goalrating_start() { }
+void navigation_markroutes(entity fixed_source_waypoint) { }
+void navigation_markroutes_inverted(entity fixed_source_waypoint) { }
+void navigation_routerating(entity e, float f, float rangebias) { }
+
+bool tracewalk(entity e, vector start, vector m1, vector m2, vector end, float movemode) { return false; }
+
+void waypoint_remove(entity e) { }
+void waypoint_saveall() { }
+void waypoint_schedulerelinkall() { }
+void waypoint_schedulerelink(entity wp) { }
+void waypoint_spawnforitem(entity e) { }
+void waypoint_spawnforitem_force(entity e, vector org) { }
+void waypoint_spawnforteleporter(entity e, vector destination, float timetaken) { }
+void waypoint_spawnforteleporter_v(entity e, vector org, vector destination, float timetaken) { }
+entity waypoint_spawn(vector m1, vector m2, float f) { return NULL; }
+++ /dev/null
-#include "scripting.qh"
-#include "../_all.qh"
-
-#include "bot.qh"
-
-.float bot_cmdqueuebuf_allocated;
-.float bot_cmdqueuebuf;
-.float bot_cmdqueuebuf_start;
-.float bot_cmdqueuebuf_end;
-
-void bot_clearqueue(entity bot)
-{
- if(!bot.bot_cmdqueuebuf_allocated)
- return;
- buf_del(bot.bot_cmdqueuebuf);
- bot.bot_cmdqueuebuf_allocated = false;
- LOG_TRACE("bot ", bot.netname, " queue cleared\n");
-}
-
-void bot_queuecommand(entity bot, string cmdstring)
-{
- if(!bot.bot_cmdqueuebuf_allocated)
- {
- bot.bot_cmdqueuebuf = buf_create();
- bot.bot_cmdqueuebuf_allocated = true;
- bot.bot_cmdqueuebuf_start = 0;
- bot.bot_cmdqueuebuf_end = 0;
- }
-
- bufstr_set(bot.bot_cmdqueuebuf, bot.bot_cmdqueuebuf_end, cmdstring);
-
- // if the command was a "sound" command, precache the sound NOW
- // this prevents lagging!
- {
- float sp;
- string parm;
- string cmdstr;
-
- sp = strstrofs(cmdstring, " ", 0);
- if(sp >= 0)
- {
- parm = substring(cmdstring, sp + 1, -1);
- cmdstr = substring(cmdstring, 0, sp);
- if(cmdstr == "sound")
- {
- // find the LAST word
- for (;;)
- {
- sp = strstrofs(parm, " ", 0);
- if(sp < 0)
- break;
- parm = substring(parm, sp + 1, -1);
- }
- precache_sound(parm);
- }
- }
- }
-
- bot.bot_cmdqueuebuf_end += 1;
-}
-
-void bot_dequeuecommand(entity bot, float idx)
-{
- if(!bot.bot_cmdqueuebuf_allocated)
- error("dequeuecommand but no queue allocated");
- if(idx < bot.bot_cmdqueuebuf_start)
- error("dequeueing a command in the past");
- if(idx >= bot.bot_cmdqueuebuf_end)
- error("dequeueing a command in the future");
- bufstr_set(bot.bot_cmdqueuebuf, idx, "");
- if(idx == bot.bot_cmdqueuebuf_start)
- bot.bot_cmdqueuebuf_start += 1;
- if(bot.bot_cmdqueuebuf_start >= bot.bot_cmdqueuebuf_end)
- bot_clearqueue(bot);
-}
-
-string bot_readcommand(entity bot, float idx)
-{
- if(!bot.bot_cmdqueuebuf_allocated)
- error("readcommand but no queue allocated");
- if(idx < bot.bot_cmdqueuebuf_start)
- error("reading a command in the past");
- if(idx >= bot.bot_cmdqueuebuf_end)
- error("reading a command in the future");
- return bufstr_get(bot.bot_cmdqueuebuf, idx);
-}
-
-float bot_havecommand(entity bot, float idx)
-{
- if(!bot.bot_cmdqueuebuf_allocated)
- return 0;
- if(idx < bot.bot_cmdqueuebuf_start)
- return 0;
- if(idx >= bot.bot_cmdqueuebuf_end)
- return 0;
- return 1;
-}
-
-const int MAX_BOT_PLACES = 4;
-.float bot_places_count;
-.entity bot_places[MAX_BOT_PLACES];
-.string bot_placenames[MAX_BOT_PLACES];
-entity bot_getplace(string placename)
-{SELFPARAM();
- entity e;
- if(substring(placename, 0, 1) == "@")
- {
- int i, p;
- placename = substring(placename, 1, -1);
- string s, s2;
- for(i = 0; i < self.bot_places_count; ++i)
- if(self.(bot_placenames[i]) == placename)
- return self.(bot_places[i]);
- // now: i == self.bot_places_count
- s = s2 = cvar_string(placename);
- p = strstrofs(s2, " ", 0);
- if(p >= 0)
- {
- s = substring(s2, 0, p);
- //print("places: ", placename, " -> ", cvar_string(placename), "\n");
- cvar_set(placename, strcat(substring(s2, p+1, -1), " ", s));
- //print("places: ", placename, " := ", cvar_string(placename), "\n");
- }
- e = find(world, targetname, s);
- if(!e)
- LOG_INFO("invalid place ", s, "\n");
- if(i < MAX_BOT_PLACES)
- {
- self.(bot_placenames[i]) = strzone(placename);
- self.(bot_places[i]) = e;
- self.bot_places_count += 1;
- }
- return e;
- }
- else
- {
- e = find(world, targetname, placename);
- if(!e)
- LOG_INFO("invalid place ", placename, "\n");
- return e;
- }
-}
-
-
-// Initialize global commands list
-// NOTE: New commands should be initialized here
-void bot_commands_init()
-{
- bot_cmd_string[BOT_CMD_NULL] = "";
- bot_cmd_parm_type[BOT_CMD_NULL] = BOT_CMD_PARAMETER_NONE;
-
- bot_cmd_string[BOT_CMD_PAUSE] = "pause";
- bot_cmd_parm_type[BOT_CMD_PAUSE] = BOT_CMD_PARAMETER_NONE;
-
- bot_cmd_string[BOT_CMD_CONTINUE] = "continue";
- bot_cmd_parm_type[BOT_CMD_CONTINUE] = BOT_CMD_PARAMETER_NONE;
-
- bot_cmd_string[BOT_CMD_WAIT] = "wait";
- bot_cmd_parm_type[BOT_CMD_WAIT] = BOT_CMD_PARAMETER_FLOAT;
-
- bot_cmd_string[BOT_CMD_TURN] = "turn";
- bot_cmd_parm_type[BOT_CMD_TURN] = BOT_CMD_PARAMETER_FLOAT;
-
- bot_cmd_string[BOT_CMD_MOVETO] = "moveto";
- bot_cmd_parm_type[BOT_CMD_MOVETO] = BOT_CMD_PARAMETER_VECTOR;
-
- bot_cmd_string[BOT_CMD_MOVETOTARGET] = "movetotarget";
- bot_cmd_parm_type[BOT_CMD_MOVETOTARGET] = BOT_CMD_PARAMETER_STRING;
-
- bot_cmd_string[BOT_CMD_RESETGOAL] = "resetgoal";
- bot_cmd_parm_type[BOT_CMD_RESETGOAL] = BOT_CMD_PARAMETER_NONE;
-
- bot_cmd_string[BOT_CMD_CC] = "cc";
- bot_cmd_parm_type[BOT_CMD_CC] = BOT_CMD_PARAMETER_STRING;
-
- bot_cmd_string[BOT_CMD_IF] = "if";
- bot_cmd_parm_type[BOT_CMD_IF] = BOT_CMD_PARAMETER_STRING;
-
- bot_cmd_string[BOT_CMD_ELSE] = "else";
- bot_cmd_parm_type[BOT_CMD_ELSE] = BOT_CMD_PARAMETER_NONE;
-
- bot_cmd_string[BOT_CMD_FI] = "fi";
- bot_cmd_parm_type[BOT_CMD_FI] = BOT_CMD_PARAMETER_NONE;
-
- bot_cmd_string[BOT_CMD_RESETAIM] = "resetaim";
- bot_cmd_parm_type[BOT_CMD_RESETAIM] = BOT_CMD_PARAMETER_NONE;
-
- bot_cmd_string[BOT_CMD_AIM] = "aim";
- bot_cmd_parm_type[BOT_CMD_AIM] = BOT_CMD_PARAMETER_STRING;
-
- bot_cmd_string[BOT_CMD_AIMTARGET] = "aimtarget";
- bot_cmd_parm_type[BOT_CMD_AIMTARGET] = BOT_CMD_PARAMETER_STRING;
-
- bot_cmd_string[BOT_CMD_PRESSKEY] = "presskey";
- bot_cmd_parm_type[BOT_CMD_PRESSKEY] = BOT_CMD_PARAMETER_STRING;
-
- bot_cmd_string[BOT_CMD_RELEASEKEY] = "releasekey";
- bot_cmd_parm_type[BOT_CMD_RELEASEKEY] = BOT_CMD_PARAMETER_STRING;
-
- bot_cmd_string[BOT_CMD_SELECTWEAPON] = "selectweapon";
- bot_cmd_parm_type[BOT_CMD_SELECTWEAPON] = BOT_CMD_PARAMETER_FLOAT;
-
- bot_cmd_string[BOT_CMD_IMPULSE] = "impulse";
- bot_cmd_parm_type[BOT_CMD_IMPULSE] = BOT_CMD_PARAMETER_FLOAT;
-
- bot_cmd_string[BOT_CMD_WAIT_UNTIL] = "wait_until";
- bot_cmd_parm_type[BOT_CMD_WAIT_UNTIL] = BOT_CMD_PARAMETER_FLOAT;
-
- bot_cmd_string[BOT_CMD_BARRIER] = "barrier";
- bot_cmd_parm_type[BOT_CMD_BARRIER] = BOT_CMD_PARAMETER_NONE;
-
- bot_cmd_string[BOT_CMD_CONSOLE] = "console";
- bot_cmd_parm_type[BOT_CMD_CONSOLE] = BOT_CMD_PARAMETER_STRING;
-
- bot_cmd_string[BOT_CMD_SOUND] = "sound";
- bot_cmd_parm_type[BOT_CMD_SOUND] = BOT_CMD_PARAMETER_STRING;
-
- bot_cmd_string[BOT_CMD_DEBUG_ASSERT_CANFIRE] = "debug_assert_canfire";
- bot_cmd_parm_type[BOT_CMD_DEBUG_ASSERT_CANFIRE] = BOT_CMD_PARAMETER_FLOAT;
-
- bot_cmds_initialized = true;
-}
-
-// Returns first bot with matching name
-entity find_bot_by_name(string name)
-{
- entity bot;
-
- bot = findchainflags(flags, FL_CLIENT);
- while (bot)
- {
- if(IS_BOT_CLIENT(bot))
- if(bot.netname==name)
- return bot;
-
- bot = bot.chain;
- }
-
- return world;
-}
-
-// Returns a bot by number on list
-entity find_bot_by_number(float number)
-{
- entity bot;
- float c = 0;
-
- if(!number)
- return world;
-
- bot = findchainflags(flags, FL_CLIENT);
- while (bot)
- {
- if(IS_BOT_CLIENT(bot))
- {
- if(++c==number)
- return bot;
- }
- bot = bot.chain;
- }
-
- return world;
-}
-
-float bot_decodecommand(string cmdstring)
-{
- float cmd_parm_type;
- float sp;
- string parm;
-
- sp = strstrofs(cmdstring, " ", 0);
- if(sp < 0)
- {
- parm = "";
- }
- else
- {
- parm = substring(cmdstring, sp + 1, -1);
- cmdstring = substring(cmdstring, 0, sp);
- }
-
- if(!bot_cmds_initialized)
- bot_commands_init();
-
- int i;
- for(i=1;i<BOT_CMD_COUNTER;++i)
- {
- if(bot_cmd_string[i]!=cmdstring)
- continue;
-
- cmd_parm_type = bot_cmd_parm_type[i];
-
- if(cmd_parm_type!=BOT_CMD_PARAMETER_NONE&&parm=="")
- {
- LOG_INFO("ERROR: A parameter is required for this command\n");
- return 0;
- }
-
- // Load command into queue
- bot_cmd.bot_cmd_type = i;
-
- // Attach parameter
- switch(cmd_parm_type)
- {
- case BOT_CMD_PARAMETER_FLOAT:
- bot_cmd.bot_cmd_parm_float = stof(parm);
- break;
- case BOT_CMD_PARAMETER_STRING:
- if(bot_cmd.bot_cmd_parm_string)
- strunzone(bot_cmd.bot_cmd_parm_string);
- bot_cmd.bot_cmd_parm_string = strzone(parm);
- break;
- case BOT_CMD_PARAMETER_VECTOR:
- bot_cmd.bot_cmd_parm_vector = stov(parm);
- break;
- default:
- break;
- }
- return 1;
- }
- LOG_INFO("ERROR: No such command '", cmdstring, "'\n");
- return 0;
-}
-
-void bot_cmdhelp(string scmd)
-{
- int i, ntype;
- string stype;
-
- if(!bot_cmds_initialized)
- bot_commands_init();
-
- for(i=1;i<BOT_CMD_COUNTER;++i)
- {
- if(bot_cmd_string[i]!=scmd)
- continue;
-
- ntype = bot_cmd_parm_type[i];
-
- switch(ntype)
- {
- case BOT_CMD_PARAMETER_FLOAT:
- stype = "float number";
- break;
- case BOT_CMD_PARAMETER_STRING:
- stype = "string";
- break;
- case BOT_CMD_PARAMETER_VECTOR:
- stype = "vector";
- break;
- default:
- stype = "none";
- break;
- }
-
- LOG_INFO(strcat("Command: ",bot_cmd_string[i],"\nParameter: <",stype,"> \n"));
-
- LOG_INFO("Description: ");
- switch(i)
- {
- case BOT_CMD_PAUSE:
- LOG_INFO("Stops the bot completely. Any command other than 'continue' will be ignored.");
- break;
- case BOT_CMD_CONTINUE:
- LOG_INFO("Disable paused status");
- break;
- case BOT_CMD_WAIT:
- LOG_INFO("Pause command parsing and bot ai for N seconds. Pressed key will remain pressed");
- break;
- case BOT_CMD_WAIT_UNTIL:
- LOG_INFO("Pause command parsing and bot ai until time is N from the last barrier. Pressed key will remain pressed");
- break;
- case BOT_CMD_BARRIER:
- LOG_INFO("Waits till all bots that have a command queue reach this command. Pressed key will remain pressed");
- break;
- case BOT_CMD_TURN:
- LOG_INFO("Look to the right or left N degrees. For turning to the left use positive numbers.");
- break;
- case BOT_CMD_MOVETO:
- LOG_INFO("Walk to an specific coordinate on the map. Usage: moveto \"x y z\"");
- break;
- case BOT_CMD_MOVETOTARGET:
- LOG_INFO("Walk to the specific target on the map");
- break;
- case BOT_CMD_RESETGOAL:
- LOG_INFO("Resets the goal stack");
- break;
- case BOT_CMD_CC:
- LOG_INFO("Execute client command. Examples: cc \"say something\"; cc god; cc \"name newnickname\"; cc kill;");
- break;
- case BOT_CMD_IF:
- LOG_INFO("Perform simple conditional execution.\n");
- LOG_INFO("Syntax: \n");
- LOG_INFO(" sv_cmd .. if \"condition\"\n");
- LOG_INFO(" sv_cmd .. <instruction if true>\n");
- LOG_INFO(" sv_cmd .. <instruction if true>\n");
- LOG_INFO(" sv_cmd .. else\n");
- LOG_INFO(" sv_cmd .. <instruction if false>\n");
- LOG_INFO(" sv_cmd .. <instruction if false>\n");
- LOG_INFO(" sv_cmd .. fi\n");
- LOG_INFO("Conditions: a=b, a>b, a<b, a\t\t(spaces not allowed)\n");
- LOG_INFO(" Values in conditions can be numbers, cvars in the form cvar.cvar_string or special fields\n");
- LOG_INFO("Fields: health, speed, flagcarrier\n");
- LOG_INFO("Examples: if health>50; if health>cvar.g_balance_laser_primary_damage; if flagcarrier;");
- break;
- case BOT_CMD_RESETAIM:
- LOG_INFO("Points the aim to the coordinates x,y 0,0");
- break;
- case BOT_CMD_AIM:
- LOG_INFO("Move the aim x/y (horizontal/vertical) degrees relatives to the bot\n");
- LOG_INFO("There is a 3rd optional parameter telling in how many seconds the aim has to reach the new position\n");
- LOG_INFO("Examples: aim \"90 0\" // Turn 90 degrees inmediately (positive numbers move to the left/up)\n");
- LOG_INFO(" aim \"0 90 2\" // Will gradually look to the sky in the next two seconds");
- break;
- case BOT_CMD_AIMTARGET:
- LOG_INFO("Points the aim to given target");
- break;
- case BOT_CMD_PRESSKEY:
- LOG_INFO("Press one of the following keys: forward, backward, left, right, jump, crouch, attack1, attack2, use\n");
- LOG_INFO("Multiple keys can be pressed at time (with many presskey calls) and it will remain pressed until the command \"releasekey\" is called");
- LOG_INFO("Note: The script will not return the control to the bot ai until all keys are released");
- break;
- case BOT_CMD_RELEASEKEY:
- LOG_INFO("Release previoulsy used keys. Use the parameter \"all\" to release all keys");
- break;
- case BOT_CMD_SOUND:
- LOG_INFO("play sound file at bot location");
- break;
- case BOT_CMD_DEBUG_ASSERT_CANFIRE:
- LOG_INFO("verify the state of the weapon entity");
- break;
- default:
- LOG_INFO("This command has no description yet.");
- break;
- }
- LOG_INFO("\n");
- }
-}
-
-void bot_list_commands()
-{
- int i;
- string ptype;
-
- if(!bot_cmds_initialized)
- bot_commands_init();
-
- LOG_INFO("List of all available commands:\n");
- LOG_INFO(" Command - Parameter Type\n");
-
- for(i=1;i<BOT_CMD_COUNTER;++i)
- {
- switch(bot_cmd_parm_type[i])
- {
- case BOT_CMD_PARAMETER_FLOAT:
- ptype = "float number";
- break;
- case BOT_CMD_PARAMETER_STRING:
- ptype = "string";
- break;
- case BOT_CMD_PARAMETER_VECTOR:
- ptype = "vector";
- break;
- default:
- ptype = "none";
- break;
- }
- LOG_INFO(strcat(" ",bot_cmd_string[i]," - <",ptype,"> \n"));
- }
-}
-
-// Commands code
-.int bot_exec_status;
-
-void SV_ParseClientCommand(string s);
-float bot_cmd_cc()
-{
- SV_ParseClientCommand(bot_cmd.bot_cmd_parm_string);
- return CMD_STATUS_FINISHED;
-}
-
-float bot_cmd_impulse()
-{SELFPARAM();
- self.impulse = bot_cmd.bot_cmd_parm_float;
- return CMD_STATUS_FINISHED;
-}
-
-float bot_cmd_continue()
-{SELFPARAM();
- self.bot_exec_status &= ~BOT_EXEC_STATUS_PAUSED;
- return CMD_STATUS_FINISHED;
-}
-
-.float bot_cmd_wait_time;
-float bot_cmd_wait()
-{SELFPARAM();
- if(self.bot_exec_status & BOT_EXEC_STATUS_WAITING)
- {
- if(time>=self.bot_cmd_wait_time)
- {
- self.bot_exec_status &= ~BOT_EXEC_STATUS_WAITING;
- return CMD_STATUS_FINISHED;
- }
- else
- return CMD_STATUS_EXECUTING;
- }
-
- self.bot_cmd_wait_time = time + bot_cmd.bot_cmd_parm_float;
- self.bot_exec_status |= BOT_EXEC_STATUS_WAITING;
- return CMD_STATUS_EXECUTING;
-}
-
-float bot_cmd_wait_until()
-{SELFPARAM();
- if(time < bot_cmd.bot_cmd_parm_float + bot_barriertime)
- {
- self.bot_exec_status |= BOT_EXEC_STATUS_WAITING;
- return CMD_STATUS_EXECUTING;
- }
- self.bot_exec_status &= ~BOT_EXEC_STATUS_WAITING;
- return CMD_STATUS_FINISHED;
-}
-
-float bot_cmd_barrier()
-{SELFPARAM();
- entity cl;
-
- // 0 = no barrier, 1 = waiting, 2 = waiting finished
-
- if(self.bot_barrier == 0) // initialization
- {
- self.bot_barrier = 1;
-
- //self.colormod = '4 4 0';
- }
-
- if(self.bot_barrier == 1) // find other bots
- {
- FOR_EACH_CLIENT(cl) if(cl.isbot)
- {
- if(cl.bot_cmdqueuebuf_allocated)
- if(cl.bot_barrier != 1)
- return CMD_STATUS_EXECUTING; // not all are at the barrier yet
- }
-
- // all bots hit the barrier!
- FOR_EACH_CLIENT(cl) if(cl.isbot)
- {
- cl.bot_barrier = 2; // acknowledge barrier
- }
-
- bot_barriertime = time;
- }
-
- // if we get here, the barrier is finished
- // so end it...
- self.bot_barrier = 0;
- //self.colormod = '0 0 0';
-
- return CMD_STATUS_FINISHED;
-}
-
-float bot_cmd_turn()
-{SELFPARAM();
- self.v_angle_y = self.v_angle.y + bot_cmd.bot_cmd_parm_float;
- self.v_angle_y = self.v_angle.y - floor(self.v_angle.y / 360) * 360;
- return CMD_STATUS_FINISHED;
-}
-
-float bot_cmd_select_weapon()
-{SELFPARAM();
- float id;
-
- id = bot_cmd.bot_cmd_parm_float;
-
- if(id < WEP_FIRST || id > WEP_LAST)
- return CMD_STATUS_ERROR;
-
- if(client_hasweapon(self, id, true, false))
- self.switchweapon = id;
- else
- return CMD_STATUS_ERROR;
-
- return CMD_STATUS_FINISHED;
-}
-
-.int bot_cmd_condition_status;
-
-const int CMD_CONDITION_NONE = 0;
-const int CMD_CONDITION_true = 1;
-const int CMD_CONDITION_false = 2;
-const int CMD_CONDITION_true_BLOCK = 4;
-const int CMD_CONDITION_false_BLOCK = 8;
-
-float bot_cmd_eval(string expr)
-{SELFPARAM();
- // Search for numbers
- if(strstrofs("0123456789", substring(expr, 0, 1), 0) >= 0)
- {
- return stof(expr);
- }
-
- // Search for cvars
- if(substring(expr, 0, 5)=="cvar.")
- {
- return cvar(substring(expr, 5, strlen(expr)));
- }
-
- // Search for fields
- switch(expr)
- {
- case "health":
- return self.health;
- case "speed":
- return vlen(self.velocity);
- case "flagcarrier":
- return ((self.flagcarried!=world));
- }
-
- LOG_INFO(strcat("ERROR: Unable to convert the expression '",expr,"' into a numeric value\n"));
- return 0;
-}
-
-float bot_cmd_if()
-{SELFPARAM();
- string expr, val_a, val_b;
- float cmpofs;
-
- if(self.bot_cmd_condition_status != CMD_CONDITION_NONE)
- {
- // Only one "if" block is allowed at time
- LOG_INFO("ERROR: Only one conditional block can be processed at time");
- bot_clearqueue(self);
- return CMD_STATUS_ERROR;
- }
-
- self.bot_cmd_condition_status |= CMD_CONDITION_true_BLOCK;
-
- // search for operators
- expr = bot_cmd.bot_cmd_parm_string;
-
- cmpofs = strstrofs(expr,"=",0);
-
- if(cmpofs>0)
- {
- val_a = substring(expr,0,cmpofs);
- val_b = substring(expr,cmpofs+1,strlen(expr));
-
- if(bot_cmd_eval(val_a)==bot_cmd_eval(val_b))
- self.bot_cmd_condition_status |= CMD_CONDITION_true;
- else
- self.bot_cmd_condition_status |= CMD_CONDITION_false;
-
- return CMD_STATUS_FINISHED;
- }
-
- cmpofs = strstrofs(expr,">",0);
-
- if(cmpofs>0)
- {
- val_a = substring(expr,0,cmpofs);
- val_b = substring(expr,cmpofs+1,strlen(expr));
-
- if(bot_cmd_eval(val_a)>bot_cmd_eval(val_b))
- self.bot_cmd_condition_status |= CMD_CONDITION_true;
- else
- self.bot_cmd_condition_status |= CMD_CONDITION_false;
-
- return CMD_STATUS_FINISHED;
- }
-
- cmpofs = strstrofs(expr,"<",0);
-
- if(cmpofs>0)
- {
- val_a = substring(expr,0,cmpofs);
- val_b = substring(expr,cmpofs+1,strlen(expr));
-
- if(bot_cmd_eval(val_a)<bot_cmd_eval(val_b))
- self.bot_cmd_condition_status |= CMD_CONDITION_true;
- else
- self.bot_cmd_condition_status |= CMD_CONDITION_false;
-
- return CMD_STATUS_FINISHED;
- }
-
- if(bot_cmd_eval(expr))
- self.bot_cmd_condition_status |= CMD_CONDITION_true;
- else
- self.bot_cmd_condition_status |= CMD_CONDITION_false;
-
- return CMD_STATUS_FINISHED;
-}
-
-float bot_cmd_else()
-{SELFPARAM();
- self.bot_cmd_condition_status &= ~CMD_CONDITION_true_BLOCK;
- self.bot_cmd_condition_status |= CMD_CONDITION_false_BLOCK;
- return CMD_STATUS_FINISHED;
-}
-
-float bot_cmd_fi()
-{SELFPARAM();
- self.bot_cmd_condition_status = CMD_CONDITION_NONE;
- return CMD_STATUS_FINISHED;
-}
-
-float bot_cmd_resetaim()
-{SELFPARAM();
- self.v_angle = '0 0 0';
- return CMD_STATUS_FINISHED;
-}
-
-.float bot_cmd_aim_begintime;
-.float bot_cmd_aim_endtime;
-.vector bot_cmd_aim_begin;
-.vector bot_cmd_aim_end;
-
-float bot_cmd_aim()
-{SELFPARAM();
- // Current direction
- if(self.bot_cmd_aim_endtime)
- {
- float progress;
-
- progress = min(1 - (self.bot_cmd_aim_endtime - time) / (self.bot_cmd_aim_endtime - self.bot_cmd_aim_begintime),1);
- self.v_angle = self.bot_cmd_aim_begin + ((self.bot_cmd_aim_end - self.bot_cmd_aim_begin) * progress);
-
- if(time>=self.bot_cmd_aim_endtime)
- {
- self.bot_cmd_aim_endtime = 0;
- return CMD_STATUS_FINISHED;
- }
- else
- return CMD_STATUS_EXECUTING;
- }
-
- // New aiming direction
- string parms;
- float tokens, step;
-
- parms = bot_cmd.bot_cmd_parm_string;
-
- tokens = tokenizebyseparator(parms, " ");
-
- if(tokens<2||tokens>3)
- return CMD_STATUS_ERROR;
-
- step = (tokens == 3) ? stof(argv(2)) : 0;
-
- if(step == 0)
- {
- self.v_angle_x -= stof(argv(1));
- self.v_angle_y += stof(argv(0));
- return CMD_STATUS_FINISHED;
- }
-
- self.bot_cmd_aim_begin = self.v_angle;
-
- self.bot_cmd_aim_end_x = self.v_angle.x - stof(argv(1));
- self.bot_cmd_aim_end_y = self.v_angle.y + stof(argv(0));
- self.bot_cmd_aim_end_z = 0;
-
- self.bot_cmd_aim_begintime = time;
- self.bot_cmd_aim_endtime = time + step;
-
- return CMD_STATUS_EXECUTING;
-}
-
-float bot_cmd_aimtarget()
-{SELFPARAM();
- if(self.bot_cmd_aim_endtime)
- {
- return bot_cmd_aim();
- }
-
- entity e;
- string parms;
- vector v;
- float tokens, step;
-
- parms = bot_cmd.bot_cmd_parm_string;
-
- tokens = tokenizebyseparator(parms, " ");
-
- e = bot_getplace(argv(0));
- if(!e)
- return CMD_STATUS_ERROR;
-
- v = e.origin + (e.mins + e.maxs) * 0.5;
-
- if(tokens==1)
- {
- self.v_angle = vectoangles(v - (self.origin + self.view_ofs));
- self.v_angle_x = -self.v_angle.x;
- return CMD_STATUS_FINISHED;
- }
-
- if(tokens<1||tokens>2)
- return CMD_STATUS_ERROR;
-
- step = stof(argv(1));
-
- self.bot_cmd_aim_begin = self.v_angle;
- self.bot_cmd_aim_end = vectoangles(v - (self.origin + self.view_ofs));
- self.bot_cmd_aim_end_x = -self.bot_cmd_aim_end.x;
-
- self.bot_cmd_aim_begintime = time;
- self.bot_cmd_aim_endtime = time + step;
-
- return CMD_STATUS_EXECUTING;
-}
-
-.int bot_cmd_keys;
-
-const int BOT_CMD_KEY_NONE = 0;
-const int BOT_CMD_KEY_FORWARD = 1;
-const int BOT_CMD_KEY_BACKWARD = 2;
-const int BOT_CMD_KEY_RIGHT = 4;
-const int BOT_CMD_KEY_LEFT = 8;
-const int BOT_CMD_KEY_JUMP = 16;
-const int BOT_CMD_KEY_ATTACK1 = 32;
-const int BOT_CMD_KEY_ATTACK2 = 64;
-const int BOT_CMD_KEY_USE = 128;
-const int BOT_CMD_KEY_HOOK = 256;
-const int BOT_CMD_KEY_CROUCH = 512;
-const int BOT_CMD_KEY_CHAT = 1024;
-
-float bot_presskeys()
-{SELFPARAM();
- self.movement = '0 0 0';
- self.BUTTON_JUMP = false;
- self.BUTTON_CROUCH = false;
- self.BUTTON_ATCK = false;
- self.BUTTON_ATCK2 = false;
- self.BUTTON_USE = false;
- self.BUTTON_HOOK = false;
- self.BUTTON_CHAT = false;
-
- if(self.bot_cmd_keys == BOT_CMD_KEY_NONE)
- return false;
-
- if(self.bot_cmd_keys & BOT_CMD_KEY_FORWARD)
- self.movement_x = autocvar_sv_maxspeed;
- else if(self.bot_cmd_keys & BOT_CMD_KEY_BACKWARD)
- self.movement_x = -autocvar_sv_maxspeed;
-
- if(self.bot_cmd_keys & BOT_CMD_KEY_RIGHT)
- self.movement_y = autocvar_sv_maxspeed;
- else if(self.bot_cmd_keys & BOT_CMD_KEY_LEFT)
- self.movement_y = -autocvar_sv_maxspeed;
-
- if(self.bot_cmd_keys & BOT_CMD_KEY_JUMP)
- self.BUTTON_JUMP = true;
-
- if(self.bot_cmd_keys & BOT_CMD_KEY_CROUCH)
- self.BUTTON_CROUCH = true;
-
- if(self.bot_cmd_keys & BOT_CMD_KEY_ATTACK1)
- self.BUTTON_ATCK = true;
-
- if(self.bot_cmd_keys & BOT_CMD_KEY_ATTACK2)
- self.BUTTON_ATCK2 = true;
-
- if(self.bot_cmd_keys & BOT_CMD_KEY_USE)
- self.BUTTON_USE = true;
-
- if(self.bot_cmd_keys & BOT_CMD_KEY_HOOK)
- self.BUTTON_HOOK = true;
-
- if(self.bot_cmd_keys & BOT_CMD_KEY_CHAT)
- self.BUTTON_CHAT = true;
-
- return true;
-}
-
-
-float bot_cmd_keypress_handler(string key, float enabled)
-{SELFPARAM();
- switch(key)
- {
- case "all":
- if(enabled)
- self.bot_cmd_keys = power2of(20) - 1; // >:)
- else
- self.bot_cmd_keys = BOT_CMD_KEY_NONE;
- case "forward":
- if(enabled)
- {
- self.bot_cmd_keys |= BOT_CMD_KEY_FORWARD;
- self.bot_cmd_keys &= ~BOT_CMD_KEY_BACKWARD;
- }
- else
- self.bot_cmd_keys &= ~BOT_CMD_KEY_FORWARD;
- break;
- case "backward":
- if(enabled)
- {
- self.bot_cmd_keys |= BOT_CMD_KEY_BACKWARD;
- self.bot_cmd_keys &= ~BOT_CMD_KEY_FORWARD;
- }
- else
- self.bot_cmd_keys &= ~BOT_CMD_KEY_BACKWARD;
- break;
- case "left":
- if(enabled)
- {
- self.bot_cmd_keys |= BOT_CMD_KEY_LEFT;
- self.bot_cmd_keys &= ~BOT_CMD_KEY_RIGHT;
- }
- else
- self.bot_cmd_keys &= ~BOT_CMD_KEY_LEFT;
- break;
- case "right":
- if(enabled)
- {
- self.bot_cmd_keys |= BOT_CMD_KEY_RIGHT;
- self.bot_cmd_keys &= ~BOT_CMD_KEY_LEFT;
- }
- else
- self.bot_cmd_keys &= ~BOT_CMD_KEY_RIGHT;
- break;
- case "jump":
- if(enabled)
- self.bot_cmd_keys |= BOT_CMD_KEY_JUMP;
- else
- self.bot_cmd_keys &= ~BOT_CMD_KEY_JUMP;
- break;
- case "crouch":
- if(enabled)
- self.bot_cmd_keys |= BOT_CMD_KEY_CROUCH;
- else
- self.bot_cmd_keys &= ~BOT_CMD_KEY_CROUCH;
- break;
- case "attack1":
- if(enabled)
- self.bot_cmd_keys |= BOT_CMD_KEY_ATTACK1;
- else
- self.bot_cmd_keys &= ~BOT_CMD_KEY_ATTACK1;
- break;
- case "attack2":
- if(enabled)
- self.bot_cmd_keys |= BOT_CMD_KEY_ATTACK2;
- else
- self.bot_cmd_keys &= ~BOT_CMD_KEY_ATTACK2;
- break;
- case "use":
- if(enabled)
- self.bot_cmd_keys |= BOT_CMD_KEY_USE;
- else
- self.bot_cmd_keys &= ~BOT_CMD_KEY_USE;
- break;
- case "hook":
- if(enabled)
- self.bot_cmd_keys |= BOT_CMD_KEY_HOOK;
- else
- self.bot_cmd_keys &= ~BOT_CMD_KEY_HOOK;
- break;
- case "chat":
- if(enabled)
- self.bot_cmd_keys |= BOT_CMD_KEY_CHAT;
- else
- self.bot_cmd_keys &= ~BOT_CMD_KEY_CHAT;
- break;
- default:
- break;
- }
-
- return CMD_STATUS_FINISHED;
-}
-
-float bot_cmd_presskey()
-{
- string key;
-
- key = bot_cmd.bot_cmd_parm_string;
-
- bot_cmd_keypress_handler(key,true);
-
- return CMD_STATUS_FINISHED;
-}
-
-float bot_cmd_releasekey()
-{
- string key;
-
- key = bot_cmd.bot_cmd_parm_string;
-
- return bot_cmd_keypress_handler(key,false);
-}
-
-float bot_cmd_pause()
-{SELFPARAM();
- self.button1 = 0;
- self.button8 = 0;
- self.BUTTON_USE = 0;
- self.BUTTON_ATCK = 0;
- self.BUTTON_JUMP = 0;
- self.BUTTON_HOOK = 0;
- self.BUTTON_CHAT = 0;
- self.BUTTON_ATCK2 = 0;
- self.BUTTON_CROUCH = 0;
-
- self.movement = '0 0 0';
- self.bot_cmd_keys = BOT_CMD_KEY_NONE;
-
- self.bot_exec_status |= BOT_EXEC_STATUS_PAUSED;
- return CMD_STATUS_FINISHED;
-}
-
-float bot_cmd_moveto()
-{SELFPARAM();
- return self.cmd_moveto(bot_cmd.bot_cmd_parm_vector);
-}
-
-float bot_cmd_movetotarget()
-{SELFPARAM();
- entity e;
- e = bot_getplace(bot_cmd.bot_cmd_parm_string);
- if(!e)
- return CMD_STATUS_ERROR;
- return self.cmd_moveto(e.origin + (e.mins + e.maxs) * 0.5);
-}
-
-float bot_cmd_resetgoal()
-{SELFPARAM();
- return self.cmd_resetgoal();
-}
-
-
-float bot_cmd_sound()
-{SELFPARAM();
- string f;
- f = bot_cmd.bot_cmd_parm_string;
-
- float n = tokenizebyseparator(f, " ");
-
- string sample = f;
- float chan = CH_WEAPON_B;
- float vol = VOL_BASE;
- float atten = ATTEN_MIN;
-
- if(n >= 1)
- sample = argv(n - 1);
- if(n >= 2)
- chan = stof(argv(0));
- if(n >= 3)
- vol = stof(argv(1));
- if(n >= 4)
- atten = stof(argv(2));
-
- precache_sound(f);
- _sound(self, chan, sample, vol, atten);
-
- return CMD_STATUS_FINISHED;
-}
-
-.entity tuba_note;
-float bot_cmd_debug_assert_canfire()
-{SELFPARAM();
- float f;
- f = bot_cmd.bot_cmd_parm_float;
-
- if(self.weaponentity.state != WS_READY)
- {
- if(f)
- {
- self.colormod = '0 8 8';
- LOG_INFO("Bot ", self.netname, " using ", self.weaponname, " wants to fire, inhibited by weaponentity state\n");
- }
- }
- else if(ATTACK_FINISHED(self) > time)
- {
- if(f)
- {
- self.colormod = '8 0 8';
- LOG_INFO("Bot ", self.netname, " using ", self.weaponname, " wants to fire, inhibited by ATTACK_FINISHED (", ftos(ATTACK_FINISHED(self) - time), " seconds left)\n");
- }
- }
- else if(self.tuba_note)
- {
- if(f)
- {
- self.colormod = '8 0 0';
- LOG_INFO("Bot ", self.netname, " using ", self.weaponname, " wants to fire, bot still has an active tuba note\n");
- }
- }
- else
- {
- if(!f)
- {
- self.colormod = '8 8 0';
- LOG_INFO("Bot ", self.netname, " using ", self.weaponname, " thinks it has fired, but apparently did not; ATTACK_FINISHED says ", ftos(ATTACK_FINISHED(self) - time), " seconds left\n");
- }
- }
-
- return CMD_STATUS_FINISHED;
-}
-
-//
-
-void bot_command_executed(float rm)
-{SELFPARAM();
- entity cmd;
-
- cmd = bot_cmd;
-
- if(rm)
- bot_dequeuecommand(self, self.bot_cmd_execution_index);
-
- self.bot_cmd_execution_index++;
-}
-
-void bot_setcurrentcommand()
-{SELFPARAM();
- bot_cmd = world;
-
- if(!self.bot_cmd_current)
- {
- self.bot_cmd_current = spawn();
- self.bot_cmd_current.classname = "bot_cmd";
- self.bot_cmd_current.is_bot_cmd = 1;
- }
-
- bot_cmd = self.bot_cmd_current;
- if(bot_cmd.bot_cmd_index != self.bot_cmd_execution_index || self.bot_cmd_execution_index == 0)
- {
- if(bot_havecommand(self, self.bot_cmd_execution_index))
- {
- string cmdstring;
- cmdstring = bot_readcommand(self, self.bot_cmd_execution_index);
- if(bot_decodecommand(cmdstring))
- {
- bot_cmd.owner = self;
- bot_cmd.bot_cmd_index = self.bot_cmd_execution_index;
- }
- else
- {
- // Invalid command, remove from queue
- bot_cmd = world;
- bot_dequeuecommand(self, self.bot_cmd_execution_index);
- self.bot_cmd_execution_index++;
- }
- }
- else
- bot_cmd = world;
- }
-}
-
-void bot_resetqueues()
-{
- entity cl;
-
- FOR_EACH_CLIENT(cl) if(cl.isbot)
- {
- cl.bot_cmd_execution_index = 0;
- bot_clearqueue(cl);
- // also, cancel all barriers
- cl.bot_barrier = 0;
- for(int i = 0; i < cl.bot_places_count; ++i)
- {
- strunzone(cl.(bot_placenames[i]));
- cl.(bot_placenames[i]) = string_null;
- }
- cl.bot_places_count = 0;
- }
-
- bot_barriertime = time;
-}
-
-// Here we map commands to functions and deal with complex interactions between commands and execution states
-// NOTE: Of course you need to include your commands here too :)
-float bot_execute_commands_once()
-{SELFPARAM();
- float status, ispressingkey;
-
- // Find command
- bot_setcurrentcommand();
-
- // if we have no bot command, better return
- // old logic kept pressing previously pressed keys, but that has problems
- // (namely, it means you cannot make a bot "normal" ever again)
- // to keep a bot walking for a while, use the "wait" bot command
- if(bot_cmd == world)
- return false;
-
- // Ignore all commands except continue when the bot is paused
- if(self.bot_exec_status & BOT_EXEC_STATUS_PAUSED)
- if(bot_cmd.bot_cmd_type!=BOT_CMD_CONTINUE)
- {
- if(bot_cmd.bot_cmd_type!=BOT_CMD_NULL)
- {
- bot_command_executed(true);
- LOG_INFO( "WARNING: Commands are ignored while the bot is paused. Use the command 'continue' instead.\n");
- }
- return 1;
- }
-
- // Keep pressing keys raised by the "presskey" command
- ispressingkey = !!bot_presskeys();
-
- // Handle conditions
- if (!(bot_cmd.bot_cmd_type==BOT_CMD_FI||bot_cmd.bot_cmd_type==BOT_CMD_ELSE))
- if(self.bot_cmd_condition_status & CMD_CONDITION_true && self.bot_cmd_condition_status & CMD_CONDITION_false_BLOCK)
- {
- bot_command_executed(true);
- return -1;
- }
- else if(self.bot_cmd_condition_status & CMD_CONDITION_false && self.bot_cmd_condition_status & CMD_CONDITION_true_BLOCK)
- {
- bot_command_executed(true);
- return -1;
- }
-
- // Map commands to functions
- switch(bot_cmd.bot_cmd_type)
- {
- case BOT_CMD_NULL:
- return ispressingkey;
- //break;
- case BOT_CMD_PAUSE:
- status = bot_cmd_pause();
- break;
- case BOT_CMD_CONTINUE:
- status = bot_cmd_continue();
- break;
- case BOT_CMD_WAIT:
- status = bot_cmd_wait();
- break;
- case BOT_CMD_WAIT_UNTIL:
- status = bot_cmd_wait_until();
- break;
- case BOT_CMD_TURN:
- status = bot_cmd_turn();
- break;
- case BOT_CMD_MOVETO:
- status = bot_cmd_moveto();
- break;
- case BOT_CMD_MOVETOTARGET:
- status = bot_cmd_movetotarget();
- break;
- case BOT_CMD_RESETGOAL:
- status = bot_cmd_resetgoal();
- break;
- case BOT_CMD_CC:
- status = bot_cmd_cc();
- break;
- case BOT_CMD_IF:
- status = bot_cmd_if();
- break;
- case BOT_CMD_ELSE:
- status = bot_cmd_else();
- break;
- case BOT_CMD_FI:
- status = bot_cmd_fi();
- break;
- case BOT_CMD_RESETAIM:
- status = bot_cmd_resetaim();
- break;
- case BOT_CMD_AIM:
- status = bot_cmd_aim();
- break;
- case BOT_CMD_AIMTARGET:
- status = bot_cmd_aimtarget();
- break;
- case BOT_CMD_PRESSKEY:
- status = bot_cmd_presskey();
- break;
- case BOT_CMD_RELEASEKEY:
- status = bot_cmd_releasekey();
- break;
- case BOT_CMD_SELECTWEAPON:
- status = bot_cmd_select_weapon();
- break;
- case BOT_CMD_IMPULSE:
- status = bot_cmd_impulse();
- break;
- case BOT_CMD_BARRIER:
- status = bot_cmd_barrier();
- break;
- case BOT_CMD_CONSOLE:
- localcmd(strcat(bot_cmd.bot_cmd_parm_string, "\n"));
- status = CMD_STATUS_FINISHED;
- break;
- case BOT_CMD_SOUND:
- status = bot_cmd_sound();
- break;
- case BOT_CMD_DEBUG_ASSERT_CANFIRE:
- status = bot_cmd_debug_assert_canfire();
- break;
- default:
- LOG_INFO(strcat("ERROR: Invalid command on queue with id '",ftos(bot_cmd.bot_cmd_type),"'\n"));
- return 0;
- }
-
- if (status==CMD_STATUS_ERROR)
- LOG_INFO(strcat("ERROR: The command '",bot_cmd_string[bot_cmd.bot_cmd_type],"' returned an error status\n"));
-
- // Move execution pointer
- if(status==CMD_STATUS_EXECUTING)
- {
- return 1;
- }
- else
- {
- if(autocvar_g_debug_bot_commands)
- {
- string parms;
-
- switch(bot_cmd_parm_type[bot_cmd.bot_cmd_type])
- {
- case BOT_CMD_PARAMETER_FLOAT:
- parms = ftos(bot_cmd.bot_cmd_parm_float);
- break;
- case BOT_CMD_PARAMETER_STRING:
- parms = bot_cmd.bot_cmd_parm_string;
- break;
- case BOT_CMD_PARAMETER_VECTOR:
- parms = vtos(bot_cmd.bot_cmd_parm_vector);
- break;
- default:
- parms = "";
- break;
- }
- clientcommand(self,strcat("say ^7", bot_cmd_string[bot_cmd.bot_cmd_type]," ",parms,"\n"));
- }
-
- bot_command_executed(true);
- }
-
- if(status == CMD_STATUS_FINISHED)
- return -1;
-
- return CMD_STATUS_ERROR;
-}
-
-// This function should be (the only) called directly from the bot ai loop
-float bot_execute_commands()
-{
- float f;
- do
- {
- f = bot_execute_commands_once();
- }
- while(f < 0);
- return f;
-}
+++ /dev/null
-#ifndef BOT_SCRIPTING_H
-#define BOT_SCRIPTING_H
-
-#define BOT_EXEC_STATUS_IDLE 0
-#define BOT_EXEC_STATUS_PAUSED 1
-#define BOT_EXEC_STATUS_WAITING 2
-
-#define CMD_STATUS_EXECUTING 0
-#define CMD_STATUS_FINISHED 1
-#define CMD_STATUS_ERROR 2
-
-
-// NOTE: New commands should be added here. Do not forget to update BOT_CMD_COUNTER
-const int BOT_CMD_NULL = 0;
-const int BOT_CMD_PAUSE = 1;
-const int BOT_CMD_CONTINUE = 2;
-const int BOT_CMD_WAIT = 3;
-const int BOT_CMD_TURN = 4;
-const int BOT_CMD_MOVETO = 5;
-const int BOT_CMD_RESETGOAL = 6; // Not implemented yet
-const int BOT_CMD_CC = 7;
-const int BOT_CMD_IF = 8;
-const int BOT_CMD_ELSE = 9;
-const int BOT_CMD_FI = 10;
-const int BOT_CMD_RESETAIM = 11;
-const int BOT_CMD_AIM = 12;
-const int BOT_CMD_PRESSKEY = 13;
-const int BOT_CMD_RELEASEKEY = 14;
-const int BOT_CMD_SELECTWEAPON = 15;
-const int BOT_CMD_IMPULSE = 16;
-const int BOT_CMD_WAIT_UNTIL = 17;
-const int BOT_CMD_MOVETOTARGET = 18;
-const int BOT_CMD_AIMTARGET = 19;
-const int BOT_CMD_BARRIER = 20;
-const int BOT_CMD_CONSOLE = 21;
-const int BOT_CMD_SOUND = 22;
-const int BOT_CMD_DEBUG_ASSERT_CANFIRE = 23;
-const int BOT_CMD_WHILE = 24; // TODO: Not implemented yet
-const int BOT_CMD_WEND = 25; // TODO: Not implemented yet
-const int BOT_CMD_CHASE = 26; // TODO: Not implemented yet
-
-const int BOT_CMD_COUNTER = 24; // Update this value if you add/remove a command
-
-// NOTE: Following commands should be implemented on the bot ai
-// If a new command should be handled by the target ai(s) please declare it here
-.float(vector) cmd_moveto;
-.float() cmd_resetgoal;
-
-//
-const int BOT_CMD_PARAMETER_NONE = 0;
-const int BOT_CMD_PARAMETER_FLOAT = 1;
-const int BOT_CMD_PARAMETER_STRING = 2;
-const int BOT_CMD_PARAMETER_VECTOR = 3;
-
-float bot_cmds_initialized;
-int bot_cmd_parm_type[BOT_CMD_COUNTER];
-string bot_cmd_string[BOT_CMD_COUNTER];
-
-// Bots command queue
-entity bot_cmd; // global current command
-.entity bot_cmd_current; // current command of this bot
-
-.float is_bot_cmd; // Tells if the entity is a bot command
-.float bot_cmd_index; // Position of the command in the queue
-.int bot_cmd_type; // If of command (see the BOT_CMD_* defines)
-.float bot_cmd_parm_float; // Field to store a float parameter
-.string bot_cmd_parm_string; // Field to store a string parameter
-.vector bot_cmd_parm_vector; // Field to store a vector parameter
-
-float bot_barriertime;
-.float bot_barrier;
-
-.float bot_cmd_execution_index; // Position in the queue of the command to be executed
-
-
-void bot_resetqueues();
-void bot_queuecommand(entity bot, string cmdstring);
-void bot_cmdhelp(string scmd);
-void bot_list_commands();
-float bot_execute_commands();
-entity find_bot_by_name(string name);
-entity find_bot_by_number(float number);
-#endif
+++ /dev/null
-#include "waypoints.qh"
-#include "../_all.qh"
-
-#include "bot.qh"
-#include "navigation.qh"
-
-#include "../antilag.qh"
-
-#include "../../common/constants.qh"
-
-#include "../../warpzonelib/util_server.qh"
-
-// create a new spawnfunc_waypoint and automatically link it to other waypoints, and link
-// them back to it as well
-// (suitable for spawnfunc_waypoint editor)
-entity waypoint_spawn(vector m1, vector m2, float f)
-{
- entity w;
- w = find(world, classname, "waypoint");
-
- if (!(f & WAYPOINTFLAG_PERSONAL))
- while (w)
- {
- // if a matching spawnfunc_waypoint already exists, don't add a duplicate
- if (boxesoverlap(m1, m2, w.absmin, w.absmax))
- return w;
- w = find(w, classname, "waypoint");
- }
-
- w = spawn();
- w.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP;
- w.classname = "waypoint";
- w.wpflags = f;
- setorigin(w, (m1 + m2) * 0.5);
- setsize(w, m1 - w.origin, m2 - w.origin);
- if (vlen(w.size) > 0)
- w.wpisbox = true;
-
- if(!w.wpisbox)
- {
- setsize(w, PL_MIN - '1 1 0', PL_MAX + '1 1 0');
- if(!move_out_of_solid(w))
- {
- if(!(f & WAYPOINTFLAG_GENERATED))
- {
- LOG_TRACE("Killed a waypoint that was stuck in solid at ", vtos(w.origin), "\n");
- remove(w);
- return world;
- }
- else
- {
- if(autocvar_developer)
- {
- LOG_INFO("A generated waypoint is stuck in solid at ", vtos(w.origin), "\n");
- backtrace("Waypoint stuck");
- }
- }
- }
- setsize(w, '0 0 0', '0 0 0');
- }
-
- waypoint_clearlinks(w);
- //waypoint_schedulerelink(w);
-
- if (autocvar_g_waypointeditor)
- {
- m1 = w.mins;
- m2 = w.maxs;
- setmodel(w, MDL_WAYPOINT); w.effects = EF_LOWPRECISION;
- setsize(w, m1, m2);
- if (w.wpflags & WAYPOINTFLAG_ITEM)
- w.colormod = '1 0 0';
- else if (w.wpflags & WAYPOINTFLAG_GENERATED)
- w.colormod = '1 1 0';
- else
- w.colormod = '1 1 1';
- }
- else
- w.model = "";
-
- return w;
-}
-
-// add a new link to the spawnfunc_waypoint, replacing the furthest link it already has
-void waypoint_addlink(entity from, entity to)
-{
- float c;
-
- if (from == to)
- return;
- if (from.wpflags & WAYPOINTFLAG_NORELINK)
- return;
-
- if (from.wp00 == to) return;if (from.wp01 == to) return;if (from.wp02 == to) return;if (from.wp03 == to) return;
- if (from.wp04 == to) return;if (from.wp05 == to) return;if (from.wp06 == to) return;if (from.wp07 == to) return;
- if (from.wp08 == to) return;if (from.wp09 == to) return;if (from.wp10 == to) return;if (from.wp11 == to) return;
- if (from.wp12 == to) return;if (from.wp13 == to) return;if (from.wp14 == to) return;if (from.wp15 == to) return;
- if (from.wp16 == to) return;if (from.wp17 == to) return;if (from.wp18 == to) return;if (from.wp19 == to) return;
- if (from.wp20 == to) return;if (from.wp21 == to) return;if (from.wp22 == to) return;if (from.wp23 == to) return;
- if (from.wp24 == to) return;if (from.wp25 == to) return;if (from.wp26 == to) return;if (from.wp27 == to) return;
- if (from.wp28 == to) return;if (from.wp29 == to) return;if (from.wp30 == to) return;if (from.wp31 == to) return;
-
- if (to.wpisbox || from.wpisbox)
- {
- // if either is a box we have to find the nearest points on them to
- // calculate the distance properly
- vector v1, v2, m1, m2;
- v1 = from.origin;
- m1 = to.absmin;
- m2 = to.absmax;
- v1_x = bound(m1_x, v1_x, m2_x);
- v1_y = bound(m1_y, v1_y, m2_y);
- v1_z = bound(m1_z, v1_z, m2_z);
- v2 = to.origin;
- m1 = from.absmin;
- m2 = from.absmax;
- v2_x = bound(m1_x, v2_x, m2_x);
- v2_y = bound(m1_y, v2_y, m2_y);
- v2_z = bound(m1_z, v2_z, m2_z);
- v2 = to.origin;
- c = vlen(v2 - v1);
- }
- else
- c = vlen(to.origin - from.origin);
-
- if (from.wp31mincost < c) return;
- if (from.wp30mincost < c) {from.wp31 = to;from.wp31mincost = c;return;} from.wp31 = from.wp30;from.wp31mincost = from.wp30mincost;
- if (from.wp29mincost < c) {from.wp30 = to;from.wp30mincost = c;return;} from.wp30 = from.wp29;from.wp30mincost = from.wp29mincost;
- if (from.wp28mincost < c) {from.wp29 = to;from.wp29mincost = c;return;} from.wp29 = from.wp28;from.wp29mincost = from.wp28mincost;
- if (from.wp27mincost < c) {from.wp28 = to;from.wp28mincost = c;return;} from.wp28 = from.wp27;from.wp28mincost = from.wp27mincost;
- if (from.wp26mincost < c) {from.wp27 = to;from.wp27mincost = c;return;} from.wp27 = from.wp26;from.wp27mincost = from.wp26mincost;
- if (from.wp25mincost < c) {from.wp26 = to;from.wp26mincost = c;return;} from.wp26 = from.wp25;from.wp26mincost = from.wp25mincost;
- if (from.wp24mincost < c) {from.wp25 = to;from.wp25mincost = c;return;} from.wp25 = from.wp24;from.wp25mincost = from.wp24mincost;
- if (from.wp23mincost < c) {from.wp24 = to;from.wp24mincost = c;return;} from.wp24 = from.wp23;from.wp24mincost = from.wp23mincost;
- if (from.wp22mincost < c) {from.wp23 = to;from.wp23mincost = c;return;} from.wp23 = from.wp22;from.wp23mincost = from.wp22mincost;
- if (from.wp21mincost < c) {from.wp22 = to;from.wp22mincost = c;return;} from.wp22 = from.wp21;from.wp22mincost = from.wp21mincost;
- if (from.wp20mincost < c) {from.wp21 = to;from.wp21mincost = c;return;} from.wp21 = from.wp20;from.wp21mincost = from.wp20mincost;
- if (from.wp19mincost < c) {from.wp20 = to;from.wp20mincost = c;return;} from.wp20 = from.wp19;from.wp20mincost = from.wp19mincost;
- if (from.wp18mincost < c) {from.wp19 = to;from.wp19mincost = c;return;} from.wp19 = from.wp18;from.wp19mincost = from.wp18mincost;
- if (from.wp17mincost < c) {from.wp18 = to;from.wp18mincost = c;return;} from.wp18 = from.wp17;from.wp18mincost = from.wp17mincost;
- if (from.wp16mincost < c) {from.wp17 = to;from.wp17mincost = c;return;} from.wp17 = from.wp16;from.wp17mincost = from.wp16mincost;
- if (from.wp15mincost < c) {from.wp16 = to;from.wp16mincost = c;return;} from.wp16 = from.wp15;from.wp16mincost = from.wp15mincost;
- if (from.wp14mincost < c) {from.wp15 = to;from.wp15mincost = c;return;} from.wp15 = from.wp14;from.wp15mincost = from.wp14mincost;
- if (from.wp13mincost < c) {from.wp14 = to;from.wp14mincost = c;return;} from.wp14 = from.wp13;from.wp14mincost = from.wp13mincost;
- if (from.wp12mincost < c) {from.wp13 = to;from.wp13mincost = c;return;} from.wp13 = from.wp12;from.wp13mincost = from.wp12mincost;
- if (from.wp11mincost < c) {from.wp12 = to;from.wp12mincost = c;return;} from.wp12 = from.wp11;from.wp12mincost = from.wp11mincost;
- if (from.wp10mincost < c) {from.wp11 = to;from.wp11mincost = c;return;} from.wp11 = from.wp10;from.wp11mincost = from.wp10mincost;
- if (from.wp09mincost < c) {from.wp10 = to;from.wp10mincost = c;return;} from.wp10 = from.wp09;from.wp10mincost = from.wp09mincost;
- if (from.wp08mincost < c) {from.wp09 = to;from.wp09mincost = c;return;} from.wp09 = from.wp08;from.wp09mincost = from.wp08mincost;
- if (from.wp07mincost < c) {from.wp08 = to;from.wp08mincost = c;return;} from.wp08 = from.wp07;from.wp08mincost = from.wp07mincost;
- if (from.wp06mincost < c) {from.wp07 = to;from.wp07mincost = c;return;} from.wp07 = from.wp06;from.wp07mincost = from.wp06mincost;
- if (from.wp05mincost < c) {from.wp06 = to;from.wp06mincost = c;return;} from.wp06 = from.wp05;from.wp06mincost = from.wp05mincost;
- if (from.wp04mincost < c) {from.wp05 = to;from.wp05mincost = c;return;} from.wp05 = from.wp04;from.wp05mincost = from.wp04mincost;
- if (from.wp03mincost < c) {from.wp04 = to;from.wp04mincost = c;return;} from.wp04 = from.wp03;from.wp04mincost = from.wp03mincost;
- if (from.wp02mincost < c) {from.wp03 = to;from.wp03mincost = c;return;} from.wp03 = from.wp02;from.wp03mincost = from.wp02mincost;
- if (from.wp01mincost < c) {from.wp02 = to;from.wp02mincost = c;return;} from.wp02 = from.wp01;from.wp02mincost = from.wp01mincost;
- if (from.wp00mincost < c) {from.wp01 = to;from.wp01mincost = c;return;} from.wp01 = from.wp00;from.wp01mincost = from.wp00mincost;
- from.wp00 = to;from.wp00mincost = c;return;
-}
-
-// relink this spawnfunc_waypoint
-// (precompile a list of all reachable waypoints from this spawnfunc_waypoint)
-// (SLOW!)
-void waypoint_think()
-{SELFPARAM();
- entity e;
- vector sv, sm1, sm2, ev, em1, em2, dv;
-
- bot_calculate_stepheightvec();
-
- bot_navigation_movemode = ((autocvar_bot_navigation_ignoreplayers) ? MOVE_NOMONSTERS : MOVE_NORMAL);
-
- //dprint("waypoint_think wpisbox = ", ftos(self.wpisbox), "\n");
- sm1 = self.origin + self.mins;
- sm2 = self.origin + self.maxs;
- for(e = world; (e = find(e, classname, "waypoint")); )
- {
- if (boxesoverlap(self.absmin, self.absmax, e.absmin, e.absmax))
- {
- waypoint_addlink(self, e);
- waypoint_addlink(e, self);
- }
- else
- {
- ++relink_total;
- if(!checkpvs(self.origin, e))
- {
- ++relink_pvsculled;
- continue;
- }
- sv = e.origin;
- sv.x = bound(sm1_x, sv.x, sm2_x);
- sv.y = bound(sm1_y, sv.y, sm2_y);
- sv.z = bound(sm1_z, sv.z, sm2_z);
- ev = self.origin;
- em1 = e.origin + e.mins;
- em2 = e.origin + e.maxs;
- ev.x = bound(em1_x, ev.x, em2_x);
- ev.y = bound(em1_y, ev.y, em2_y);
- ev.z = bound(em1_z, ev.z, em2_z);
- dv = ev - sv;
- dv.z = 0;
- if (vlen(dv) >= 1050) // max search distance in XY
- {
- ++relink_lengthculled;
- continue;
- }
- navigation_testtracewalk = 0;
- if (!self.wpisbox)
- {
- tracebox(sv - PL_MIN.z * '0 0 1', PL_MIN, PL_MAX, sv, false, self);
- if (!trace_startsolid)
- {
- //dprint("sv deviation", vtos(trace_endpos - sv), "\n");
- sv = trace_endpos + '0 0 1';
- }
- }
- if (!e.wpisbox)
- {
- tracebox(ev - PL_MIN.z * '0 0 1', PL_MIN, PL_MAX, ev, false, e);
- if (!trace_startsolid)
- {
- //dprint("ev deviation", vtos(trace_endpos - ev), "\n");
- ev = trace_endpos + '0 0 1';
- }
- }
- //traceline(self.origin, e.origin, false, world);
- //if (trace_fraction == 1)
- if (!self.wpisbox && tracewalk(self, sv, PL_MIN, PL_MAX, ev, MOVE_NOMONSTERS))
- waypoint_addlink(self, e);
- else
- relink_walkculled += 0.5;
- if (!e.wpisbox && tracewalk(e, ev, PL_MIN, PL_MAX, sv, MOVE_NOMONSTERS))
- waypoint_addlink(e, self);
- else
- relink_walkculled += 0.5;
- }
- }
- navigation_testtracewalk = 0;
- self.wplinked = true;
-}
-
-void waypoint_clearlinks(entity wp)
-{
- // clear links to other waypoints
- float f;
- f = 10000000;
- wp.wp00 = wp.wp01 = wp.wp02 = wp.wp03 = wp.wp04 = wp.wp05 = wp.wp06 = wp.wp07 = world;
- wp.wp08 = wp.wp09 = wp.wp10 = wp.wp11 = wp.wp12 = wp.wp13 = wp.wp14 = wp.wp15 = world;
- wp.wp16 = wp.wp17 = wp.wp18 = wp.wp19 = wp.wp20 = wp.wp21 = wp.wp22 = wp.wp23 = world;
- wp.wp24 = wp.wp25 = wp.wp26 = wp.wp27 = wp.wp28 = wp.wp29 = wp.wp30 = wp.wp31 = world;
-
- wp.wp00mincost = wp.wp01mincost = wp.wp02mincost = wp.wp03mincost = wp.wp04mincost = wp.wp05mincost = wp.wp06mincost = wp.wp07mincost = f;
- wp.wp08mincost = wp.wp09mincost = wp.wp10mincost = wp.wp11mincost = wp.wp12mincost = wp.wp13mincost = wp.wp14mincost = wp.wp15mincost = f;
- wp.wp16mincost = wp.wp17mincost = wp.wp18mincost = wp.wp19mincost = wp.wp20mincost = wp.wp21mincost = wp.wp22mincost = wp.wp23mincost = f;
- wp.wp24mincost = wp.wp25mincost = wp.wp26mincost = wp.wp27mincost = wp.wp28mincost = wp.wp29mincost = wp.wp30mincost = wp.wp31mincost = f;
-
- wp.wplinked = false;
-}
-
-// tell a spawnfunc_waypoint to relink
-void waypoint_schedulerelink(entity wp)
-{
- if (wp == world)
- return;
- // TODO: add some sort of visible box in edit mode for box waypoints
- if (autocvar_g_waypointeditor)
- {
- vector m1, m2;
- m1 = wp.mins;
- m2 = wp.maxs;
- setmodel(wp, MDL_WAYPOINT); wp.effects = EF_LOWPRECISION;
- setsize(wp, m1, m2);
- if (wp.wpflags & WAYPOINTFLAG_ITEM)
- wp.colormod = '1 0 0';
- else if (wp.wpflags & WAYPOINTFLAG_GENERATED)
- wp.colormod = '1 1 0';
- else
- wp.colormod = '1 1 1';
- }
- else
- wp.model = "";
- wp.wpisbox = vlen(wp.size) > 0;
- wp.enemy = world;
- if (!(wp.wpflags & WAYPOINTFLAG_PERSONAL))
- wp.owner = world;
- if (!(wp.wpflags & WAYPOINTFLAG_NORELINK))
- waypoint_clearlinks(wp);
- // schedule an actual relink on next frame
- wp.think = waypoint_think;
- wp.nextthink = time;
- wp.effects = EF_LOWPRECISION;
-}
-
-// spawnfunc_waypoint map entity
-spawnfunc(waypoint)
-{
- setorigin(self, self.origin);
- // schedule a relink after other waypoints have had a chance to spawn
- waypoint_clearlinks(self);
- //waypoint_schedulerelink(self);
-}
-
-// remove a spawnfunc_waypoint, and schedule all neighbors to relink
-void waypoint_remove(entity e)
-{
- // tell all linked waypoints that they need to relink
- waypoint_schedulerelink(e.wp00);
- waypoint_schedulerelink(e.wp01);
- waypoint_schedulerelink(e.wp02);
- waypoint_schedulerelink(e.wp03);
- waypoint_schedulerelink(e.wp04);
- waypoint_schedulerelink(e.wp05);
- waypoint_schedulerelink(e.wp06);
- waypoint_schedulerelink(e.wp07);
- waypoint_schedulerelink(e.wp08);
- waypoint_schedulerelink(e.wp09);
- waypoint_schedulerelink(e.wp10);
- waypoint_schedulerelink(e.wp11);
- waypoint_schedulerelink(e.wp12);
- waypoint_schedulerelink(e.wp13);
- waypoint_schedulerelink(e.wp14);
- waypoint_schedulerelink(e.wp15);
- waypoint_schedulerelink(e.wp16);
- waypoint_schedulerelink(e.wp17);
- waypoint_schedulerelink(e.wp18);
- waypoint_schedulerelink(e.wp19);
- waypoint_schedulerelink(e.wp20);
- waypoint_schedulerelink(e.wp21);
- waypoint_schedulerelink(e.wp22);
- waypoint_schedulerelink(e.wp23);
- waypoint_schedulerelink(e.wp24);
- waypoint_schedulerelink(e.wp25);
- waypoint_schedulerelink(e.wp26);
- waypoint_schedulerelink(e.wp27);
- waypoint_schedulerelink(e.wp28);
- waypoint_schedulerelink(e.wp29);
- waypoint_schedulerelink(e.wp30);
- waypoint_schedulerelink(e.wp31);
- // and now remove the spawnfunc_waypoint
- remove(e);
-}
-
-// empties the map of waypoints
-void waypoint_removeall()
-{
- entity head, next;
- head = findchain(classname, "waypoint");
- while (head)
- {
- next = head.chain;
- remove(head);
- head = next;
- }
-}
-
-// tell all waypoints to relink
-// (is this useful at all?)
-void waypoint_schedulerelinkall()
-{
- entity head;
- relink_total = relink_walkculled = relink_pvsculled = relink_lengthculled = 0;
- head = findchain(classname, "waypoint");
- while (head)
- {
- waypoint_schedulerelink(head);
- head = head.chain;
- }
-}
-
-// Load waypoint links from file
-float waypoint_load_links()
-{
- string filename, s;
- float file, tokens, c = 0, found;
- entity wp_from = world, wp_to;
- vector wp_to_pos, wp_from_pos;
- filename = strcat("maps/", mapname);
- filename = strcat(filename, ".waypoints.cache");
- file = fopen(filename, FILE_READ);
-
- if (file < 0)
- {
- LOG_TRACE("waypoint links load from ");
- LOG_TRACE(filename);
- LOG_TRACE(" failed\n");
- return false;
- }
-
- while ((s = fgets(file)))
- {
- tokens = tokenizebyseparator(s, "*");
-
- if (tokens!=2)
- {
- // bad file format
- fclose(file);
- return false;
- }
-
- wp_from_pos = stov(argv(0));
- wp_to_pos = stov(argv(1));
-
- // Search "from" waypoint
- if(!wp_from || wp_from.origin!=wp_from_pos)
- {
- wp_from = findradius(wp_from_pos, 1);
- found = false;
- while(wp_from)
- {
- if(vlen(wp_from.origin-wp_from_pos)<1)
- if(wp_from.classname == "waypoint")
- {
- found = true;
- break;
- }
- wp_from = wp_from.chain;
- }
-
- if(!found)
- {
- LOG_TRACE("waypoint_load_links: couldn't find 'from' waypoint at ", vtos(wp_from.origin),"\n");
- continue;
- }
-
- }
-
- // Search "to" waypoint
- wp_to = findradius(wp_to_pos, 1);
- found = false;
- while(wp_to)
- {
- if(vlen(wp_to.origin-wp_to_pos)<1)
- if(wp_to.classname == "waypoint")
- {
- found = true;
- break;
- }
- wp_to = wp_to.chain;
- }
-
- if(!found)
- {
- LOG_TRACE("waypoint_load_links: couldn't find 'to' waypoint at ", vtos(wp_to.origin),"\n");
- continue;
- }
-
- ++c;
- waypoint_addlink(wp_from, wp_to);
- }
-
- fclose(file);
-
- LOG_TRACE("loaded ");
- LOG_TRACE(ftos(c));
- LOG_TRACE(" waypoint links from maps/");
- LOG_TRACE(mapname);
- LOG_TRACE(".waypoints.cache\n");
-
- botframe_cachedwaypointlinks = true;
- return true;
-}
-
-void waypoint_load_links_hardwired()
-{
- string filename, s;
- float file, tokens, c = 0, found;
- entity wp_from = world, wp_to;
- vector wp_to_pos, wp_from_pos;
- filename = strcat("maps/", mapname);
- filename = strcat(filename, ".waypoints.hardwired");
- file = fopen(filename, FILE_READ);
-
- botframe_loadedforcedlinks = true;
-
- if (file < 0)
- {
- LOG_TRACE("waypoint links load from ");
- LOG_TRACE(filename);
- LOG_TRACE(" failed\n");
- return;
- }
-
- while ((s = fgets(file)))
- {
- if(substring(s, 0, 2)=="//")
- continue;
-
- if(substring(s, 0, 1)=="#")
- continue;
-
- tokens = tokenizebyseparator(s, "*");
-
- if (tokens!=2)
- continue;
-
- wp_from_pos = stov(argv(0));
- wp_to_pos = stov(argv(1));
-
- // Search "from" waypoint
- if(!wp_from || wp_from.origin!=wp_from_pos)
- {
- wp_from = findradius(wp_from_pos, 5);
- found = false;
- while(wp_from)
- {
- if(vlen(wp_from.origin-wp_from_pos)<5)
- if(wp_from.classname == "waypoint")
- {
- found = true;
- break;
- }
- wp_from = wp_from.chain;
- }
-
- if(!found)
- {
- LOG_INFO(strcat("NOTICE: Can not find waypoint at ", vtos(wp_from_pos), ". Path skipped\n"));
- continue;
- }
- }
-
- // Search "to" waypoint
- wp_to = findradius(wp_to_pos, 5);
- found = false;
- while(wp_to)
- {
- if(vlen(wp_to.origin-wp_to_pos)<5)
- if(wp_to.classname == "waypoint")
- {
- found = true;
- break;
- }
- wp_to = wp_to.chain;
- }
-
- if(!found)
- {
- LOG_INFO(strcat("NOTICE: Can not find waypoint at ", vtos(wp_to_pos), ". Path skipped\n"));
- continue;
- }
-
- ++c;
- waypoint_addlink(wp_from, wp_to);
- wp_from.wphardwired = true;
- wp_to.wphardwired = true;
- }
-
- fclose(file);
-
- LOG_TRACE("loaded ");
- LOG_TRACE(ftos(c));
- LOG_TRACE(" waypoint links from maps/");
- LOG_TRACE(mapname);
- LOG_TRACE(".waypoints.hardwired\n");
-}
-
-entity waypoint_get_link(entity w, float i)
-{
- switch(i)
- {
- case 0:return w.wp00;
- case 1:return w.wp01;
- case 2:return w.wp02;
- case 3:return w.wp03;
- case 4:return w.wp04;
- case 5:return w.wp05;
- case 6:return w.wp06;
- case 7:return w.wp07;
- case 8:return w.wp08;
- case 9:return w.wp09;
- case 10:return w.wp10;
- case 11:return w.wp11;
- case 12:return w.wp12;
- case 13:return w.wp13;
- case 14:return w.wp14;
- case 15:return w.wp15;
- case 16:return w.wp16;
- case 17:return w.wp17;
- case 18:return w.wp18;
- case 19:return w.wp19;
- case 20:return w.wp20;
- case 21:return w.wp21;
- case 22:return w.wp22;
- case 23:return w.wp23;
- case 24:return w.wp24;
- case 25:return w.wp25;
- case 26:return w.wp26;
- case 27:return w.wp27;
- case 28:return w.wp28;
- case 29:return w.wp29;
- case 30:return w.wp30;
- case 31:return w.wp31;
- default:return world;
- }
-}
-
-// Save all waypoint links to a file
-void waypoint_save_links()
-{
- string filename, s;
- float file, c, i;
- entity w, link;
- filename = strcat("maps/", mapname);
- filename = strcat(filename, ".waypoints.cache");
- file = fopen(filename, FILE_WRITE);
- if (file < 0)
- {
- LOG_INFO("waypoint links save to ");
- LOG_INFO(filename);
- LOG_INFO(" failed\n");
- }
- c = 0;
- w = findchain(classname, "waypoint");
- while (w)
- {
- for(i=0;i<32;++i)
- {
- // :S
- link = waypoint_get_link(w, i);
- if(link==world)
- continue;
-
- s = strcat(vtos(w.origin), "*", vtos(link.origin), "\n");
- fputs(file, s);
- ++c;
- }
- w = w.chain;
- }
- fclose(file);
- botframe_cachedwaypointlinks = true;
-
- LOG_INFO("saved ");
- LOG_INFO(ftos(c));
- LOG_INFO(" waypoints links to maps/");
- LOG_INFO(mapname);
- LOG_INFO(".waypoints.cache\n");
-}
-
-// save waypoints to gamedir/data/maps/mapname.waypoints
-void waypoint_saveall()
-{
- string filename, s;
- float file, c;
- entity w;
- filename = strcat("maps/", mapname);
- filename = strcat(filename, ".waypoints");
- file = fopen(filename, FILE_WRITE);
- if (file >= 0)
- {
- c = 0;
- w = findchain(classname, "waypoint");
- while (w)
- {
- if (!(w.wpflags & WAYPOINTFLAG_GENERATED))
- {
- s = strcat(vtos(w.origin + w.mins), "\n");
- s = strcat(s, vtos(w.origin + w.maxs));
- s = strcat(s, "\n");
- s = strcat(s, ftos(w.wpflags));
- s = strcat(s, "\n");
- fputs(file, s);
- c = c + 1;
- }
- w = w.chain;
- }
- fclose(file);
- bprint("saved ");
- bprint(ftos(c));
- bprint(" waypoints to maps/");
- bprint(mapname);
- bprint(".waypoints\n");
- }
- else
- {
- bprint("waypoint save to ");
- bprint(filename);
- bprint(" failed\n");
- }
- waypoint_save_links();
- botframe_loadedforcedlinks = false;
-}
-
-// load waypoints from file
-float waypoint_loadall()
-{
- string filename, s;
- float file, cwp, cwb, fl;
- vector m1, m2;
- cwp = 0;
- cwb = 0;
- filename = strcat("maps/", mapname);
- filename = strcat(filename, ".waypoints");
- file = fopen(filename, FILE_READ);
- if (file >= 0)
- {
- while ((s = fgets(file)))
- {
- m1 = stov(s);
- s = fgets(file);
- if (!s)
- break;
- m2 = stov(s);
- s = fgets(file);
- if (!s)
- break;
- fl = stof(s);
- waypoint_spawn(m1, m2, fl);
- if (m1 == m2)
- cwp = cwp + 1;
- else
- cwb = cwb + 1;
- }
- fclose(file);
- LOG_TRACE("loaded ");
- LOG_TRACE(ftos(cwp));
- LOG_TRACE(" waypoints and ");
- LOG_TRACE(ftos(cwb));
- LOG_TRACE(" wayboxes from maps/");
- LOG_TRACE(mapname);
- LOG_TRACE(".waypoints\n");
- }
- else
- {
- LOG_TRACE("waypoint load from ");
- LOG_TRACE(filename);
- LOG_TRACE(" failed\n");
- }
- return cwp + cwb;
-}
-
-vector waypoint_fixorigin(vector position)
-{
- tracebox(position + '0 0 1' * (1 - PL_MIN.z), PL_MIN, PL_MAX, position + '0 0 -512', MOVE_NOMONSTERS, world);
- if(trace_fraction < 1)
- position = trace_endpos;
- //traceline(position, position + '0 0 -512', MOVE_NOMONSTERS, world);
- //print("position is ", ftos(trace_endpos_z - position_z), " above solid\n");
- return position;
-}
-
-void waypoint_spawnforitem_force(entity e, vector org)
-{
- entity w;
-
- // Fix the waypoint altitude if necessary
- org = waypoint_fixorigin(org);
-
- // don't spawn an item spawnfunc_waypoint if it already exists
- w = findchain(classname, "waypoint");
- while (w)
- {
- if (w.wpisbox)
- {
- if (boxesoverlap(org, org, w.absmin, w.absmax))
- {
- e.nearestwaypoint = w;
- return;
- }
- }
- else
- {
- if (vlen(w.origin - org) < 16)
- {
- e.nearestwaypoint = w;
- return;
- }
- }
- w = w.chain;
- }
- e.nearestwaypoint = waypoint_spawn(org, org, WAYPOINTFLAG_GENERATED | WAYPOINTFLAG_ITEM);
-}
-
-void waypoint_spawnforitem(entity e)
-{
- if(!bot_waypoints_for_items)
- return;
-
- waypoint_spawnforitem_force(e, e.origin);
-}
-
-void waypoint_spawnforteleporter_boxes(entity e, vector org1, vector org2, vector destination1, vector destination2, float timetaken)
-{
- entity w;
- entity dw;
- w = waypoint_spawn(org1, org2, WAYPOINTFLAG_GENERATED | WAYPOINTFLAG_TELEPORT | WAYPOINTFLAG_NORELINK);
- dw = waypoint_spawn(destination1, destination2, WAYPOINTFLAG_GENERATED);
- // one way link to the destination
- w.wp00 = dw;
- w.wp00mincost = timetaken; // this is just for jump pads
- // the teleporter's nearest spawnfunc_waypoint is this one
- // (teleporters are not goals, so this is probably useless)
- e.nearestwaypoint = w;
- e.nearestwaypointtimeout = time + 1000000000;
-}
-
-void waypoint_spawnforteleporter_v(entity e, vector org, vector destination, float timetaken)
-{
- org = waypoint_fixorigin(org);
- destination = waypoint_fixorigin(destination);
- waypoint_spawnforteleporter_boxes(e, org, org, destination, destination, timetaken);
-}
-
-void waypoint_spawnforteleporter(entity e, vector destination, float timetaken)
-{
- destination = waypoint_fixorigin(destination);
- waypoint_spawnforteleporter_boxes(e, e.absmin, e.absmax, destination, destination, timetaken);
-}
-
-entity waypoint_spawnpersonal(vector position)
-{SELFPARAM();
- entity w;
-
- // drop the waypoint to a proper location:
- // first move it up by a player height
- // then move it down to hit the floor with player bbox size
- position = waypoint_fixorigin(position);
-
- w = waypoint_spawn(position, position, WAYPOINTFLAG_GENERATED | WAYPOINTFLAG_PERSONAL);
- w.nearestwaypoint = world;
- w.nearestwaypointtimeout = 0;
- w.owner = self;
-
- waypoint_schedulerelink(w);
-
- return w;
-}
-
-void botframe_showwaypointlinks()
-{
- entity player, head, w;
- if (time < botframe_waypointeditorlightningtime)
- return;
- botframe_waypointeditorlightningtime = time + 0.5;
- player = find(world, classname, "player");
- while (player)
- {
- if (!player.isbot)
- if (player.flags & FL_ONGROUND || player.waterlevel > WATERLEVEL_NONE)
- {
- //navigation_testtracewalk = true;
- head = navigation_findnearestwaypoint(player, false);
- // print("currently selected WP is ", etos(head), "\n");
- //navigation_testtracewalk = false;
- if (head)
- {
- w = head ;if (w) te_lightning2(world, w.origin, player.origin);
- w = head.wp00;if (w) te_lightning2(world, w.origin, head.origin);
- w = head.wp01;if (w) te_lightning2(world, w.origin, head.origin);
- w = head.wp02;if (w) te_lightning2(world, w.origin, head.origin);
- w = head.wp03;if (w) te_lightning2(world, w.origin, head.origin);
- w = head.wp04;if (w) te_lightning2(world, w.origin, head.origin);
- w = head.wp05;if (w) te_lightning2(world, w.origin, head.origin);
- w = head.wp06;if (w) te_lightning2(world, w.origin, head.origin);
- w = head.wp07;if (w) te_lightning2(world, w.origin, head.origin);
- w = head.wp08;if (w) te_lightning2(world, w.origin, head.origin);
- w = head.wp09;if (w) te_lightning2(world, w.origin, head.origin);
- w = head.wp10;if (w) te_lightning2(world, w.origin, head.origin);
- w = head.wp11;if (w) te_lightning2(world, w.origin, head.origin);
- w = head.wp12;if (w) te_lightning2(world, w.origin, head.origin);
- w = head.wp13;if (w) te_lightning2(world, w.origin, head.origin);
- w = head.wp14;if (w) te_lightning2(world, w.origin, head.origin);
- w = head.wp15;if (w) te_lightning2(world, w.origin, head.origin);
- w = head.wp16;if (w) te_lightning2(world, w.origin, head.origin);
- w = head.wp17;if (w) te_lightning2(world, w.origin, head.origin);
- w = head.wp18;if (w) te_lightning2(world, w.origin, head.origin);
- w = head.wp19;if (w) te_lightning2(world, w.origin, head.origin);
- w = head.wp20;if (w) te_lightning2(world, w.origin, head.origin);
- w = head.wp21;if (w) te_lightning2(world, w.origin, head.origin);
- w = head.wp22;if (w) te_lightning2(world, w.origin, head.origin);
- w = head.wp23;if (w) te_lightning2(world, w.origin, head.origin);
- w = head.wp24;if (w) te_lightning2(world, w.origin, head.origin);
- w = head.wp25;if (w) te_lightning2(world, w.origin, head.origin);
- w = head.wp26;if (w) te_lightning2(world, w.origin, head.origin);
- w = head.wp27;if (w) te_lightning2(world, w.origin, head.origin);
- w = head.wp28;if (w) te_lightning2(world, w.origin, head.origin);
- w = head.wp29;if (w) te_lightning2(world, w.origin, head.origin);
- w = head.wp30;if (w) te_lightning2(world, w.origin, head.origin);
- w = head.wp31;if (w) te_lightning2(world, w.origin, head.origin);
- }
- }
- player = find(player, classname, "player");
- }
-}
-
-float botframe_autowaypoints_fixdown(vector v)
-{
- tracebox(v, PL_MIN, PL_MAX, v + '0 0 -64', MOVE_NOMONSTERS, world);
- if(trace_fraction >= 1)
- return 0;
- return 1;
-}
-
-float botframe_autowaypoints_createwp(vector v, entity p, .entity fld, float f)
-{
- entity w;
-
- w = find(world, classname, "waypoint");
- while (w)
- {
- // if a matching spawnfunc_waypoint already exists, don't add a duplicate
- if (boxesoverlap(v - '32 32 32', v + '32 32 32', w.absmin, w.absmax))
- //if (boxesoverlap(v - '4 4 4', v + '4 4 4', w.absmin, w.absmax))
- return 0;
- w = find(w, classname, "waypoint");
- }
-
- waypoint_schedulerelink(p.(fld) = waypoint_spawn(v, v, f));
- return 1;
-}
-
-// return value:
-// 1 = WP created
-// 0 = no action needed
-// -1 = temp fail, try from world too
-// -2 = permanent fail, do not retry
-float botframe_autowaypoints_fix_from(entity p, float walkfromwp, entity wp, .entity fld)
-{
- // make it possible to go from p to wp, if we can
- // if wp is world, nearest is chosen
-
- entity w;
- vector porg;
- float t, tmin, tmax;
- vector o;
- vector save;
-
- if(!botframe_autowaypoints_fixdown(p.origin))
- return -2;
- porg = trace_endpos;
-
- if(wp)
- {
- // if any WP w fulfills wp -> w -> porg and w is closer than wp, then switch from wp to w
-
- // if wp -> porg, then OK
- float maxdist;
- if(navigation_waypoint_will_link(wp.origin, porg, p, walkfromwp, 1050))
- {
- // we may find a better one
- maxdist = vlen(wp.origin - porg);
- }
- else
- {
- // accept any "good"
- maxdist = 2100;
- }
-
- float bestdist;
- bestdist = maxdist;
- w = find(world, classname, "waypoint");
- while (w)
- {
- if(w != wp && !(w.wpflags & WAYPOINTFLAG_NORELINK))
- {
- float d;
- d = vlen(wp.origin - w.origin) + vlen(w.origin - porg);
- if(d < bestdist)
- if(navigation_waypoint_will_link(wp.origin, w.origin, p, walkfromwp, 1050))
- if(navigation_waypoint_will_link(w.origin, porg, p, walkfromwp, 1050))
- {
- bestdist = d;
- p.(fld) = w;
- }
- }
- w = find(w, classname, "waypoint");
- }
- if(bestdist < maxdist)
- {
- LOG_INFO("update chain to new nearest WP ", etos(p.(fld)), "\n");
- return 0;
- }
-
- if(bestdist < 2100)
- {
- // we know maxdist < 2100
- // so wp -> porg is still valid
- // all is good
- p.(fld) = wp;
- return 0;
- }
-
- // otherwise, no existing WP can fix our issues
- }
- else
- {
- save = p.origin;
- setorigin(p, porg);
- w = navigation_findnearestwaypoint(p, walkfromwp);
- setorigin(p, save);
- if(w)
- {
- p.(fld) = w;
- return 0;
- }
- }
-
- tmin = 0;
- tmax = 1;
- for (;;)
- {
- if(tmax - tmin < 0.001)
- {
- // did not get a good candidate
- return -1;
- }
-
- t = (tmin + tmax) * 0.5;
- o = antilag_takebackorigin(p, time - t);
- if(!botframe_autowaypoints_fixdown(o))
- return -2;
- o = trace_endpos;
-
- if(wp)
- {
- if(!navigation_waypoint_will_link(wp.origin, o, p, walkfromwp, 1050))
- {
- // we cannot walk from wp.origin to o
- // get closer to tmax
- tmin = t;
- continue;
- }
- }
- else
- {
- save = p.origin;
- setorigin(p, o);
- w = navigation_findnearestwaypoint(p, walkfromwp);
- setorigin(p, save);
- if(!w)
- {
- // we cannot walk from any WP to o
- // get closer to tmax
- tmin = t;
- continue;
- }
- }
-
- // if we get here, o is valid regarding waypoints
- // check if o is connected right to the player
- // we break if it succeeds, as that means o is a good waypoint location
- if(navigation_waypoint_will_link(o, porg, p, walkfromwp, 1050))
- break;
-
- // o is no good, we need to get closer to the player
- tmax = t;
- }
-
- LOG_INFO("spawning a waypoint for connecting to ", etos(wp), "\n");
- botframe_autowaypoints_createwp(o, p, fld, 0);
- return 1;
-}
-
-// automatically create missing waypoints
-.entity botframe_autowaypoints_lastwp0, botframe_autowaypoints_lastwp1;
-void botframe_autowaypoints_fix(entity p, float walkfromwp, .entity fld)
-{
- float r = botframe_autowaypoints_fix_from(p, walkfromwp, p.(fld), fld);
- if(r != -1)
- return;
- r = botframe_autowaypoints_fix_from(p, walkfromwp, world, fld);
- if(r != -1)
- return;
-
- LOG_INFO("emergency: got no good nearby WP to build a link from, starting a new chain\n");
- if(!botframe_autowaypoints_fixdown(p.origin))
- return; // shouldn't happen, caught above
- botframe_autowaypoints_createwp(trace_endpos, p, fld, WAYPOINTFLAG_PROTECTED);
-}
-
-void botframe_deleteuselesswaypoints()
-{
- entity w, w1, w2;
- float i, j, k;
- for (w = world; (w = findfloat(w, bot_pickup, true)); )
- {
- // NOTE: this protects waypoints if they're the ONLY nearest
- // waypoint. That's the intention.
- navigation_findnearestwaypoint(w, false); // Walk TO item.
- navigation_findnearestwaypoint(w, true); // Walk FROM item.
- }
- for (w = world; (w = find(w, classname, "waypoint")); )
- {
- w.wpflags |= WAYPOINTFLAG_DEAD_END;
- w.wpflags &= ~WAYPOINTFLAG_USEFUL;
- // WP is useful if:
- if (w.wpflags & WAYPOINTFLAG_ITEM)
- w.wpflags |= WAYPOINTFLAG_USEFUL;
- if (w.wpflags & WAYPOINTFLAG_TELEPORT)
- w.wpflags |= WAYPOINTFLAG_USEFUL;
- if (w.wpflags & WAYPOINTFLAG_PROTECTED)
- w.wpflags |= WAYPOINTFLAG_USEFUL;
- // b) WP is closest WP for an item/spawnpoint/other entity
- // This has been done above by protecting these WPs.
- }
- // c) There are w1, w, w2 so that w1 -> w, w -> w2 and not w1 -> w2.
- for (w1 = world; (w1 = find(w1, classname, "waypoint")); )
- {
- if (w1.wpflags & WAYPOINTFLAG_PERSONAL)
- continue;
- for (i = 0; i < 32; ++i)
- {
- w = waypoint_get_link(w1, i);
- if (!w)
- break;
- if (w.wpflags & WAYPOINTFLAG_PERSONAL)
- continue;
- if (w.wpflags & WAYPOINTFLAG_USEFUL)
- continue;
- for (j = 0; j < 32; ++j)
- {
- w2 = waypoint_get_link(w, j);
- if (!w2)
- break;
- if (w1 == w2)
- continue;
- if (w2.wpflags & WAYPOINTFLAG_PERSONAL)
- continue;
- // If we got here, w1 != w2 exist with w1 -> w
- // and w -> w2. That means the waypoint is not
- // a dead end.
- w.wpflags &= ~WAYPOINTFLAG_DEAD_END;
- for (k = 0; k < 32; ++k)
- {
- if (waypoint_get_link(w1, k) == w2)
- continue;
- // IF WE GET HERE, w is proven useful
- // to get from w1 to w2!
- w.wpflags |= WAYPOINTFLAG_USEFUL;
- goto next;
- }
- }
-:next
- }
- }
- // d) The waypoint is a dead end. Dead end waypoints must be kept as
- // they are needed to complete routes while autowaypointing.
-
- for (w = world; (w = find(w, classname, "waypoint")); )
- {
- if (!(w.wpflags & (WAYPOINTFLAG_USEFUL | WAYPOINTFLAG_DEAD_END)))
- {
- LOG_INFOF("Removed a waypoint at %v. Try again for more!\n", w.origin);
- te_explosion(w.origin);
- waypoint_remove(w);
- break;
- }
- }
- for (w = world; (w = find(w, classname, "waypoint")); )
- w.wpflags &= ~(WAYPOINTFLAG_USEFUL | WAYPOINTFLAG_DEAD_END); // temp flag
-}
-
-void botframe_autowaypoints()
-{
- entity p;
- FOR_EACH_REALPLAYER(p)
- {
- if(p.deadflag)
- continue;
- // going back is broken, so only fix waypoints to walk TO the player
- //botframe_autowaypoints_fix(p, false, botframe_autowaypoints_lastwp0);
- botframe_autowaypoints_fix(p, true, botframe_autowaypoints_lastwp1);
- //te_explosion(p.botframe_autowaypoints_lastwp0.origin);
- }
-
- if (autocvar_g_waypointeditor_auto >= 2) {
- botframe_deleteuselesswaypoints();
- }
-}
-
+++ /dev/null
-#ifndef WAYPOINTS_H
-#define WAYPOINTS_H
-/*
- * Globals and Fields
- */
-
-const int WAYPOINTFLAG_GENERATED = 8388608;
-const int WAYPOINTFLAG_ITEM = 4194304;
-const int WAYPOINTFLAG_TELEPORT = 2097152;
-const int WAYPOINTFLAG_NORELINK = 1048576;
-const int WAYPOINTFLAG_PERSONAL = 524288;
-const int WAYPOINTFLAG_PROTECTED = 262144; // Useless WP detection never kills these.
-const int WAYPOINTFLAG_USEFUL = 131072; // Useless WP detection temporary flag.
-const int WAYPOINTFLAG_DEAD_END = 65536; // Useless WP detection temporary flag.
-
-// fields you can query using prvm_global server to get some statistics about waypoint linking culling
-float relink_total, relink_walkculled, relink_pvsculled, relink_lengthculled;
-
-float botframe_waypointeditorlightningtime;
-float botframe_loadedforcedlinks;
-float botframe_cachedwaypointlinks;
-
-.entity wp00, wp01, wp02, wp03, wp04, wp05, wp06, wp07, wp08, wp09, wp10, wp11, wp12, wp13, wp14, wp15;
-.entity wp16, wp17, wp18, wp19, wp20, wp21, wp22, wp23, wp24, wp25, wp26, wp27, wp28, wp29, wp30, wp31;
-
-// itemscore = (howmuchmoreIwant / howmuchIcanwant) / itemdistance
-.float wp00mincost, wp01mincost, wp02mincost, wp03mincost, wp04mincost, wp05mincost, wp06mincost, wp07mincost;
-.float wp08mincost, wp09mincost, wp10mincost, wp11mincost, wp12mincost, wp13mincost, wp14mincost, wp15mincost;
-.float wp16mincost, wp17mincost, wp18mincost, wp19mincost, wp20mincost, wp21mincost, wp22mincost, wp23mincost;
-.float wp24mincost, wp25mincost, wp26mincost, wp27mincost, wp28mincost, wp29mincost, wp30mincost, wp31mincost;
-
-.float wpfire, wpcost, wpconsidered, wpisbox, wplinked, wphardwired;
-.int wpflags;
-
-.vector wpnearestpoint;
-
-/*
- * Functions
- */
-
-spawnfunc(waypoint);
-void waypoint_addlink(entity from, entity to);
-void waypoint_think();
-void waypoint_clearlinks(entity wp);
-void waypoint_schedulerelink(entity wp);
-
-void waypoint_remove(entity e);
-void waypoint_removeall();
-void waypoint_schedulerelinkall();
-void waypoint_load_links_hardwired();
-void waypoint_save_links();
-void waypoint_saveall();
-
-void waypoint_spawnforitem_force(entity e, vector org);
-void waypoint_spawnforitem(entity e);
-void waypoint_spawnforteleporter(entity e, vector destination, float timetaken);
-void waypoint_spawnforteleporter_v(entity e, vector org, vector destination, float timetaken);
-void botframe_showwaypointlinks();
-
-float waypoint_loadall();
-float waypoint_load_links();
-
-entity waypoint_spawn(vector m1, vector m2, float f);
-entity waypoint_spawnpersonal(vector position);
-
-vector waypoint_fixorigin(vector position);
-
-void botframe_autowaypoints();
-#endif
#include "campaign.qh"
#include "command/common.qh"
-#include "bot/bot.qh"
-#include "bot/navigation.qh"
+#include "bot/api.qh"
#include "../common/vehicles/all.qh"
#include "_all.qh"
#include "round_handler.qh"
-#include "bot/waypoints.qh"
-
#include "weapons/throwing.qh"
#include "command/common.qh"
#include "cheats.qh"
-#include "bot/navigation.qh"
#include "weapons/selection.qh"
#include "weapons/tracing.qh"
#include "weapons/weaponsystem.qh"
#include "cl_player.qh"
#include "_all.qh"
-#include "bot/bot.qh"
#include "cheats.qh"
#include "g_damage.qh"
#include "g_subs.qh"
#include "../playerdemo.qh"
#include "../teamplay.qh"
-#include "../bot/bot.qh"
-#include "../bot/navigation.qh"
-#include "../bot/scripting.qh"
-
#include "../mutators/mutators_include.qh"
#include "../../common/constants.qh"
float sv_foginterval;
float player_count;
-float currentbots;
float bots_would_leave;
void UpdateFrags(entity player, float f);
#include "anticheat.qh"
#include "antilag.qh"
-#include "bot/bot.qh"
#include "campaign.qh"
#include "cheats.qh"
#include "cl_client.qh"
#include "../scores.qh"
#include "../scores_rules.qh"
-#include "../bot/bot.qh"
-#include "../bot/navigation.qh"
-#include "../bot/waypoints.qh"
-#include "../bot/havocbot/roles.qh"
-#include "../bot/havocbot/role_keyhunt.qh"
-
-#include "../bot/havocbot/havocbot.qh"
-
#include "../command/vote.qh"
#include "../../common/monsters/all.qh"
void(entity bot) havocbot_ast_reset_role;
-void(float ratingscale, vector org, float sradius) havocbot_goalrating_items;
-void(float ratingscale, vector org, float sradius) havocbot_goalrating_enemyplayers;
-
// scoreboard stuff
const float ST_ASSAULT_OBJECTIVES = 1;
const float SP_ASSAULT_OBJECTIVES = 4;
return 0;
}
-//entity kh_worldkeylist;
+entity kh_worldkeylist;
.entity kh_worldkeynext;
entity kh_controller;
//float kh_tracking_enabled;
#include "gamemode.qh"
+#include "../../common/triggers/trigger/jumppads.qh"
+
float autocvar_g_nexball_basketball_bouncefactor;
float autocvar_g_nexball_basketball_bouncestop;
float autocvar_g_nexball_basketball_carrier_highspeed;
#include "../scores.qh"
#include "../scores_rules.qh"
-#include "../bot/bot.qh"
-#include "../bot/navigation.qh"
-#include "../bot/waypoints.qh"
-
-#include "../bot/havocbot/havocbot.qh"
-#include "../bot/havocbot/roles.qh"
-#include "../bot/havocbot/role_keyhunt.qh"
-
#include "../command/vote.qh"
#include "../command/common.qh"
-#include "../bot/waypoints.qh"
-
#include "pathlib.qh"
#include "main.qh"
#include "t_quake3.qc"
#include "t_quake.qc"
-#include "bot/aim.qc"
-#include "bot/bot.qc"
-#include "bot/navigation.qc"
-#include "bot/scripting.qc"
-#include "bot/waypoints.qc"
-
-#include "bot/havocbot/havocbot.qc"
-#include "bot/havocbot/role_keyhunt.qc"
-#include "bot/havocbot/roles.qc"
-
#include "command/all.qc"
#include "mutators/mutators_include.qc"
#include "../warpzonelib/server.qc"
#include "../warpzonelib/util_server.qc"
+#include "bot/impl.qc"
+
#if BUILD_MOD
#include "../../mod/server/progs.inc"
#endif
#include "portals.qh"
#include "scores.qh"
#include "spawnpoints.qh"
-#include "bot/waypoints.qh"
-#include "bot/navigation.qh"
#include "command/getreplies.qh"
#include "../common/deathtypes.qh"
#include "../common/notifications.qh"
#include "g_hook.qh"
#include "g_world.qh"
-#include "bot/bot.qh"
-#include "bot/waypoints.qh"
-
#include "command/common.qh"
#include "mutators/mutators_include.qh"
#if defined(SVQC)
#include "_all.qh"
- #include "bot/bot.qh"
- #include "bot/waypoints.qh"
-
#include "mutators/mutators_include.qh"
#include "weapons/common.qh"
#include "scores.qh"
#include "scores_rules.qh"
-#include "bot/bot.qh"
-
#include "command/vote.qh"
#include "mutators/mutators_include.qh"