From: TimePath Date: Thu, 8 Oct 2015 11:35:21 +0000 (+1100) Subject: Revert "Merge branch 'TimePath/bot_api' into 'master' " X-Git-Tag: xonotic-v0.8.2~1846 X-Git-Url: https://de.git.xonotic.org/?p=xonotic%2Fxonotic-data.pk3dir.git;a=commitdiff_plain;h=a97b89297fa91ae42b9d56c262662eb34ede3e45 Revert "Merge branch 'TimePath/bot_api' into 'master' " This reverts commit a39af09cb4b15ec94461af2c1f314098ffe7ce0c, reversing changes made to 81b0f2bc5760bb652515453ac450f2822a98b725. --- diff --git a/qcsrc/client/movelib.qc b/qcsrc/client/movelib.qc new file mode 100644 index 0000000000..074f146dcf --- /dev/null +++ b/qcsrc/client/movelib.qc @@ -0,0 +1 @@ +#include "../server/movelib.qc" diff --git a/qcsrc/client/movelib.qh b/qcsrc/client/movelib.qh new file mode 100644 index 0000000000..a0634f6de5 --- /dev/null +++ b/qcsrc/client/movelib.qh @@ -0,0 +1 @@ +#include "../server/movelib.qh" diff --git a/qcsrc/client/progs.inc b/qcsrc/client/progs.inc index 4a10ef1969..ec5c31844a 100644 --- a/qcsrc/client/progs.inc +++ b/qcsrc/client/progs.inc @@ -19,6 +19,7 @@ #include "mapvoting.qc" #include "miscfunctions.qc" #include "modeleffects.qc" +#include "movelib.qc" #include "particles.qc" #include "player_skeleton.qc" #include "rubble.qc" @@ -50,8 +51,6 @@ #include "../common/minigames/minigames.qc" #include "../common/minigames/cl_minigames.qc" -#include "../server/bot/lib/all.inc" - #include "../common/buffs/all.qc" #include "../common/items/all.qc" #include "../common/monsters/all.qc" diff --git a/qcsrc/common/monsters/monster/zombie.qc b/qcsrc/common/monsters/monster/zombie.qc index 05f466621a..8425a123ad 100644 --- a/qcsrc/common/monsters/monster/zombie.qc +++ b/qcsrc/common/monsters/monster/zombie.qc @@ -27,7 +27,6 @@ REGISTER_MONSTER(ZOMBIE, NEW(Zombie)) { #ifdef IMPLEMENTATION #ifdef SVQC -#include "../../../server/bot/lib/movelib/movelib.qh" float autocvar_g_monster_zombie_health; float autocvar_g_monster_zombie_damageforcescale = 0.55; float autocvar_g_monster_zombie_attack_melee_damage; diff --git a/qcsrc/common/monsters/sv_monsters.qc b/qcsrc/common/monsters/sv_monsters.qc index 75837cf1a0..8b05f781d2 100644 --- a/qcsrc/common/monsters/sv_monsters.qc +++ b/qcsrc/common/monsters/sv_monsters.qc @@ -14,6 +14,7 @@ #include "../../server/defs.qh" #include "../deathtypes.qh" #include "../../server/mutators/mutators_include.qh" + #include "../../server/steerlib.qh" #include "../turrets/sv_turrets.qh" #include "../turrets/util.qh" #include "../vehicles/all.qh" diff --git a/qcsrc/common/physics.qc b/qcsrc/common/physics.qc index 6d63226115..1a799c8de7 100644 --- a/qcsrc/common/physics.qc +++ b/qcsrc/common/physics.qc @@ -1109,8 +1109,6 @@ void PM_check_frozen(void) } } -.float ladder_time; - void PM_check_hitground() {SELFPARAM(); #ifdef SVQC @@ -1302,8 +1300,6 @@ void PM_swim(float maxspd_mod) PM_ClientMovement_Move(); } -.entity ladder_entity; - void PM_ladder(float maxspd_mod) {SELFPARAM(); // on a spawnfunc_func_ladder or swimming in spawnfunc_func_water diff --git a/qcsrc/common/triggers/func/breakable.qc b/qcsrc/common/triggers/func/breakable.qc index e6253b14de..af9a6b4233 100644 --- a/qcsrc/common/triggers/func/breakable.qc +++ b/qcsrc/common/triggers/func/breakable.qc @@ -4,6 +4,7 @@ #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" diff --git a/qcsrc/common/turrets/turret/walker.qc b/qcsrc/common/turrets/turret/walker.qc index 4a5ed2fc8f..e629ada798 100644 --- a/qcsrc/common/turrets/turret/walker.qc +++ b/qcsrc/common/turrets/turret/walker.qc @@ -631,7 +631,7 @@ spawnfunc(turret_walker) { if(!turret_initialize(TUR_WALKER)) remove(self); } #endif // SVQC #ifdef CSQC -#include "../../../server/bot/lib/movelib/movelib.qh" +#include "../../../client/movelib.qh" void walker_draw(entity this) { diff --git a/qcsrc/common/weapons/all.qh b/qcsrc/common/weapons/all.qh index faea45b5f8..c96061fe61 100644 --- a/qcsrc/common/weapons/all.qh +++ b/qcsrc/common/weapons/all.qh @@ -26,6 +26,10 @@ WepSet ReadWepSet(); #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); diff --git a/qcsrc/dpdefs/csprogsdefs.qh b/qcsrc/dpdefs/csprogsdefs.qh index 0daa5dce35..6ad27242aa 100644 --- a/qcsrc/dpdefs/csprogsdefs.qh +++ b/qcsrc/dpdefs/csprogsdefs.qh @@ -23,8 +23,6 @@ #undef particleeffectnum #undef setmodel -entity(.entity fld, entity match) findchainentity = #403; - #pragma noref 0 #define ReadFloat() ReadCoord() diff --git a/qcsrc/server/autocvars.qh b/qcsrc/server/autocvars.qh index 6a2ff9d4cd..affd9437ed 100644 --- a/qcsrc/server/autocvars.qh +++ b/qcsrc/server/autocvars.qh @@ -7,6 +7,63 @@ bool autocvar__campaign_testrun; 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") @@ -236,6 +293,7 @@ float autocvar_g_ctf_dropped_capture_delay; 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; @@ -444,6 +502,7 @@ float autocvar_g_turrets_targetscan_maxdelay; 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; @@ -475,6 +534,7 @@ int autocvar_rescan_pending; 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; @@ -524,6 +584,7 @@ float autocvar_sv_itemstime; 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; @@ -591,6 +652,7 @@ float autocvar_timelimit_overtime; 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; diff --git a/qcsrc/server/bot/aim.qc b/qcsrc/server/bot/aim.qc new file mode 100644 index 0000000000..4f750cdb4d --- /dev/null +++ b/qcsrc/server/bot/aim.qc @@ -0,0 +1,389 @@ +#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; +} diff --git a/qcsrc/server/bot/aim.qh b/qcsrc/server/bot/aim.qh new file mode 100644 index 0000000000..d1cbd0d629 --- /dev/null +++ b/qcsrc/server/bot/aim.qh @@ -0,0 +1,101 @@ +#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 diff --git a/qcsrc/server/bot/api.qh b/qcsrc/server/bot/api.qh deleted file mode 100644 index 9dc20ebb3b..0000000000 --- a/qcsrc/server/bot/api.qh +++ /dev/null @@ -1,96 +0,0 @@ -#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 diff --git a/qcsrc/server/bot/bot.qc b/qcsrc/server/bot/bot.qc new file mode 100644 index 0000000000..00051cadb9 --- /dev/null +++ b/qcsrc/server/bot/bot.qc @@ -0,0 +1,725 @@ +#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; + } +} diff --git a/qcsrc/server/bot/bot.qh b/qcsrc/server/bot/bot.qh new file mode 100644 index 0000000000..d5a2794008 --- /dev/null +++ b/qcsrc/server/bot/bot.qh @@ -0,0 +1,118 @@ +#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 diff --git a/qcsrc/server/bot/default/aim.qc b/qcsrc/server/bot/default/aim.qc deleted file mode 100644 index 6353d1ee3a..0000000000 --- a/qcsrc/server/bot/default/aim.qc +++ /dev/null @@ -1,384 +0,0 @@ -#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; -} diff --git a/qcsrc/server/bot/default/aim.qh b/qcsrc/server/bot/default/aim.qh deleted file mode 100644 index d1cbd0d629..0000000000 --- a/qcsrc/server/bot/default/aim.qh +++ /dev/null @@ -1,101 +0,0 @@ -#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 diff --git a/qcsrc/server/bot/default/all.inc b/qcsrc/server/bot/default/all.inc deleted file mode 100644 index a613a39187..0000000000 --- a/qcsrc/server/bot/default/all.inc +++ /dev/null @@ -1,11 +0,0 @@ -#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" diff --git a/qcsrc/server/bot/default/bot.qc b/qcsrc/server/bot/default/bot.qc deleted file mode 100644 index 7eb438ec85..0000000000 --- a/qcsrc/server/bot/default/bot.qc +++ /dev/null @@ -1,697 +0,0 @@ -#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; - } -} diff --git a/qcsrc/server/bot/default/bot.qh b/qcsrc/server/bot/default/bot.qh deleted file mode 100644 index b3c628de1d..0000000000 --- a/qcsrc/server/bot/default/bot.qh +++ /dev/null @@ -1,109 +0,0 @@ -#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 diff --git a/qcsrc/server/bot/default/defs.qh b/qcsrc/server/bot/default/defs.qh deleted file mode 100644 index 2d7c49975d..0000000000 --- a/qcsrc/server/bot/default/defs.qh +++ /dev/null @@ -1,61 +0,0 @@ -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; diff --git a/qcsrc/server/bot/default/havocbot/havocbot.qc b/qcsrc/server/bot/default/havocbot/havocbot.qc deleted file mode 100644 index ae5314a8cd..0000000000 --- a/qcsrc/server/bot/default/havocbot/havocbot.qc +++ /dev/null @@ -1,1311 +0,0 @@ -#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= 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) - { - 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 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= 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 -} diff --git a/qcsrc/server/bot/default/havocbot/havocbot.qh b/qcsrc/server/bot/default/havocbot/havocbot.qh deleted file mode 100644 index 2d3d329132..0000000000 --- a/qcsrc/server/bot/default/havocbot/havocbot.qh +++ /dev/null @@ -1,66 +0,0 @@ -#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 diff --git a/qcsrc/server/bot/default/havocbot/role_keyhunt.qc b/qcsrc/server/bot/default/havocbot/role_keyhunt.qc deleted file mode 100644 index a260685814..0000000000 --- a/qcsrc/server/bot/default/havocbot/role_keyhunt.qc +++ /dev/null @@ -1,212 +0,0 @@ -#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; -} diff --git a/qcsrc/server/bot/default/havocbot/role_keyhunt.qh b/qcsrc/server/bot/default/havocbot/role_keyhunt.qh deleted file mode 100644 index ac3c77e524..0000000000 --- a/qcsrc/server/bot/default/havocbot/role_keyhunt.qh +++ /dev/null @@ -1,5 +0,0 @@ -#ifndef ROLE_KEYHUNT_H -#define ROLE_KEYHUNT_H - -void havocbot_chooserole_kh(); -#endif diff --git a/qcsrc/server/bot/default/havocbot/roles.qc b/qcsrc/server/bot/default/havocbot/roles.qc deleted file mode 100644 index c520ab3cd5..0000000000 --- a/qcsrc/server/bot/default/havocbot/roles.qc +++ /dev/null @@ -1,241 +0,0 @@ -#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(); -} diff --git a/qcsrc/server/bot/default/havocbot/roles.qh b/qcsrc/server/bot/default/havocbot/roles.qh deleted file mode 100644 index cfabf0534d..0000000000 --- a/qcsrc/server/bot/default/havocbot/roles.qh +++ /dev/null @@ -1,4 +0,0 @@ -#ifndef ROLES_H -#define ROLES_H -void havocbot_goalrating_controlpoints(float ratingscale, vector org, float sradius); -#endif diff --git a/qcsrc/server/bot/default/navigation.qc b/qcsrc/server/bot/default/navigation.qc deleted file mode 100644 index 8898b71632..0000000000 --- a/qcsrc/server/bot/default/navigation.qc +++ /dev/null @@ -1,1259 +0,0 @@ -#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 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++; -} diff --git a/qcsrc/server/bot/default/navigation.qh b/qcsrc/server/bot/default/navigation.qh deleted file mode 100644 index cf4a5ce5b2..0000000000 --- a/qcsrc/server/bot/default/navigation.qh +++ /dev/null @@ -1,80 +0,0 @@ -#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 diff --git a/qcsrc/server/bot/default/scripting.qc b/qcsrc/server/bot/default/scripting.qc deleted file mode 100644 index 3f53d2b9f2..0000000000 --- a/qcsrc/server/bot/default/scripting.qc +++ /dev/null @@ -1,1346 +0,0 @@ -#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 \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 .. \n"); - LOG_INFO(" sv_cmd .. \n"); - LOG_INFO(" sv_cmd .. else\n"); - LOG_INFO(" sv_cmd .. \n"); - LOG_INFO(" sv_cmd .. \n"); - LOG_INFO(" sv_cmd .. fi\n"); - LOG_INFO("Conditions: a=b, a>b, a50; 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 \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)=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; -} diff --git a/qcsrc/server/bot/default/scripting.qh b/qcsrc/server/bot/default/scripting.qh deleted file mode 100644 index 1f2d4da312..0000000000 --- a/qcsrc/server/bot/default/scripting.qh +++ /dev/null @@ -1,84 +0,0 @@ -#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 diff --git a/qcsrc/server/bot/default/waypoints.qc b/qcsrc/server/bot/default/waypoints.qc deleted file mode 100644 index 25031ef340..0000000000 --- a/qcsrc/server/bot/default/waypoints.qc +++ /dev/null @@ -1,1160 +0,0 @@ -#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(); - } -} - diff --git a/qcsrc/server/bot/default/waypoints.qh b/qcsrc/server/bot/default/waypoints.qh deleted file mode 100644 index 42082c6ab8..0000000000 --- a/qcsrc/server/bot/default/waypoints.qh +++ /dev/null @@ -1,68 +0,0 @@ -#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 diff --git a/qcsrc/server/bot/havocbot/havocbot.qc b/qcsrc/server/bot/havocbot/havocbot.qc new file mode 100644 index 0000000000..f69527916c --- /dev/null +++ b/qcsrc/server/bot/havocbot/havocbot.qc @@ -0,0 +1,1324 @@ +#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= 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) + { + 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 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= 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 +} diff --git a/qcsrc/server/bot/havocbot/havocbot.qh b/qcsrc/server/bot/havocbot/havocbot.qh new file mode 100644 index 0000000000..2d3d329132 --- /dev/null +++ b/qcsrc/server/bot/havocbot/havocbot.qh @@ -0,0 +1,66 @@ +#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 diff --git a/qcsrc/server/bot/havocbot/role_keyhunt.qc b/qcsrc/server/bot/havocbot/role_keyhunt.qc new file mode 100644 index 0000000000..b1603c792a --- /dev/null +++ b/qcsrc/server/bot/havocbot/role_keyhunt.qc @@ -0,0 +1,220 @@ +#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; +} diff --git a/qcsrc/server/bot/havocbot/role_keyhunt.qh b/qcsrc/server/bot/havocbot/role_keyhunt.qh new file mode 100644 index 0000000000..f3136c6307 --- /dev/null +++ b/qcsrc/server/bot/havocbot/role_keyhunt.qh @@ -0,0 +1,8 @@ +#ifndef ROLE_KEYHUNT_H +#define ROLE_KEYHUNT_H + +entity kh_worldkeylist; +.entity kh_worldkeynext; + +void havocbot_chooserole_kh(); +#endif diff --git a/qcsrc/server/bot/havocbot/roles.qc b/qcsrc/server/bot/havocbot/roles.qc new file mode 100644 index 0000000000..037968f136 --- /dev/null +++ b/qcsrc/server/bot/havocbot/roles.qc @@ -0,0 +1,247 @@ +#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(); +} diff --git a/qcsrc/server/bot/havocbot/roles.qh b/qcsrc/server/bot/havocbot/roles.qh new file mode 100644 index 0000000000..cfabf0534d --- /dev/null +++ b/qcsrc/server/bot/havocbot/roles.qh @@ -0,0 +1,4 @@ +#ifndef ROLES_H +#define ROLES_H +void havocbot_goalrating_controlpoints(float ratingscale, vector org, float sradius); +#endif diff --git a/qcsrc/server/bot/havocbot/scripting.qh b/qcsrc/server/bot/havocbot/scripting.qh new file mode 100644 index 0000000000..c09dbdeacc --- /dev/null +++ b/qcsrc/server/bot/havocbot/scripting.qh @@ -0,0 +1,5 @@ +#ifndef HAVOCBOT_SCRIPTING_H +#define HAVOCBOT_SCRIPTING_H + +void bot_clearqueue(entity bot); +#endif diff --git a/qcsrc/server/bot/impl.qc b/qcsrc/server/bot/impl.qc deleted file mode 100644 index c0b6c8716d..0000000000 --- a/qcsrc/server/bot/impl.qc +++ /dev/null @@ -1,5 +0,0 @@ -#include "api.qh" - -#include "lib/all.inc" - -#include "default/all.inc" diff --git a/qcsrc/server/bot/lib/all.inc b/qcsrc/server/bot/lib/all.inc deleted file mode 100644 index 9a92a96160..0000000000 --- a/qcsrc/server/bot/lib/all.inc +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef BOT_LIB_ALL_H -#define BOT_LIB_ALL_H - -#include "movelib/all.inc" -#ifdef SVQC -#include "pathlib/all.inc" -#endif -#include "steerlib/all.inc" - -#endif diff --git a/qcsrc/server/bot/lib/movelib/all.inc b/qcsrc/server/bot/lib/movelib/all.inc deleted file mode 100644 index c12ce90ef7..0000000000 --- a/qcsrc/server/bot/lib/movelib/all.inc +++ /dev/null @@ -1 +0,0 @@ -#include "movelib.qc" diff --git a/qcsrc/server/bot/lib/movelib/movelib.qc b/qcsrc/server/bot/lib/movelib/movelib.qc deleted file mode 100644 index acacba093a..0000000000 --- a/qcsrc/server/bot/lib/movelib/movelib.qc +++ /dev/null @@ -1,237 +0,0 @@ -#include "movelib.qh" - -#ifdef SVQC -.vector moveto; - -/** - Simulate drag - self.velocity = movelib_dragvec(self.velocity,0.02,0.5); -**/ -vector movelib_dragvec(float drag, float exp_) -{SELFPARAM(); - float lspeed,ldrag; - - lspeed = vlen(self.velocity); - ldrag = lspeed * drag; - ldrag = ldrag * (drag * exp_); - ldrag = 1 - (ldrag / lspeed); - - return self.velocity * ldrag; -} - -/** - Simulate drag - self.velocity *= movelib_dragflt(somespeed,0.01,0.7); -**/ -float movelib_dragflt(float fspeed,float drag,float exp_) -{ - float ldrag; - - ldrag = fspeed * drag; - ldrag = ldrag * ldrag * exp_; - ldrag = 1 - (ldrag / fspeed); - - return ldrag; -} - -/** - Do a inertia simulation based on velocity. - Basicaly, this allows you to simulate loss of steering with higher speed. - self.velocity = movelib_inertmove_byspeed(self.velocity,newvel,1000,0.1,0.9); -**/ -vector movelib_inertmove_byspeed(vector vel_new, float vel_max,float newmin,float oldmax) -{SELFPARAM(); - float influense; - - influense = vlen(self.velocity) * (1 / vel_max); - - influense = bound(newmin,influense,oldmax); - - return (vel_new * (1 - influense)) + (self.velocity * influense); -} - -vector movelib_inertmove(vector new_vel,float new_bias) -{SELFPARAM(); - return new_vel * new_bias + self.velocity * (1-new_bias); -} - -void movelib_move(vector force,float max_velocity,float drag,float theMass,float breakforce) -{SELFPARAM(); - float deltatime; - float acceleration; - float mspeed; - vector breakvec; - - deltatime = time - self.movelib_lastupdate; - if (deltatime > 0.15) deltatime = 0; - self.movelib_lastupdate = time; - if (!deltatime) return; - - mspeed = vlen(self.velocity); - - if (theMass) - acceleration = vlen(force) / theMass; - else - acceleration = vlen(force); - - if (self.flags & FL_ONGROUND) - { - if (breakforce) - { - breakvec = (normalize(self.velocity) * (breakforce / theMass) * deltatime); - self.velocity = self.velocity - breakvec; - } - - self.velocity = self.velocity + force * (acceleration * deltatime); - } - - if (drag) - self.velocity = movelib_dragvec(drag, 1); - - if (self.waterlevel > 1) - { - self.velocity = self.velocity + force * (acceleration * deltatime); - self.velocity = self.velocity + '0 0 0.05' * autocvar_sv_gravity * deltatime; - } - else - self.velocity = self.velocity + '0 0 -1' * autocvar_sv_gravity * deltatime; - - mspeed = vlen(self.velocity); - - if (max_velocity) - if (mspeed > max_velocity) - self.velocity = normalize(self.velocity) * (mspeed - 50);//* max_velocity; -} - -/* -.float mass; -.float side_friction; -.float ground_friction; -.float air_friction; -.float water_friction; -.float buoyancy; -float movelib_deltatime; - -void movelib_startupdate() -{ - movelib_deltatime = time - self.movelib_lastupdate; - - if (movelib_deltatime > 0.5) - movelib_deltatime = 0; - - self.movelib_lastupdate = time; -} - -void movelib_update(vector dir,float force) -{ - vector acceleration; - float old_speed; - float ffriction,v_z; - - vector breakvec; - vector old_dir; - vector ggravity; - vector old; - - if(!movelib_deltatime) - return; - v_z = self.velocity_z; - old_speed = vlen(self.velocity); - old_dir = normalize(self.velocity); - - //ggravity = (autocvar_sv_gravity / self.mass) * '0 0 100'; - acceleration = (force / self.mass) * dir; - //acceleration -= old_dir * (old_speed / self.mass); - acceleration -= ggravity; - - if(self.waterlevel > 1) - { - ffriction = self.water_friction; - acceleration += self.buoyancy * '0 0 1'; - } - else - if(self.flags & FL_ONGROUND) - ffriction = self.ground_friction; - else - ffriction = self.air_friction; - - acceleration *= ffriction; - //self.velocity = self.velocity * (ffriction * movelib_deltatime); - self.velocity += acceleration * movelib_deltatime; - self.velocity_z = v_z; - -} -*/ - -void movelib_beak_simple(float force) -{SELFPARAM(); - float mspeed; - vector mdir; - float vz; - - mspeed = max(0,vlen(self.velocity) - force); - mdir = normalize(self.velocity); - vz = self.velocity.z; - self.velocity = mdir * mspeed; - self.velocity_z = vz; -} - -/** -Pitches and rolls the entity to match the gound. -Yed need to set v_up and v_forward (generally by calling makevectors) before calling this. -**/ -#endif - -void movelib_groundalign4point(float spring_length, float spring_up, float blendrate, float _max) -{SELFPARAM(); - vector a, b, c, d, e, r, push_angle, ahead, side; - - push_angle.y = 0; - r = (self.absmax + self.absmin) * 0.5 + (v_up * spring_up); - e = v_up * spring_length; - - // Put springs slightly inside bbox - ahead = v_forward * (self.maxs.x * 0.8); - side = v_right * (self.maxs.y * 0.8); - - a = r + ahead + side; - b = r + ahead - side; - c = r - ahead + side; - d = r - ahead - side; - - traceline(a, a - e,MOVE_NORMAL,self); - a.z = (1 - trace_fraction); - r = trace_endpos; - - traceline(b, b - e,MOVE_NORMAL,self); - b.z = (1 - trace_fraction); - r += trace_endpos; - - traceline(c, c - e,MOVE_NORMAL,self); - c.z = (1 - trace_fraction); - r += trace_endpos; - - traceline(d, d - e,MOVE_NORMAL,self); - d.z = (1 - trace_fraction); - r += trace_endpos; - - a.x = r.z; - r = self.origin; - r.z = r.z; - - push_angle.x = (a.z - c.z) * _max; - push_angle.x += (b.z - d.z) * _max; - - push_angle.z = (b.z - a.z) * _max; - push_angle.z += (d.z - c.z) * _max; - - //self.angles_x += push_angle_x * 0.95; - //self.angles_z += push_angle_z * 0.95; - - self.angles_x = ((1-blendrate) * self.angles.x) + (push_angle.x * blendrate); - self.angles_z = ((1-blendrate) * self.angles.z) + (push_angle.z * blendrate); - - //a = self.origin; - setorigin(self,r); -} - diff --git a/qcsrc/server/bot/lib/movelib/movelib.qh b/qcsrc/server/bot/lib/movelib/movelib.qh deleted file mode 100644 index 8a4bfd4883..0000000000 --- a/qcsrc/server/bot/lib/movelib/movelib.qh +++ /dev/null @@ -1,53 +0,0 @@ -#ifndef MOVELIB_H -#define MOVELIB_H - -#ifdef SVQC -.vector moveto; - -/** - Simulate drag - self.velocity = movelib_dragvec(self.velocity,0.02,0.5); -**/ -vector movelib_dragvec(float drag, float exp_); - -/** - Simulate drag - self.velocity *= movelib_dragflt(somespeed,0.01,0.7); -**/ -float movelib_dragflt(float fspeed,float drag,float exp_); - -/** - Do a inertia simulation based on velocity. - Basicaly, this allows you to simulate loss of steering with higher speed. - self.velocity = movelib_inertmove_byspeed(self.velocity,newvel,1000,0.1,0.9); -**/ -vector movelib_inertmove_byspeed(vector vel_new, float vel_max,float newmin,float oldmax); - -vector movelib_inertmove(vector new_vel,float new_bias); - -.float movelib_lastupdate; -void movelib_move(vector force,float max_velocity,float drag,float theMass,float breakforce); - -/* -void movelib_move_simple(vector newdir,float velo,float blendrate) -{ - self.velocity = self.velocity * (1 - blendrate) + (newdir * blendrate) * velo; -} -*/ -#define movelib_move_simple(newdir,velo,blendrate) \ - self.velocity = self.velocity * (1 - blendrate) + (newdir * blendrate) * velo - -#define movelib_move_simple_gravity(newdir,velo,blendrate) \ - if(self.flags & FL_ONGROUND) movelib_move_simple(newdir,velo,blendrate) - -void movelib_beak_simple(float force); - -/** -Pitches and rolls the entity to match the gound. -Yed need to set v_up and v_forward (generally by calling makevectors) before calling this. -**/ -#endif - -void movelib_groundalign4point(float spring_length, float spring_up, float blendrate, float _max); - -#endif diff --git a/qcsrc/server/bot/lib/pathlib/all.inc b/qcsrc/server/bot/lib/pathlib/all.inc deleted file mode 100644 index 8622734d07..0000000000 --- a/qcsrc/server/bot/lib/pathlib/all.inc +++ /dev/null @@ -1,6 +0,0 @@ -#include "costs.qc" -#include "expandnode.qc" -#include "main.qc" -#include "movenode.qc" -#include "path_waypoint.qc" -#include "utility.qc" diff --git a/qcsrc/server/bot/lib/pathlib/costs.qc b/qcsrc/server/bot/lib/pathlib/costs.qc deleted file mode 100644 index eb885a4f53..0000000000 --- a/qcsrc/server/bot/lib/pathlib/costs.qc +++ /dev/null @@ -1,146 +0,0 @@ -#include "pathlib.qh" - -float pathlib_g_static(entity parent,vector to, float static_cost) -{ - return parent.pathlib_node_g + static_cost; -} - -float pathlib_g_static_water(entity parent,vector to, float static_cost) -{ - if(inwater(to)) - return parent.pathlib_node_g + static_cost * pathlib_movecost_waterfactor; - else - return parent.pathlib_node_g + static_cost; -} - -float pathlib_g_euclidean(entity parent,vector to, float static_cost) -{ - return parent.pathlib_node_g + vlen(parent.origin - to); -} - -float pathlib_g_euclidean_water(entity parent,vector to, float static_cost) -{ - if(inwater(to)) - return parent.pathlib_node_g + vlen(parent.origin - to) * pathlib_movecost_waterfactor; - else - return parent.pathlib_node_g + vlen(parent.origin - to); -} - - -/** - Manhattan Menas we expect to move up,down left or right - No diagonal moves espected. (like moving bewteen city blocks) -**/ -float pathlib_h_manhattan(vector a,vector b) -{ - //h(n) = D * (abs(n.x-goal.x) + abs(n.y-goal.y)) - - float h; - h = fabs(a.x - b.x); - h += fabs(a.y - b.y); - h *= pathlib_gridsize; - - return h; -} - -/** - This heuristic consider both stright and disagonal moves - to have teh same cost. -**/ -float pathlib_h_diagonal(vector a,vector b) -{ - //h(n) = D * max(abs(n.x-goal.x), abs(n.y-goal.y)) - float h,x,y; - - x = fabs(a.x - b.x); - y = fabs(a.y - b.y); - h = pathlib_movecost * max(x,y); - - return h; -} - -/** - This heuristic only considers the stright line distance. - Will usualy mean a lower H then G meaning A* Will speand more - and run slower. -**/ -float pathlib_h_euclidean(vector a,vector b) -{ - return vlen(a - b); -} - -/** - This heuristic consider both stright and disagonal moves, - But has a separate cost for diagonal moves. -**/ -float pathlib_h_diagonal2(vector a,vector b) -{ - float h_diag,h_str,h,x,y; - - /* - h_diagonal(n) = min(abs(n.x-goal.x), abs(n.y-goal.y)) - h_straight(n) = (abs(n.x-goal.x) + abs(n.y-goal.y)) - h(n) = D2 * h_diagonal(n) + D * (h_straight(n) - 2*h_diagonal(n))) - */ - - x = fabs(a.x - b.x); - y = fabs(a.y - b.y); - - h_diag = min(x,y); - h_str = x + y; - - h = pathlib_movecost_diag * h_diag; - h += pathlib_movecost * (h_str - 2 * h_diag); - - return h; -} - -/** - This heuristic consider both stright and disagonal moves, - But has a separate cost for diagonal moves. -**/ -float pathlib_h_diagonal2sdp(vector preprev,vector prev,vector point,vector end) -{ - float h_diag,h_str,h,x,y,z; - - //h_diagonal(n) = min(abs(n.x-goal.x), abs(n.y-goal.y)) - //h_straight(n) = (abs(n.x-goal.x) + abs(n.y-goal.y)) - //h(n) = D2 * h_diagonal(n) + D * (h_straight(n) - 2*h_diagonal(n))) - - x = fabs(point.x - end.x); - y = fabs(point.y - end.y); - z = fabs(point.z - end.z); - - h_diag = min(x,y,z); - h_str = x + y + z; - - h = pathlib_movecost_diag * h_diag; - h += pathlib_movecost * (h_str - 2 * h_diag); - - float m; - vector d1,d2; - - d1 = normalize(preprev - point); - d2 = normalize(prev - point); - m = vlen(d1-d2); - - return h * m; -} - - -float pathlib_h_diagonal3(vector a,vector b) -{ - float h_diag,h_str,h,x,y,z; - - x = fabs(a.x - b.x); - y = fabs(a.y - b.y); - z = fabs(a.z - b.z); - - h_diag = min(x,y,z); - h_str = x + y + z; - - h = pathlib_movecost_diag * h_diag; - h += pathlib_movecost * (h_str - 2 * h_diag); - - return h; -} diff --git a/qcsrc/server/bot/lib/pathlib/debug.qc b/qcsrc/server/bot/lib/pathlib/debug.qc deleted file mode 100644 index 37e167aae7..0000000000 --- a/qcsrc/server/bot/lib/pathlib/debug.qc +++ /dev/null @@ -1,123 +0,0 @@ -#include "pathlib.qh" - -MODEL(SQUARE, "models/pathlib/square.md3"); -MODEL(SQUARE_GOOD, "models/pathlib/goodsquare.md3"); -MODEL(SQUARE_BAD, "models/pathlib/badsquare.md3"); -MODEL(EDGE, "models/pathlib/edge.md3"); - -#ifdef TURRET_DEBUG -void mark_error(vector where,float lifetime); -void mark_info(vector where,float lifetime); -entity mark_misc(vector where,float lifetime); -#endif - -void pathlib_showpath(entity start) -{ - entity e; - e = start; - while(e.path_next) - { - te_lightning1(e,e.origin,e.path_next.origin); - e = e.path_next; - } -} - -void path_dbg_think() -{SELFPARAM(); - pathlib_showpath(self); - self.nextthink = time + 1; -} - -void __showpath2_think() -{SELFPARAM(); - #ifdef TURRET_DEBUG - mark_info(self.origin,1); - #endif - if(self.path_next) - { - self.path_next.think = __showpath2_think; - self.path_next.nextthink = time + 0.15; - } - else - { - self.owner.think = __showpath2_think; - self.owner.nextthink = time + 0.15; - } -} - -void pathlib_showpath2(entity path) -{ - path.think = __showpath2_think; - path.nextthink = time; -} - - -void pathlib_showsquare2(entity node ,vector ncolor,float align) -{ - - node.alpha = 0.25; - node.scale = pathlib_gridsize / 512.001; - node.solid = SOLID_NOT; - - setmodel(node, MDL_SQUARE); - setorigin(node,node.origin); - node.colormod = ncolor; - - if(align) - { - traceline(node.origin + '0 0 32', node.origin - '0 0 128', MOVE_WORLDONLY, node); - node.angles = vectoangles(trace_plane_normal); - node.angles_x -= 90; - } -} - -void SUB_Remove(); - -void pathlib_showsquare(vector where,float goodsquare,float _lifetime) -{ - entity s; - - if(!_lifetime) - _lifetime = time + 30; - else - _lifetime += time; - - s = spawn(); - s.alpha = 0.25; - s.think = SUB_Remove; - s.nextthink = _lifetime; - s.scale = pathlib_gridsize / 512.001; - s.solid = SOLID_NOT; - - setmodel(s, goodsquare ? MDL_SQUARE_GOOD : MDL_SQUARE_BAD); - - traceline(where + '0 0 32',where - '0 0 128',MOVE_WORLDONLY,s); - - s.angles = vectoangles(trace_plane_normal); - s.angles_x -= 90; - setorigin(s,where); -} - -void pathlib_showedge(vector where,float _lifetime,float rot) -{ - entity e; - - if(!_lifetime) - _lifetime = time + 30; - else - _lifetime += time; - - e = spawn(); - e.alpha = 0.25; - e.think = SUB_Remove; - e.nextthink = _lifetime; - e.scale = pathlib_gridsize / 512; - e.solid = SOLID_NOT; - setorigin(e,where); - setmodel(e, MDL_EDGE); - //traceline(where + '0 0 32',where - '0 0 128',MOVE_WORLDONLY,e); - //e.angles = vectoangles(trace_plane_normal); - e.angles_y = rot; - //e.angles_x += 90; - -} diff --git a/qcsrc/server/bot/lib/pathlib/expandnode.qc b/qcsrc/server/bot/lib/pathlib/expandnode.qc deleted file mode 100644 index 1f095a7856..0000000000 --- a/qcsrc/server/bot/lib/pathlib/expandnode.qc +++ /dev/null @@ -1,235 +0,0 @@ -#include "pathlib.qh" -#include "utility.qh" - -vector plib_points2[8]; -vector plib_points[8]; -float plib_fvals[8]; - -float pathlib_expandnode_starf(entity node, vector start, vector goal) -{ - vector where,f,r,t; - float fc,c; - entity nap; - - where = node.origin; - - f = PLIB_FORWARD * pathlib_gridsize; - r = PLIB_RIGHT * pathlib_gridsize; - - // Forward - plib_points[0] = where + f; - - // Back - plib_points[1] = where - f; - - // Right - plib_points[2] = where + r; - - // Left - plib_points[3] = where - r; - - // Forward-right - plib_points[4] = where + f + r; - - // Forward-left - plib_points[5] = where + f - r; - - // Back-right - plib_points[6] = where - f + r; - - // Back-left - plib_points[7] = where - f - r; - - for(int i=0;i < 8; ++i) - { - t = plib_points[i]; - fc = pathlib_heuristic(t,goal) + pathlib_cost(node, t, pathlib_gridsize); - plib_fvals[i] = fc; - - } - - fc = plib_fvals[0]; - plib_points2[0] = plib_points[0]; - vector bp; - bp = plib_points[0]; - int fc2 = 0; - for(int i = 0; i < 8; ++i) - { - c = 0; - nap = pathlib_nodeatpoint(plib_points[i]); - if(nap) - if(nap.owner == openlist) - c = 1; - else - c = 1; - - if(c) - if(plib_fvals[i] < fc) - { - bp = plib_points[i]; - fc = plib_fvals[i]; - plib_points2[fc2] = plib_points[i]; - ++fc2; - } - - /* - nap = pathlib_nodeatpoint(plib_points[i]); - if(nap) - if not nap.owner == closedlist) - { - } - */ - } - - pathlib_makenode(node, start, bp, goal, pathlib_gridsize); - - for(int i = 0; i < 3; ++i) - { - pathlib_makenode(node, start, plib_points2[i], goal, pathlib_gridsize); - } - - return pathlib_open_cnt; -} - -float pathlib_expandnode_star(entity node, vector start, vector goal) -{ - vector point, where, f, r; - - where = node.origin; - - f = PLIB_FORWARD * pathlib_gridsize; - r = PLIB_RIGHT * pathlib_gridsize; - - if (node.pathlib_node_edgeflags == pathlib_node_edgeflag_unknown) - node.pathlib_node_edgeflags = tile_check_plus2(node.origin); - - if(node.pathlib_node_edgeflags == pathlib_node_edgeflag_none) - { - LOG_TRACE("Node at ", vtos(node.origin), " not expanable"); - return pathlib_open_cnt; - } - - // Forward - if (node.pathlib_node_edgeflags & pathlib_node_edgeflag_forward) - { - point = where + f; - pathlib_makenode(node, start, point, goal, pathlib_movecost); - } - - // Back - if (node.pathlib_node_edgeflags & pathlib_node_edgeflag_back) - { - point = where - f; - pathlib_makenode(node, start, point, goal, pathlib_movecost); - } - - // Right - if (node.pathlib_node_edgeflags & pathlib_node_edgeflag_right) - { - point = where + r; - pathlib_makenode(node, start, point, goal, pathlib_movecost); - } - - // Left - if (node.pathlib_node_edgeflags & pathlib_node_edgeflag_left) - { - point = where - r; - pathlib_makenode(node, start, point, goal, pathlib_movecost); - - } - - // Forward-right - if (node.pathlib_node_edgeflags & pathlib_node_edgeflag_forwardright) - { - point = where + f + r; - pathlib_makenode(node, start, point, goal, pathlib_movecost_diag); - } - - // Forward-left - if (node.pathlib_node_edgeflags & pathlib_node_edgeflag_forwardleft) - { - point = where + f - r; - pathlib_makenode(node, start, point, goal, pathlib_movecost_diag); - - } - - // Back-right - if (node.pathlib_node_edgeflags & pathlib_node_edgeflag_backright) - { - point = where - f + r; - pathlib_makenode(node, start, point, goal, pathlib_movecost_diag); - } - - // Back-left - if (node.pathlib_node_edgeflags & pathlib_node_edgeflag_backleft) - { - point = where - f - r; - pathlib_makenode(node, start, point, goal, pathlib_movecost_diag); - } - - return pathlib_open_cnt; -} - -float pathlib_expandnode_octagon(entity node, vector start, vector goal) -{ - vector point,where,f,r; - - where = node.origin; - - f = PLIB_FORWARD * pathlib_gridsize; - r = PLIB_RIGHT * pathlib_gridsize; - - // Forward - point = where + f; - pathlib_makenode(node, start, point, goal, pathlib_movecost); - - // Back - point = where - f; - pathlib_makenode(node, start, point, goal, pathlib_movecost); - - // Right - point = where + r; - pathlib_makenode(node, start, point, goal, pathlib_movecost); - - // Left - point = where - r; - pathlib_makenode(node, start, point, goal, pathlib_movecost); - - f = PLIB_FORWARD * pathlib_gridsize * 0.5; - r = PLIB_RIGHT * pathlib_gridsize * 0.5; - - // Forward-right - point = where + f + r; - pathlib_makenode(node, start, point, goal, pathlib_movecost); - - - // Forward-left - point = where + f - r; - pathlib_makenode(node, start, point, goal, pathlib_movecost); - - - // Back-right - point = where - f + r; - pathlib_makenode(node, start, point, goal, pathlib_movecost); - - // Back-left - point = where - f - r; - pathlib_makenode(node, start, point, goal, pathlib_movecost); - - return pathlib_open_cnt; -} - -float pathlib_expandnode_box(entity node, vector start, vector goal) -{ - vector v; - - for(v.z = node.origin.z - pathlib_gridsize; v.z <= node.origin.z + pathlib_gridsize; v.z += pathlib_gridsize) - for(v.y = node.origin.y - pathlib_gridsize; v.y <= node.origin.y + pathlib_gridsize; v.y += pathlib_gridsize) - for(v.x = node.origin.x - pathlib_gridsize; v.x <= node.origin.x + pathlib_gridsize; v.x += pathlib_gridsize) - { - //if(vlen(v - node.origin)) - pathlib_makenode(node,start,v,goal,pathlib_movecost); - } - - return pathlib_open_cnt; -} diff --git a/qcsrc/server/bot/lib/pathlib/main.qc b/qcsrc/server/bot/lib/pathlib/main.qc deleted file mode 100644 index 39e23bd0bf..0000000000 --- a/qcsrc/server/bot/lib/pathlib/main.qc +++ /dev/null @@ -1,566 +0,0 @@ -#include "pathlib.qh" -#include "utility.qh" - -void pathlib_deletepath(entity start) -{ - entity e; - - e = findchainentity(owner, start); - while(e) - { - e.think = SUB_Remove; - e.nextthink = time; - e = e.chain; - } -} - -//#define PATHLIB_NODEEXPIRE 0.05 -const float PATHLIB_NODEEXPIRE = 20; - -void dumpnode(entity n) -{ - n.is_path_node = false; - n.think = SUB_Remove; - n.nextthink = time; -} - -entity pathlib_mknode(vector where,entity parent) -{ - entity node; - - node = pathlib_nodeatpoint(where); - if(node) - { - #ifdef TURRET_DEBUG - mark_error(where, 60); - #endif - return node; - } - - node = spawn(); - - node.think = SUB_Remove; - node.nextthink = time + PATHLIB_NODEEXPIRE; - node.is_path_node = true; - node.owner = openlist; - node.path_prev = parent; - - - setsize(node, '0 0 0', '0 0 0'); - - setorigin(node, where); - node.medium = pointcontents(where); - pathlib_showsquare(where, 1 ,15); - - ++pathlib_made_cnt; - ++pathlib_open_cnt; - - return node; -} - -float pathlib_makenode_adaptive(entity parent,vector start, vector to, vector goal,float cost) -{ - entity node; - float h,g,f,doedge = 0; - vector where; - - ++pathlib_searched_cnt; - - if(inwater(parent.origin)) - { - LOG_TRACE("FromWater\n"); - pathlib_expandnode = pathlib_expandnode_box; - pathlib_movenode = pathlib_swimnode; - } - else - { - if(inwater(to)) - { - LOG_TRACE("ToWater\n"); - pathlib_expandnode = pathlib_expandnode_box; - pathlib_movenode = pathlib_walknode; - } - else - { - LOG_TRACE("LandToLoand\n"); - //if(edge_check(parent.origin)) - // return 0; - - pathlib_expandnode = pathlib_expandnode_star; - pathlib_movenode = pathlib_walknode; - doedge = 1; - } - } - - node = pathlib_nodeatpoint(to); - if(node) - { - LOG_TRACE("NodeAtPoint\n"); - ++pathlib_merge_cnt; - - if(node.owner == openlist) - { - h = pathlib_heuristic(node.origin,goal); - g = pathlib_cost(parent,node.origin,cost); - f = g + h; - - if(node.pathlib_node_g > g) - { - node.pathlib_node_h = h; - node.pathlib_node_g = g; - node.pathlib_node_f = f; - - node.path_prev = parent; - } - - if (!best_open_node) - best_open_node = node; - else if(best_open_node.pathlib_node_f > node.pathlib_node_f) - best_open_node = node; - } - - return 1; - } - - where = pathlib_movenode(parent.origin, to, 0); - - if (!pathlib_movenode_goodnode) - { - //pathlib_showsquare(where, 0 ,30); - //pathlib_showsquare(parent.origin, 1 ,30); - LOG_TRACE("pathlib_movenode_goodnode = 0\n"); - return 0; - } - - //pathlib_showsquare(where, 1 ,30); - - if(pathlib_nodeatpoint(where)) - { - LOG_TRACE("NAP WHERE :",vtos(where),"\n"); - LOG_TRACE("not NAP TO:",vtos(to),"\n"); - LOG_TRACE("NAP-NNAP:",ftos(vlen(to-where)),"\n\n"); - return 0; - } - - - if(doedge) - if (!tile_check(where)) - { - LOG_TRACE("tile_check fail\n"); - pathlib_showsquare(where, 0 ,30); - return 0; - } - - - h = pathlib_heuristic(where,goal); - g = pathlib_cost(parent,where,cost); - f = g + h; - - /* - node = findradius(where,pathlib_gridsize * 0.5); - while(node) - { - if(node.is_path_node == true) - { - ++pathlib_merge_cnt; - if(node.owner == openlist) - { - if(node.pathlib_node_g > g) - { - //pathlib_movenode(where,node.origin,0); - //if(pathlib_movenode_goodnode) - //{ - //mark_error(node.origin + '0 0 128',30); - node.pathlib_node_h = h; - node.pathlib_node_g = g; - node.pathlib_node_f = f; - node.path_prev = parent; - //} - } - - if (!best_open_node) - best_open_node = node; - else if(best_open_node.pathlib_node_f > node.pathlib_node_f) - best_open_node = node; - } - - return 1; - } - node = node.chain; - } - */ - - node = pathlib_mknode(where, parent); - node.pathlib_node_h = h; - node.pathlib_node_g = g; - node.pathlib_node_f = f; - - if (!best_open_node) - best_open_node = node; - else if(best_open_node.pathlib_node_f > node.pathlib_node_f) - best_open_node = node; - - return 1; -} - -entity pathlib_getbestopen() -{ - entity node; - entity bestnode; - - if(best_open_node) - { - ++pathlib_bestcash_hits; - pathlib_bestcash_saved += pathlib_open_cnt; - - return best_open_node; - } - - node = findchainentity(owner,openlist); - if(!node) - return world; - - bestnode = node; - while(node) - { - ++pathlib_bestopen_seached; - if(node.pathlib_node_f < bestnode.pathlib_node_f) - bestnode = node; - - node = node.chain; - } - - return bestnode; -} - -void pathlib_close_node(entity node,vector goal) -{ - - if(node.owner == closedlist) - { - LOG_TRACE("Pathlib: Tried to close a closed node!\n"); - return; - } - - if(node == best_open_node) - best_open_node = world; - - ++pathlib_closed_cnt; - --pathlib_open_cnt; - - node.owner = closedlist; - - if(vlen(node.origin - goal) <= pathlib_gridsize) - { - vector goalmove; - - goalmove = pathlib_walknode(node.origin,goal,1); - if(pathlib_movenode_goodnode) - { - goal_node = node; - pathlib_foundgoal = true; - } - } -} - -void pathlib_cleanup() -{ - best_open_node = world; - - //return; - - entity node; - - node = findfloat(world,is_path_node, true); - while(node) - { - /* - node.owner = openlist; - node.pathlib_node_g = 0; - node.pathlib_node_h = 0; - node.pathlib_node_f = 0; - node.path_prev = world; - */ - - dumpnode(node); - node = findfloat(node,is_path_node, true); - } - - if(openlist) - remove(openlist); - - if(closedlist) - remove(closedlist); - - openlist = world; - closedlist = world; - -} - -float Cosine_Interpolate(float a, float b, float x) -{ - float ft,f; - - ft = x * 3.1415927; - f = (1 - cos(ft)) * 0.5; - - return a*(1-f) + b*f; -} - -float buildpath_nodefilter_directional(vector n,vector c,vector p) -{ - vector d1,d2; - - d2 = normalize(p - c); - d1 = normalize(c - n); - - if(vlen(d1-d2) < 0.25) - { - //mark_error(c,30); - return 1; - } - //mark_info(c,30); - return 0; -} - -float buildpath_nodefilter_moveskip(vector n,vector c,vector p) -{ - pathlib_walknode(p,n,1); - if(pathlib_movenode_goodnode) - return 1; - - return 0; -} - -float buildpath_nodefilter_none(vector n,vector c,vector p) -{ - return 0; -} - -entity path_build(entity next, vector where, entity prev, entity start) -{ - entity path; - - if(prev && next) - if(buildpath_nodefilter) - if(buildpath_nodefilter(next.origin,where,prev.origin)) - return next; - - - path = spawn(); - path.owner = start; - path.path_next = next; - - setorigin(path,where); - - if(!next) - path.classname = "path_end"; - else - { - if(!prev) - path.classname = "path_start"; - else - path.classname = "path_node"; - } - - return path; -} - -entity pathlib_astar(vector from,vector to) -{SELFPARAM(); - entity path, start, end, open, n, ln; - float ptime, ftime, ctime; - - ptime = gettime(GETTIME_REALTIME); - pathlib_starttime = ptime; - - pathlib_cleanup(); - - // Select water<->land capable node make/link - pathlib_makenode = pathlib_makenode_adaptive; - - // Select XYZ cost estimate - //pathlib_heuristic = pathlib_h_diagonal3; - pathlib_heuristic = pathlib_h_diagonal; - - // Select distance + waterfactor cost - pathlib_cost = pathlib_g_euclidean_water; - - // Select star expander - pathlib_expandnode = pathlib_expandnode_star; - - // Select walk simulation movement test - pathlib_movenode = pathlib_walknode; - - // Filter final nodes by direction - buildpath_nodefilter = buildpath_nodefilter_directional; - - // Filter tiles with cross pattern - tile_check = tile_check_cross; - - // If the start is in water we need diffrent settings - if(inwater(from)) - { - // Select volumetric node expaner - pathlib_expandnode = pathlib_expandnode_box; - - // Water movement test - pathlib_movenode = pathlib_swimnode; - } - - if (!openlist) - openlist = spawn(); - - if (!closedlist) - closedlist = spawn(); - - pathlib_closed_cnt = 0; - pathlib_open_cnt = 0; - pathlib_made_cnt = 0; - pathlib_merge_cnt = 0; - pathlib_searched_cnt = 0; - pathlib_bestopen_seached = 0; - pathlib_bestcash_hits = 0; - pathlib_bestcash_saved = 0; - - pathlib_gridsize = 128; - pathlib_movecost = pathlib_gridsize; - pathlib_movecost_diag = vlen(('1 1 0' * pathlib_gridsize)); - pathlib_movecost_waterfactor = 25000000; - pathlib_foundgoal = 0; - - movenode_boxmax = self.maxs * 1.1; - movenode_boxmin = self.mins * 1.1; - - movenode_stepsize = pathlib_gridsize * 0.25; - - tile_check_size = pathlib_gridsize * 0.5; - tile_check_up = '0 0 2' * pathlib_gridsize; - tile_check_up = '0 0 128'; - tile_check_down = '0 0 3' * pathlib_gridsize; - tile_check_down = '0 0 256'; - - //movenode_stepup = '0 0 128'; - movenode_stepup = '0 0 36'; - movenode_maxdrop = '0 0 512'; - //movenode_maxdrop = '0 0 512'; - movenode_boxup = '0 0 72'; - - from.x = fsnap(from.x, pathlib_gridsize); - from.y = fsnap(from.y, pathlib_gridsize); - //from_z += 32; - - to.x = fsnap(to.x, pathlib_gridsize); - to.y = fsnap(to.y, pathlib_gridsize); - //to_z += 32; - - LOG_TRACE("AStar init\n"); - path = pathlib_mknode(from, world); - pathlib_close_node(path, to); - if(pathlib_foundgoal) - { - LOG_TRACE("AStar: Goal found on first node!\n"); - - open = spawn(); - open.owner = open; - open.classname = "path_end"; - setorigin(open,path.origin); - - pathlib_cleanup(); - - return open; - } - - if(pathlib_expandnode(path, from, to) <= 0) - { - LOG_TRACE("AStar path fail.\n"); - pathlib_cleanup(); - - return world; - } - - best_open_node = pathlib_getbestopen(); - n = best_open_node; - pathlib_close_node(best_open_node, to); - if(inwater(n.origin)) - pathlib_expandnode_box(n, from, to); - else - pathlib_expandnode_star(n, from, to); - - while(pathlib_open_cnt) - { - if((gettime(GETTIME_REALTIME) - pathlib_starttime) > pathlib_maxtime) - { - LOG_TRACE("Path took to long to compute!\n"); - LOG_TRACE("Nodes - created: ", ftos(pathlib_made_cnt),"\n"); - LOG_TRACE("Nodes - open: ", ftos(pathlib_open_cnt),"\n"); - LOG_TRACE("Nodes - merged: ", ftos(pathlib_merge_cnt),"\n"); - LOG_TRACE("Nodes - closed: ", ftos(pathlib_closed_cnt),"\n"); - - pathlib_cleanup(); - return world; - } - - best_open_node = pathlib_getbestopen(); - n = best_open_node; - pathlib_close_node(best_open_node,to); - - if(inwater(n.origin)) - pathlib_expandnode_box(n,from,to); - else - pathlib_expandnode(n,from,to); - - if(pathlib_foundgoal) - { - LOG_TRACE("Target found. Rebuilding and filtering path...\n"); - ftime = gettime(GETTIME_REALTIME); - ptime = ftime - ptime; - - start = path_build(world,path.origin,world,world); - end = path_build(world,goal_node.origin,world,start); - ln = end; - - open = goal_node; - for(open = goal_node; open.path_prev != path; open = open.path_prev) - { - n = path_build(ln,open.origin,open.path_prev,start); - ln.path_prev = n; - ln = n; - } - start.path_next = n; - n.path_prev = start; - ftime = gettime(GETTIME_REALTIME) - ftime; - - ctime = gettime(GETTIME_REALTIME); - pathlib_cleanup(); - ctime = gettime(GETTIME_REALTIME) - ctime; - - -#ifdef DEBUGPATHING - pathlib_showpath2(start); - - LOG_TRACE("Time used - pathfinding: ", ftos(ptime),"\n"); - LOG_TRACE("Time used - rebuild & filter: ", ftos(ftime),"\n"); - LOG_TRACE("Time used - cleanup: ", ftos(ctime),"\n"); - LOG_TRACE("Time used - total: ", ftos(ptime + ftime + ctime),"\n"); - LOG_TRACE("Time used - # frames: ", ftos(ceil((ptime + ftime + ctime) / sys_frametime)),"\n\n"); - LOG_TRACE("Nodes - created: ", ftos(pathlib_made_cnt),"\n"); - LOG_TRACE("Nodes - open: ", ftos(pathlib_open_cnt),"\n"); - LOG_TRACE("Nodes - merged: ", ftos(pathlib_merge_cnt),"\n"); - LOG_TRACE("Nodes - closed: ", ftos(pathlib_closed_cnt),"\n"); - LOG_TRACE("Nodes - searched: ", ftos(pathlib_searched_cnt),"\n"); - LOG_TRACE("Nodes bestopen searched: ", ftos(pathlib_bestopen_seached),"\n"); - LOG_TRACE("Nodes bestcash - hits: ", ftos(pathlib_bestcash_hits),"\n"); - LOG_TRACE("Nodes bestcash - save: ", ftos(pathlib_bestcash_saved),"\n"); - LOG_TRACE("AStar done.\n"); -#endif - return start; - } - } - - LOG_TRACE("A* Faild to find a path! Try a smaller gridsize.\n"); - - pathlib_cleanup(); - - return world; -} diff --git a/qcsrc/server/bot/lib/pathlib/main.qh b/qcsrc/server/bot/lib/pathlib/main.qh deleted file mode 100644 index 177c432cf3..0000000000 --- a/qcsrc/server/bot/lib/pathlib/main.qh +++ /dev/null @@ -1,5 +0,0 @@ -#ifndef PATHLIB_MAIN_H -#define PATHLIB_MAIN_H -float buildpath_nodefilter_none(vector n,vector c,vector p); -entity path_build(entity next, vector where, entity prev, entity start); -#endif diff --git a/qcsrc/server/bot/lib/pathlib/movenode.qc b/qcsrc/server/bot/lib/pathlib/movenode.qc deleted file mode 100644 index 5326a746d0..0000000000 --- a/qcsrc/server/bot/lib/pathlib/movenode.qc +++ /dev/null @@ -1,152 +0,0 @@ -#include "pathlib.qh" -#include "utility.qh" - -vector pathlib_wateroutnode(vector start,vector end, float doedge) -{SELFPARAM(); - vector surface; - - pathlib_movenode_goodnode = 0; - - end.x = fsnap(end.x, pathlib_gridsize); - end.y = fsnap(end.y, pathlib_gridsize); - - traceline(end + ('0 0 0.25' * pathlib_gridsize),end - ('0 0 1' * pathlib_gridsize),MOVE_WORLDONLY,self); - end = trace_endpos; - - if (!(pointcontents(end - '0 0 1') == CONTENT_SOLID)) - return end; - - for(surface = start ; surface.z < (end.z + 32); ++surface.z) - { - if(pointcontents(surface) == CONTENT_EMPTY) - break; - } - - if(pointcontents(surface + '0 0 1') != CONTENT_EMPTY) - return end; - - tracebox(start + '0 0 64', movenode_boxmin,movenode_boxmax, end + '0 0 64', MOVE_WORLDONLY, self); - if(trace_fraction == 1) - pathlib_movenode_goodnode = 1; - - if(fabs(surface.z - end.z) > 32) - pathlib_movenode_goodnode = 0; - - return end; -} - -vector pathlib_swimnode(vector start,vector end, float doedge) -{SELFPARAM(); - pathlib_movenode_goodnode = 0; - - if(pointcontents(start) != CONTENT_WATER) - return end; - - end.x = fsnap(end.x, pathlib_gridsize); - end.y = fsnap(end.y, pathlib_gridsize); - - if(pointcontents(end) == CONTENT_EMPTY) - return pathlib_wateroutnode( start, end, doedge); - - tracebox(start, movenode_boxmin,movenode_boxmax, end, MOVE_WORLDONLY, self); - if(trace_fraction == 1) - pathlib_movenode_goodnode = 1; - - return end; -} - -vector pathlib_flynode(vector start,vector end, float doedge) -{SELFPARAM(); - pathlib_movenode_goodnode = 0; - - end.x = fsnap(end.x, pathlib_gridsize); - end.y = fsnap(end.y, pathlib_gridsize); - - tracebox(start, movenode_boxmin,movenode_boxmax, end, MOVE_WORLDONLY, self); - if(trace_fraction == 1) - pathlib_movenode_goodnode = 1; - - return end; -} - -void a_think() -{SELFPARAM(); - te_lightning1(self,self.origin, self.pos1); - if(self.cnt < time) - remove(self); - else - self.nextthink = time + 0.2; -} - -vector pathlib_walknode(vector start,vector end,float doedge) -{SELFPARAM(); - vector direction,point,last_point,s,e; - float steps, distance, i; - - LOG_TRACE("Walking node from ", vtos(start), " to ", vtos(end), "\n"); - - pathlib_movenode_goodnode = 0; - - end.x = fsnap(end.x,pathlib_gridsize); - end.y = fsnap(end.y,pathlib_gridsize); - start.x = fsnap(start.x,pathlib_gridsize); - start.y = fsnap(start.y,pathlib_gridsize); - - // Find the floor - traceline(start + movenode_stepup, start - movenode_maxdrop, MOVE_WORLDONLY, self); - if(trace_fraction == 1.0) - { - entity a; - a = spawn(); - a.think = a_think; - a.nextthink = time; - setorigin(a,start + movenode_stepup); - a.pos1 = trace_endpos; - //start - movenode_maxdrop - a.cnt = time + 10; - - LOG_TRACE("I cant walk on air!\n"); - return trace_endpos; - } - - start = trace_endpos; - - // Find the direcion, without Z - s = start; e = end; - //e_z = 0; s_z = 0; - direction = normalize(e - s); - - distance = vlen(start - end); - steps = rint(distance / movenode_stepsize); - - last_point = start; - for(i = 1; i < steps; ++i) - { - point = last_point + (direction * movenode_stepsize); - traceline(point + movenode_stepup,point - movenode_maxdrop,MOVE_WORLDONLY,self); - if(trace_fraction == 1.0) - return trace_endpos; - - last_point = trace_endpos; - } - - point = last_point + (direction * movenode_stepsize); - point.x = fsnap(point.x,pathlib_gridsize); - point.y = fsnap(point.y,pathlib_gridsize); - - //dprint("end_x: ",ftos(end_x), " end_y: ",ftos(end_y),"\n"); - //dprint("point_x:",ftos(point_x)," point_y:",ftos(point_y),"\n\n"); - - traceline(point + movenode_stepup, point - movenode_maxdrop,MOVE_WORLDONLY,self); - if(trace_fraction == 1.0) - return trace_endpos; - - last_point = trace_endpos; - - tracebox(start + movenode_boxup, movenode_boxmin,movenode_boxmax, last_point + movenode_boxup, MOVE_WORLDONLY, self); - if(trace_fraction != 1.0) - return trace_endpos; - - pathlib_movenode_goodnode = 1; - return last_point; -} diff --git a/qcsrc/server/bot/lib/pathlib/path_waypoint.qc b/qcsrc/server/bot/lib/pathlib/path_waypoint.qc deleted file mode 100644 index 615840d51b..0000000000 --- a/qcsrc/server/bot/lib/pathlib/path_waypoint.qc +++ /dev/null @@ -1,248 +0,0 @@ -#include "pathlib.qh" -#include "main.qh" - -var float pathlib_wpp_open(entity wp, entity child, float cost); - -void pathlib_wpp_close(entity wp) -{ - --pathlib_open_cnt; - ++pathlib_closed_cnt; - - wp.pathlib_list = closedlist; - - if(wp == best_open_node) - best_open_node = world; - - if(wp == goal_node) - pathlib_foundgoal = true; -} - -float pathlib_wpp_opencb(entity wp, entity child, float cost) -{ - - if(child.pathlib_list == closedlist) - return false; - - // FIXME! wp.wp##mincost is NOT distance. Make it distance or add a field for distance to be used here (for better speed) - cost = vlen(child.origin - wp.origin); - - child.path_prev = wp; - child.pathlib_list = openlist; - child.pathlib_node_g = wp.pathlib_node_g + cost; - child.pathlib_node_h = pathlib_heuristic(child.origin, goal_node.origin); - child.pathlib_node_c = pathlib_wpp_waypointcallback(child, wp); - child.pathlib_node_f = child.pathlib_node_g + child.pathlib_node_h + child.pathlib_node_c; - - - if(child == goal_node) - pathlib_foundgoal = true; - - ++pathlib_open_cnt; - - if(best_open_node.pathlib_node_f > child.pathlib_node_f) - best_open_node = child; - - return true; -} - -float pathlib_wpp_openncb(entity wp, entity child, float cost) -{ - - if(child.pathlib_list == closedlist) - return false; - - // FIXME! wp.wp##mincost is NOT distance. Make it distance or add a field for distance to be used here (for better speed) - cost = vlen(child.origin - wp.origin); - - child.path_prev = wp; - child.pathlib_list = openlist; - child.pathlib_node_g = wp.pathlib_node_g + cost; - child.pathlib_node_h = pathlib_heuristic(child.origin, goal_node.origin); - child.pathlib_node_f = child.pathlib_node_g + child.pathlib_node_h; - - if(child == goal_node) - pathlib_foundgoal = true; - - ++pathlib_open_cnt; - - if(best_open_node.pathlib_node_f > child.pathlib_node_f) - best_open_node = child; - - return true; -} - -float pathlib_wpp_expand(entity wp) -{ - if(wp.wp00) pathlib_wpp_open(wp,wp.wp00,wp.wp00mincost); else return 0; - if(wp.wp01) pathlib_wpp_open(wp,wp.wp01,wp.wp01mincost); else return 1; - if(wp.wp02) pathlib_wpp_open(wp,wp.wp02,wp.wp02mincost); else return 2; - if(wp.wp03) pathlib_wpp_open(wp,wp.wp03,wp.wp03mincost); else return 3; - if(wp.wp04) pathlib_wpp_open(wp,wp.wp04,wp.wp04mincost); else return 4; - if(wp.wp05) pathlib_wpp_open(wp,wp.wp05,wp.wp05mincost); else return 5; - if(wp.wp06) pathlib_wpp_open(wp,wp.wp06,wp.wp06mincost); else return 6; - if(wp.wp07) pathlib_wpp_open(wp,wp.wp07,wp.wp07mincost); else return 7; - if(wp.wp08) pathlib_wpp_open(wp,wp.wp08,wp.wp08mincost); else return 8; - if(wp.wp09) pathlib_wpp_open(wp,wp.wp09,wp.wp09mincost); else return 9; - if(wp.wp10) pathlib_wpp_open(wp,wp.wp10,wp.wp10mincost); else return 10; - if(wp.wp11) pathlib_wpp_open(wp,wp.wp11,wp.wp11mincost); else return 11; - if(wp.wp12) pathlib_wpp_open(wp,wp.wp12,wp.wp12mincost); else return 12; - if(wp.wp13) pathlib_wpp_open(wp,wp.wp13,wp.wp13mincost); else return 13; - if(wp.wp14) pathlib_wpp_open(wp,wp.wp14,wp.wp14mincost); else return 14; - if(wp.wp15) pathlib_wpp_open(wp,wp.wp15,wp.wp15mincost); else return 15; - if(wp.wp16) pathlib_wpp_open(wp,wp.wp16,wp.wp16mincost); else return 16; - if(wp.wp17) pathlib_wpp_open(wp,wp.wp17,wp.wp17mincost); else return 17; - if(wp.wp18) pathlib_wpp_open(wp,wp.wp18,wp.wp18mincost); else return 18; - if(wp.wp19) pathlib_wpp_open(wp,wp.wp19,wp.wp19mincost); else return 19; - if(wp.wp20) pathlib_wpp_open(wp,wp.wp20,wp.wp20mincost); else return 20; - if(wp.wp21) pathlib_wpp_open(wp,wp.wp21,wp.wp21mincost); else return 21; - if(wp.wp22) pathlib_wpp_open(wp,wp.wp22,wp.wp22mincost); else return 22; - if(wp.wp23) pathlib_wpp_open(wp,wp.wp23,wp.wp23mincost); else return 23; - if(wp.wp24) pathlib_wpp_open(wp,wp.wp24,wp.wp24mincost); else return 24; - if(wp.wp25) pathlib_wpp_open(wp,wp.wp25,wp.wp25mincost); else return 25; - if(wp.wp26) pathlib_wpp_open(wp,wp.wp26,wp.wp26mincost); else return 26; - if(wp.wp27) pathlib_wpp_open(wp,wp.wp27,wp.wp27mincost); else return 27; - if(wp.wp28) pathlib_wpp_open(wp,wp.wp28,wp.wp28mincost); else return 28; - if(wp.wp29) pathlib_wpp_open(wp,wp.wp29,wp.wp29mincost); else return 29; - if(wp.wp30) pathlib_wpp_open(wp,wp.wp30,wp.wp30mincost); else return 30; - if(wp.wp31) pathlib_wpp_open(wp,wp.wp31,wp.wp31mincost); else return 31; - - return 32; -} - -entity pathlib_wpp_bestopen() -{ - entity n, best; - - if(best_open_node) - return best_open_node; - - n = findchainentity(pathlib_list, openlist); - best = n; - while(n) - { - if(n.pathlib_node_f < best.pathlib_node_f) - best = n; - - n = n.chain; - } - - return best; - -} - -entity pathlib_waypointpath(entity wp_from, entity wp_to, float callback) -{ - entity n; - float ptime; - - ptime = gettime(GETTIME_REALTIME); - pathlib_starttime = ptime; - pathlib_movecost = 300; - pathlib_movecost_diag = vlen('1 1 0' * pathlib_movecost); - - if (!pathlib_wpp_waypointcallback) - callback = false; - - if (callback) - pathlib_wpp_open = pathlib_wpp_opencb; - else - pathlib_wpp_open = pathlib_wpp_openncb; - - pathlib_heuristic = pathlib_h_none; - - if (!openlist) - openlist = spawn(); - - if (!closedlist) - closedlist = spawn(); - - pathlib_closed_cnt = 0; - pathlib_open_cnt = 0; - pathlib_searched_cnt = 0; - pathlib_foundgoal = false; - - LOG_TRACE("pathlib_waypointpath init\n"); - - // Initialize waypoint grid - // FIXME! presisted chain for better preformance - for(n = findchain(classname, "waypoint"); n; n = n.chain) - { - n.pathlib_list = world; - n.pathlib_node_g = 0; - n.pathlib_node_f = 0; - n.pathlib_node_h = 0; - - //setmodel(n, "models/runematch/rune.mdl"); - //n.effects = EF_LOWPRECISION; - //n.colormod = '0 0 0'; - //n.scale = 1; - - } - - goal_node = wp_to; - start_node = wp_from; - - start_node.pathlib_list = closedlist; - LOG_TRACE("Expanding ",ftos(pathlib_wpp_expand(start_node))," links\n"); - if(pathlib_open_cnt <= 0) - { - LOG_TRACE("pathlib_waypointpath: Start waypoint not linked! aborting.\n"); - return world; - } - - return world; -} - -entity pathlib_waypointpath_step() -{ - entity n; - - n = pathlib_wpp_bestopen(); - if(!n) - { - LOG_TRACE("Cannot find best open node, abort.\n"); - return world; - } - pathlib_wpp_close(n); - LOG_TRACE("Expanding ",ftos(pathlib_wpp_expand(n))," links\n"); - - if(pathlib_foundgoal) - { - entity start, end, open, ln; - - LOG_TRACE("Target found. Rebuilding and filtering path...\n"); - - buildpath_nodefilter = buildpath_nodefilter_none; - start = path_build(world, start_node.origin, world, world); - end = path_build(world, goal_node.origin, world, start); - ln = end; - - for(open = goal_node; open.path_prev != start_node; open = open.path_prev) - { - n = path_build(ln,open.origin,open.path_prev,start); - ln.path_prev = n; - ln = n; - } - start.path_next = n; - n.path_prev = start; - - return start; - } - - return world; -} -void plas_think() -{SELFPARAM(); - pathlib_waypointpath_step(); - if(pathlib_foundgoal) - return; - self.nextthink = time + 0.1; -} - -void pathlib_waypointpath_autostep() -{ - entity n; - n = spawn(); - n.think = plas_think; - n.nextthink = time + 0.1; -} diff --git a/qcsrc/server/bot/lib/pathlib/pathlib.qh b/qcsrc/server/bot/lib/pathlib/pathlib.qh deleted file mode 100644 index dbf785266a..0000000000 --- a/qcsrc/server/bot/lib/pathlib/pathlib.qh +++ /dev/null @@ -1,116 +0,0 @@ -#ifndef PATHLIB_H -#define PATHLIB_H - -.entity pathlib_list; -.entity path_next; -.entity path_prev; - -#define inwater(point) (pointcontents(point) == CONTENT_WATER) -#define medium spawnshieldtime - -const vector PLIB_FORWARD = '0 1 0'; -//#define PLIB_BACK '0 -1 0' -const vector PLIB_RIGHT = '1 0 0'; -//#define PLIB_LEFT '-1 0 0' - -#define DEBUGPATHING -#ifdef DEBUGPATHING -void pathlib_showpath(entity start); -void pathlib_showpath2(entity path); -#endif - -entity openlist; -entity closedlist; - -entity goal_node; -entity start_node; - -.float is_path_node; -.float pathlib_node_g; -.float pathlib_node_h; -.float pathlib_node_f; -.float pathlib_node_c; - -const float pathlib_node_edgeflag_unknown = 0; -const float pathlib_node_edgeflag_left = 2; -const float pathlib_node_edgeflag_right = 4; -const float pathlib_node_edgeflag_forward = 8; -const float pathlib_node_edgeflag_back = 16; -const float pathlib_node_edgeflag_backleft = 32; -const float pathlib_node_edgeflag_backright = 64; -const float pathlib_node_edgeflag_forwardleft = 128; -const float pathlib_node_edgeflag_forwardright = 256; -const float pathlib_node_edgeflag_none = 512; -.float pathlib_node_edgeflags; - -float pathlib_open_cnt; -float pathlib_closed_cnt; -float pathlib_made_cnt; -float pathlib_merge_cnt; -float pathlib_searched_cnt; -float pathlib_bestopen_seached; -float pathlib_bestcash_hits; -float pathlib_bestcash_saved; -float pathlib_gridsize; -float pathlib_movecost; -float pathlib_movecost_diag; -float pathlib_movecost_waterfactor; -float pathlib_foundgoal; - -float pathlib_starttime; -const float pathlib_maxtime = 60; - -entity best_open_node; - -vector tile_check_up; -vector tile_check_down; -float tile_check_size; -float tile_check_cross(vector where); -float tile_check_plus(vector where); -float tile_check_star(vector where); -var float tile_check(vector where); - -float movenode_stepsize; -vector movenode_stepup; -vector movenode_maxdrop; -vector movenode_boxup; -vector movenode_boxmax; -vector movenode_boxmin; -float pathlib_movenode_goodnode; - -vector pathlib_wateroutnode(vector start, vector end, float doedge); -vector pathlib_swimnode(vector start, vector end, float doedge); -vector pathlib_flynode(vector start, vector end, float doedge); -vector pathlib_walknode(vector start, vector end, float doedge); -var vector pathlib_movenode(vector start, vector end, float doedge); - -float pathlib_expandnode_star(entity node, vector start, vector goal); -float pathlib_expandnode_box(entity node, vector start, vector goal); -float pathlib_expandnode_octagon(entity node, vector start, vector goal); -var float pathlib_expandnode(entity node, vector start, vector goal); - -float pathlib_g_static(entity parent, vector to, float static_cost); -float pathlib_g_static_water(entity parent, vector to, float static_cost); -float pathlib_g_euclidean(entity parent, vector to, float static_cost); -float pathlib_g_euclidean_water(entity parent, vector to, float static_cost); -var float pathlib_cost(entity parent, vector to, float static_cost); - -float pathlib_h_manhattan(vector a, vector b); -float pathlib_h_diagonal(vector a, vector b); -float pathlib_h_euclidean(vector a,vector b); -float pathlib_h_diagonal2(vector a, vector b); -float pathlib_h_diagonal3(vector a, vector b); -float pathlib_h_diagonal2sdp(vector preprev, vector prev, vector point, vector end); -float pathlib_h_none(vector preprev, vector prev) { return 0; } -var float pathlib_heuristic(vector from, vector to); - -var float pathlib_makenode(entity parent,vector start, vector to, vector goal,float cost); -var float buildpath_nodefilter(vector n,vector c,vector p); - -var float pathlib_wpp_waypointcallback(entity wp, entity wp_prev); - -#ifdef DEBUGPATHING - #include "debug.qc" -#endif - -#endif diff --git a/qcsrc/server/bot/lib/pathlib/utility.qc b/qcsrc/server/bot/lib/pathlib/utility.qc deleted file mode 100644 index 9028f85e4f..0000000000 --- a/qcsrc/server/bot/lib/pathlib/utility.qc +++ /dev/null @@ -1,245 +0,0 @@ -#include "utility.qh" - -#include "pathlib.qh" - -float location_isok(vector point, float water_isok, float air_isok) -{ - float pc,pc2; - - if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_SKY) - return 0; - - pc = pointcontents(point); - pc2 = pointcontents(point - '0 0 1'); - - switch(pc) - { - case CONTENT_SOLID: - break; - - case CONTENT_SLIME: - break; - - case CONTENT_LAVA: - break; - - case CONTENT_SKY: - break; - - case CONTENT_EMPTY: - if (pc2 == CONTENT_SOLID) - return 1; - - if (pc2 == CONTENT_EMPTY) - if(air_isok) - return 1; - - if (pc2 == CONTENT_WATER) - if(water_isok) - return 1; - - break; - - case CONTENT_WATER: - if (water_isok) - return 1; - - break; - } - - return 0; -} - -entity pathlib_nodeatpoint(vector where) -{ - entity node; - - ++pathlib_searched_cnt; - - where.x = fsnap(where.x,pathlib_gridsize); - where.y = fsnap(where.y,pathlib_gridsize); - - node = findradius(where,pathlib_gridsize * 0.5); - while(node) - { - if(node.is_path_node == true) - return node; - - node = node.chain; - } - - return world; -} - -float tile_check_cross(vector where) -{SELFPARAM(); - vector p,f,r; - - f = PLIB_FORWARD * tile_check_size; - r = PLIB_RIGHT * tile_check_size; - - - // forward-right - p = where + f + r; - traceline(p + tile_check_up, p - tile_check_down, MOVE_WORLDONLY, self); - if (!location_isok(trace_endpos, 1, 0)) - return 0; - - // Forward-left - p = where + f - r; - traceline(p + tile_check_up, p - tile_check_down, MOVE_WORLDONLY, self); - if (!location_isok(trace_endpos, 1, 0)) - return 0; - - // Back-right - p = where - f + r; - traceline(p + tile_check_up, p - tile_check_down, MOVE_WORLDONLY, self); - if (!location_isok(trace_endpos, 1 ,0)) - return 0; - - //Back-left - p = where - f - r; - traceline(p + tile_check_up, p - tile_check_down, MOVE_WORLDONLY, self); - if (!location_isok(trace_endpos, 1, 0)) - return 0; - - return 1; -} - -float tile_check_plus(vector where) -{SELFPARAM(); - vector p,f,r; - - f = PLIB_FORWARD * tile_check_size; - r = PLIB_RIGHT * tile_check_size; - - // forward - p = where + f; - traceline(p+tile_check_up,p-tile_check_down,MOVE_WORLDONLY,self); - if (!location_isok(trace_endpos,1,0)) - return 0; - - - //left - p = where - r; - traceline(p+tile_check_up,p-tile_check_down,MOVE_WORLDONLY,self); - if (!location_isok(trace_endpos,1,0)) - return 0; - - // Right - p = where + r; - traceline(p+tile_check_up,p-tile_check_down,MOVE_WORLDONLY,self); - if (!location_isok(trace_endpos,1,0)) - return 0; - - //Back - p = where - f; - traceline(p+tile_check_up,p-tile_check_down,MOVE_WORLDONLY,self); - if (!location_isok(trace_endpos,1,0)) - return 0; - - return 1; -} - -float tile_check_plus2(vector where) -{SELFPARAM(); - vector p,f,r; - float i = 0, e = 0; - - f = PLIB_FORWARD * pathlib_gridsize; - r = PLIB_RIGHT * pathlib_gridsize; - -//#define pathlib_node_edgeflag_left 2 -//#define pathlib_node_edgeflag_right 4 -//#define pathlib_node_edgeflag_forward 8 -//#define pathlib_node_edgeflag_back 16 - - // forward - p = where + f; - traceline(p+tile_check_up,p-tile_check_down,MOVE_WORLDONLY,self); - if (location_isok(trace_endpos,1,0)) - { - ++i; - e |= pathlib_node_edgeflag_forward; - } - - - //left - p = where - r; - traceline(p+tile_check_up,p-tile_check_down,MOVE_WORLDONLY,self); - if (location_isok(trace_endpos,1,0)) - { - ++i; - e |= pathlib_node_edgeflag_left; - } - - - // Right - p = where + r; - traceline(p+tile_check_up,p-tile_check_down,MOVE_WORLDONLY,self); - if (location_isok(trace_endpos,1,0)) - { - ++i; - e |= pathlib_node_edgeflag_right; - } - - //Back - p = where - f; - traceline(p+tile_check_up,p-tile_check_down,MOVE_WORLDONLY,self); - if (location_isok(trace_endpos,1,0)) - { - ++i; - e |= pathlib_node_edgeflag_back; - } - - // forward-right - p = where + f + r; - traceline(p + tile_check_up, p - tile_check_down, MOVE_WORLDONLY, self); - if (location_isok(trace_endpos, 1, 0)) - { - ++i; - e |= pathlib_node_edgeflag_forwardright; - } - - // Forward-left - p = where + f - r; - traceline(p + tile_check_up, p - tile_check_down, MOVE_WORLDONLY, self); - if (location_isok(trace_endpos, 1, 0)) - { - ++i; - e |= pathlib_node_edgeflag_forwardleft; - } - - // Back-right - p = where - f + r; - traceline(p + tile_check_up, p - tile_check_down, MOVE_WORLDONLY, self); - if (location_isok(trace_endpos, 1 ,0)) - { - ++i; - e |= pathlib_node_edgeflag_backright; - } - - //Back-left - p = where - f - r; - traceline(p + tile_check_up, p - tile_check_down, MOVE_WORLDONLY, self); - if (location_isok(trace_endpos, 1, 0)) - { - ++i; - e |= pathlib_node_edgeflag_backleft; - } - - - if(i == 0) - e = pathlib_node_edgeflag_none; - - return e; -} - -float tile_check_star(vector where) -{ - if(tile_check_plus(where)) - return tile_check_cross(where); - - return 0; -} - diff --git a/qcsrc/server/bot/lib/pathlib/utility.qh b/qcsrc/server/bot/lib/pathlib/utility.qh deleted file mode 100644 index bf72549a03..0000000000 --- a/qcsrc/server/bot/lib/pathlib/utility.qh +++ /dev/null @@ -1,6 +0,0 @@ -#ifndef PATHLIB_UTILITY -#define PATHLIB_UTILITY -float fsnap(float val,float fsize); -entity pathlib_nodeatpoint(vector where); -float tile_check_plus2(vector where); -#endif diff --git a/qcsrc/server/bot/lib/steerlib/all.inc b/qcsrc/server/bot/lib/steerlib/all.inc deleted file mode 100644 index 9e802b5860..0000000000 --- a/qcsrc/server/bot/lib/steerlib/all.inc +++ /dev/null @@ -1 +0,0 @@ -#include "steerlib.qc" diff --git a/qcsrc/server/bot/lib/steerlib/steerlib.qc b/qcsrc/server/bot/lib/steerlib/steerlib.qc deleted file mode 100644 index 23b847373b..0000000000 --- a/qcsrc/server/bot/lib/steerlib/steerlib.qc +++ /dev/null @@ -1,665 +0,0 @@ -/** - Uniform pull towards a point -**/ -vector steerlib_pull(vector point) -{SELFPARAM(); - return normalize(point - self.origin); -} - -/** - Uniform push from a point -**/ -#define steerlib_push(point) normalize(self.origin - point) -/* -vector steerlib_push(vector point) -{ - return normalize(self.origin - point); -} -*/ -/** - Pull toward a point, The further away, the stronger the pull. -**/ -vector steerlib_arrive(vector point,float maximal_distance) -{SELFPARAM(); - float distance; - vector direction; - - distance = bound(0.001,vlen(self.origin - point),maximal_distance); - direction = normalize(point - self.origin); - return direction * (distance / maximal_distance); -} - -/** - Pull toward a point increasing the pull the closer we get -**/ -vector steerlib_attract(vector point, float maximal_distance) -{SELFPARAM(); - float distance; - vector direction; - - distance = bound(0.001,vlen(self.origin - point),maximal_distance); - direction = normalize(point - self.origin); - - return direction * (1-(distance / maximal_distance)); -} - -vector steerlib_attract2(vector point, float min_influense,float max_distance,float max_influense) -{SELFPARAM(); - float distance; - vector direction; - float influense; - - distance = bound(0.00001,vlen(self.origin - point),max_distance); - direction = normalize(point - self.origin); - - influense = 1 - (distance / max_distance); - influense = min_influense + (influense * (max_influense - min_influense)); - - return direction * influense; -} - -/* -vector steerlib_attract2(vector point, float maximal_distance,float min_influense,float max_influense,float distance) -{ - //float distance; - vector current_direction; - vector target_direction; - float i_target,i_current; - - if(!distance) - distance = vlen(self.origin - point); - - distance = bound(0.001,distance,maximal_distance); - - target_direction = normalize(point - self.origin); - current_direction = normalize(self.velocity); - - i_target = bound(min_influense,(1-(distance / maximal_distance)),max_influense); - i_current = 1 - i_target; - - // i_target = bound(min_influense,(1-(distance / maximal_distance)),max_influense); - - string s; - s = ftos(i_target); - bprint("IT: ",s,"\n"); - s = ftos(i_current); - bprint("IC : ",s,"\n"); - - return normalize((target_direction * i_target) + (current_direction * i_current)); -} -*/ -/** - Move away from a point. -**/ -vector steerlib_repell(vector point,float maximal_distance) -{SELFPARAM(); - float distance; - vector direction; - - distance = bound(0.001,vlen(self.origin - point),maximal_distance); - direction = normalize(self.origin - point); - - return direction * (1-(distance / maximal_distance)); -} - -/** - Try to keep at ideal_distance away from point -**/ -vector steerlib_standoff(vector point,float ideal_distance) -{SELFPARAM(); - float distance; - vector direction; - - distance = vlen(self.origin - point); - - - if(distance < ideal_distance) - { - direction = normalize(self.origin - point); - return direction * (distance / ideal_distance); - } - - direction = normalize(point - self.origin); - return direction * (ideal_distance / distance); - -} - -/** - A random heading in a forward halfcicrle - - use like: - self.target = steerlib_wander(256,32,self.target) - - where range is the cicrle radius and tresh is how close we need to be to pick a new heading. -**/ -vector steerlib_wander(float range,float tresh,vector oldpoint) -{SELFPARAM(); - vector wander_point; - wander_point = v_forward - oldpoint; - - if (vlen(wander_point) > tresh) - return oldpoint; - - range = bound(0,range,1); - - wander_point = self.origin + v_forward * 128; - wander_point = wander_point + randomvec() * (range * 128) - randomvec() * (range * 128); - - return normalize(wander_point - self.origin); -} - -/** - Dodge a point. dont work to well. -**/ -vector steerlib_dodge(vector point,vector dodge_dir,float min_distance) -{SELFPARAM(); - float distance; - - distance = max(vlen(self.origin - point),min_distance); - if (min_distance < distance) - return '0 0 0'; - - return dodge_dir * (min_distance/distance); -} - -/** - flocking by .flock_id - Group will move towards the unified direction while keeping close to eachother. -**/ -.float flock_id; -vector steerlib_flock(float _radius, float standoff,float separation_force,float flock_force) -{SELFPARAM(); - entity flock_member; - vector push = '0 0 0', pull = '0 0 0'; - float ccount = 0; - - flock_member = findradius(self.origin, _radius); - while(flock_member) - { - if(flock_member != self) - if(flock_member.flock_id == self.flock_id) - { - ++ccount; - push = push + (steerlib_repell(flock_member.origin,standoff) * separation_force); - pull = pull + (steerlib_arrive(flock_member.origin + flock_member.velocity, _radius) * flock_force); - } - flock_member = flock_member.chain; - } - return push + (pull* (1 / ccount)); -} - -/** - flocking by .flock_id - Group will move towards the unified direction while keeping close to eachother. - xy only version (for ground movers). -**/ -vector steerlib_flock2d(float _radius, float standoff,float separation_force,float flock_force) -{SELFPARAM(); - entity flock_member; - vector push = '0 0 0', pull = '0 0 0'; - float ccount = 0; - - flock_member = findradius(self.origin,_radius); - while(flock_member) - { - if(flock_member != self) - if(flock_member.flock_id == self.flock_id) - { - ++ccount; - push = push + (steerlib_repell(flock_member.origin, standoff) * separation_force); - pull = pull + (steerlib_arrive(flock_member.origin + flock_member.velocity, _radius) * flock_force); - } - flock_member = flock_member.chain; - } - - push.z = 0; - pull.z = 0; - - return push + (pull * (1 / ccount)); -} - -/** - All members want to be in the center, and keep away from eachother. - The furtehr form the center the more they want to be there. - - This results in a aligned movement (?!) much like flocking. -**/ -vector steerlib_swarm(float _radius, float standoff,float separation_force,float swarm_force) -{SELFPARAM(); - entity swarm_member; - vector force = '0 0 0', center = '0 0 0'; - float ccount = 0; - - swarm_member = findradius(self.origin,_radius); - - while(swarm_member) - { - if(swarm_member.flock_id == self.flock_id) - { - ++ccount; - center = center + swarm_member.origin; - force = force + (steerlib_repell(swarm_member.origin,standoff) * separation_force); - } - swarm_member = swarm_member.chain; - } - - center = center * (1 / ccount); - force = force + (steerlib_arrive(center,_radius) * swarm_force); - - return force; -} - -/** - Steer towards the direction least obstructed. - Run four tracelines in a forward funnel, bias each diretion negative if something is found there. - You need to call makevectors() (or equivalent) before this function to set v_forward and v_right -**/ -vector steerlib_traceavoid(float pitch,float length) -{SELFPARAM(); - vector vup_left,vup_right,vdown_left,vdown_right; - float fup_left,fup_right,fdown_left,fdown_right; - vector upwish,downwish,leftwish,rightwish; - vector v_left,v_down; - - - v_left = v_right * -1; - v_down = v_up * -1; - - vup_left = (v_forward + (v_left * pitch + v_up * pitch)) * length; - traceline(self.origin, self.origin + vup_left,MOVE_NOMONSTERS,self); - fup_left = trace_fraction; - - //te_lightning1(world,self.origin, trace_endpos); - - vup_right = (v_forward + (v_right * pitch + v_up * pitch)) * length; - traceline(self.origin,self.origin + vup_right ,MOVE_NOMONSTERS,self); - fup_right = trace_fraction; - - //te_lightning1(world,self.origin, trace_endpos); - - vdown_left = (v_forward + (v_left * pitch + v_down * pitch)) * length; - traceline(self.origin,self.origin + vdown_left,MOVE_NOMONSTERS,self); - fdown_left = trace_fraction; - - //te_lightning1(world,self.origin, trace_endpos); - - vdown_right = (v_forward + (v_right * pitch + v_down * pitch)) * length; - traceline(self.origin,self.origin + vdown_right,MOVE_NOMONSTERS,self); - fdown_right = trace_fraction; - - //te_lightning1(world,self.origin, trace_endpos); - upwish = v_up * (fup_left + fup_right); - downwish = v_down * (fdown_left + fdown_right); - leftwish = v_left * (fup_left + fdown_left); - rightwish = v_right * (fup_right + fdown_right); - - return (upwish+leftwish+downwish+rightwish) * 0.25; - -} - -/** - Steer towards the direction least obstructed. - Run tracelines in a forward trident, bias each direction negative if something is found there. -**/ -vector steerlib_traceavoid_flat(float pitch, float length, vector vofs) -{SELFPARAM(); - vector vt_left, vt_right,vt_front; - float f_left, f_right,f_front; - vector leftwish, rightwish,frontwish, v_left; - - v_left = v_right * -1; - - - vt_front = v_forward * length; - traceline(self.origin + vofs, self.origin + vofs + vt_front,MOVE_NOMONSTERS,self); - f_front = trace_fraction; - - vt_left = (v_forward + (v_left * pitch)) * length; - traceline(self.origin + vofs, self.origin + vofs + vt_left,MOVE_NOMONSTERS,self); - f_left = trace_fraction; - - //te_lightning1(world,self.origin, trace_endpos); - - vt_right = (v_forward + (v_right * pitch)) * length; - traceline(self.origin + vofs, self.origin + vofs + vt_right ,MOVE_NOMONSTERS,self); - f_right = trace_fraction; - - //te_lightning1(world,self.origin, trace_endpos); - - leftwish = v_left * f_left; - rightwish = v_right * f_right; - frontwish = v_forward * f_front; - - return normalize(leftwish + rightwish + frontwish); -} - -float beamsweep_badpoint(vector point,float waterok) -{ - float pc,pc2; - - if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_SKY) - return 1; - - pc = pointcontents(point); - pc2 = pointcontents(point - '0 0 1'); - - switch(pc) - { - case CONTENT_SOLID: break; - case CONTENT_SLIME: break; - case CONTENT_LAVA: break; - - case CONTENT_SKY: - return 1; - - case CONTENT_EMPTY: - if (pc2 == CONTENT_SOLID) - return 0; - - if (pc2 == CONTENT_WATER) - if(waterok) - return 0; - - break; - - case CONTENT_WATER: - if(waterok) - return 0; - - break; - } - - return 1; -} - -//#define BEAMSTEER_VISUAL -float beamsweep(vector from, vector dir,float length, float step,float step_up, float step_down) -{SELFPARAM(); - float i; - vector a,b,u,d; - - u = '0 0 1' * step_up; - d = '0 0 1' * step_down; - - traceline(from + u, from - d,MOVE_NORMAL,self); - if(trace_fraction == 1.0) - return 0; - - if(beamsweep_badpoint(trace_endpos,0)) - return 0; - - a = trace_endpos; - for(i = 0; i < length; i += step) - { - - b = a + dir * step; - tracebox(a + u,'-4 -4 -4','4 4 4', b + u,MOVE_NORMAL,self); - if(trace_fraction != 1.0) - return i / length; - - traceline(b + u, b - d,MOVE_NORMAL,self); - if(trace_fraction == 1.0) - return i / length; - - if(beamsweep_badpoint(trace_endpos,0)) - return i / length; -#ifdef BEAMSTEER_VISUAL - te_lightning1(world,a+u,b+u); - te_lightning1(world,b+u,b-d); -#endif - a = trace_endpos; - } - - return 1; -} - -vector steerlib_beamsteer(vector dir, float length, float step, float step_up, float step_down) -{SELFPARAM(); - float bm_forward, bm_right, bm_left,p; - vector vr,vl; - - dir.z *= 0.15; - vr = vectoangles(dir); - //vr_x *= -1; - - tracebox(self.origin + '0 0 1' * step_up, self.mins, self.maxs, ('0 0 1' * step_up) + self.origin + (dir * length), MOVE_NOMONSTERS, self); - if(trace_fraction == 1.0) - { - //te_lightning1(self,self.origin,self.origin + (dir * length)); - return dir; - } - - - - - makevectors(vr); - bm_forward = beamsweep(self.origin, v_forward, length, step, step_up, step_down); - - vr = normalize(v_forward + v_right * 0.125); - vl = normalize(v_forward - v_right * 0.125); - - bm_right = beamsweep(self.origin, vr, length, step, step_up, step_down); - bm_left = beamsweep(self.origin, vl, length, step, step_up, step_down); - - - p = bm_left + bm_right; - if(p == 2) - { - //te_lightning1(self,self.origin + '0 0 32',self.origin + '0 0 32' + vr * length); - //te_lightning1(self.tur_head,self.origin + '0 0 32',self.origin + '0 0 32' + vl * length); - - return v_forward; - } - - p = 2 - p; - - vr = normalize(v_forward + v_right * p); - vl = normalize(v_forward - v_right * p); - bm_right = beamsweep(self.origin, vr, length, step, step_up, step_down); - bm_left = beamsweep(self.origin, vl, length, step, step_up, step_down); - - - if(bm_left + bm_right < 0.15) - { - vr = normalize((v_forward*-1) + v_right * 0.75); - vl = normalize((v_forward*-1) - v_right * 0.75); - - bm_right = beamsweep(self.origin, vr, length, step, step_up, step_down); - bm_left = beamsweep(self.origin, vl, length, step, step_up, step_down); - } - - //te_lightning1(self,self.origin + '0 0 32',self.origin + '0 0 32' + vr * length); - //te_lightning1(self.tur_head,self.origin + '0 0 32',self.origin + '0 0 32' + vl * length); - - bm_forward *= bm_forward; - bm_right *= bm_right; - bm_left *= bm_left; - - vr = vr * bm_right; - vl = vl * bm_left; - - return normalize(vr + vl); - -} - - -////////////////////////////////////////////// -// Testting // -// Everything below this point is a mess :D // -////////////////////////////////////////////// -//#define TLIBS_TETSLIBS -#ifdef TLIBS_TETSLIBS -void flocker_die() -{SELFPARAM(); - Send_Effect(EFFECT_ROCKET_EXPLODE, self.origin, '0 0 0', 1); - - self.owner.cnt += 1; - self.owner = world; - - self.nextthink = time; - self.think = SUB_Remove; -} - - -void flocker_think() -{SELFPARAM(); - vector dodgemove,swarmmove; - vector reprellmove,wandermove,newmove; - - self.angles_x = self.angles.x * -1; - makevectors(self.angles); - self.angles_x = self.angles.x * -1; - - dodgemove = steerlib_traceavoid(0.35,1000); - swarmmove = steerlib_flock(500,75,700,500); - reprellmove = steerlib_repell(self.owner.enemy.origin+self.enemy.velocity,2000) * 700; - - if(vlen(dodgemove) == 0) - { - self.pos1 = steerlib_wander(0.5,0.1,self.pos1); - wandermove = self.pos1 * 50; - } - else - self.pos1 = normalize(self.velocity); - - dodgemove = dodgemove * vlen(self.velocity) * 5; - - newmove = swarmmove + reprellmove + wandermove + dodgemove; - self.velocity = movelib_inertmove_byspeed(newmove,300,0.2,0.9); - //self.velocity = movelib_inertmove(dodgemove,0.65); - - self.velocity = movelib_dragvec(0.01,0.6); - - self.angles = vectoangles(self.velocity); - - if(self.health <= 0) - flocker_die(); - else - self.nextthink = time + 0.1; -} - -MODEL(FLOCKER, "models/turrets/rocket.md3"); - -void spawn_flocker() -{SELFPARAM(); - entity flocker; - - flocker = spawn (); - - setorigin(flocker, self.origin + '0 0 32'); - setmodel (flocker, MDL_FLOCKER); - setsize (flocker, '-3 -3 -3', '3 3 3'); - - flocker.flock_id = self.flock_id; - flocker.classname = "flocker"; - flocker.owner = self; - flocker.think = flocker_think; - flocker.nextthink = time + random() * 5; - PROJECTILE_MAKETRIGGER(flocker); - flocker.movetype = MOVETYPE_BOUNCEMISSILE; - flocker.effects = EF_LOWPRECISION; - flocker.velocity = randomvec() * 300; - flocker.angles = vectoangles(flocker.velocity); - flocker.health = 10; - flocker.pos1 = normalize(flocker.velocity + randomvec() * 0.1); - - self.cnt = self.cnt -1; - -} - -void flockerspawn_think() -{SELFPARAM(); - - - if(self.cnt > 0) - spawn_flocker(); - - self.nextthink = time + self.delay; - -} - -void flocker_hunter_think() -{SELFPARAM(); - vector dodgemove,attractmove,newmove; - entity e,ee; - float d,bd; - - self.angles_x = self.angles.x * -1; - makevectors(self.angles); - self.angles_x = self.angles.x * -1; - - if(self.enemy) - if(vlen(self.enemy.origin - self.origin) < 64) - { - ee = self.enemy; - ee.health = -1; - self.enemy = world; - - } - - if(!self.enemy) - { - e = findchainfloat(flock_id,self.flock_id); - while(e) - { - d = vlen(self.origin - e.origin); - - if(e != self.owner) - if(e != ee) - if(d > bd) - { - self.enemy = e; - bd = d; - } - e = e.chain; - } - } - - if(self.enemy) - attractmove = steerlib_attract(self.enemy.origin+self.enemy.velocity * 0.1,5000) * 1250; - else - attractmove = normalize(self.velocity) * 200; - - dodgemove = steerlib_traceavoid(0.35,1500) * vlen(self.velocity); - - newmove = dodgemove + attractmove; - self.velocity = movelib_inertmove_byspeed(newmove,1250,0.3,0.7); - self.velocity = movelib_dragvec(0.01,0.5); - - - self.angles = vectoangles(self.velocity); - self.nextthink = time + 0.1; -} - - -float globflockcnt; -spawnfunc(flockerspawn) -{SELFPARAM(); - ++globflockcnt; - - if(!self.cnt) self.cnt = 20; - if(!self.delay) self.delay = 0.25; - if(!self.flock_id) self.flock_id = globflockcnt; - - self.think = flockerspawn_think; - self.nextthink = time + 0.25; - - self.enemy = spawn(); - - setmodel(self.enemy, MDL_FLOCKER); - setorigin(self.enemy,self.origin + '0 0 768' + (randomvec() * 128)); - - self.enemy.classname = "FLock Hunter"; - self.enemy.scale = 3; - self.enemy.effects = EF_LOWPRECISION; - self.enemy.movetype = MOVETYPE_BOUNCEMISSILE; - PROJECTILE_MAKETRIGGER(self.enemy); - self.enemy.think = flocker_hunter_think; - self.enemy.nextthink = time + 10; - self.enemy.flock_id = self.flock_id; - self.enemy.owner = self; -} -#endif - - - diff --git a/qcsrc/server/bot/lib/steerlib/steerlib.qh b/qcsrc/server/bot/lib/steerlib/steerlib.qh deleted file mode 100644 index fcd35ba783..0000000000 --- a/qcsrc/server/bot/lib/steerlib/steerlib.qh +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef STEERLIB_H -#define STEERLIB_H - -.vector steerto; - -vector steerlib_arrive(vector point,float maximal_distance); -vector steerlib_attract2(vector point, float min_influense,float max_distance,float max_influense); -vector steerlib_pull(vector point); - -#endif diff --git a/qcsrc/server/bot/navigation.qc b/qcsrc/server/bot/navigation.qc new file mode 100644 index 0000000000..99e3901b2b --- /dev/null +++ b/qcsrc/server/bot/navigation.qc @@ -0,0 +1,1266 @@ +#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 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++; +} diff --git a/qcsrc/server/bot/navigation.qh b/qcsrc/server/bot/navigation.qh new file mode 100644 index 0000000000..cf4a5ce5b2 --- /dev/null +++ b/qcsrc/server/bot/navigation.qh @@ -0,0 +1,80 @@ +#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 diff --git a/qcsrc/server/bot/null/impl.qc b/qcsrc/server/bot/null/impl.qc deleted file mode 100644 index ea35b7fbc5..0000000000 --- a/qcsrc/server/bot/null/impl.qc +++ /dev/null @@ -1,39 +0,0 @@ -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; } diff --git a/qcsrc/server/bot/scripting.qc b/qcsrc/server/bot/scripting.qc new file mode 100644 index 0000000000..72951cd662 --- /dev/null +++ b/qcsrc/server/bot/scripting.qc @@ -0,0 +1,1347 @@ +#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 \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 .. \n"); + LOG_INFO(" sv_cmd .. \n"); + LOG_INFO(" sv_cmd .. else\n"); + LOG_INFO(" sv_cmd .. \n"); + LOG_INFO(" sv_cmd .. \n"); + LOG_INFO(" sv_cmd .. fi\n"); + LOG_INFO("Conditions: a=b, a>b, a50; 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 \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)=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; +} diff --git a/qcsrc/server/bot/scripting.qh b/qcsrc/server/bot/scripting.qh new file mode 100644 index 0000000000..d5cdda9d69 --- /dev/null +++ b/qcsrc/server/bot/scripting.qh @@ -0,0 +1,83 @@ +#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 diff --git a/qcsrc/server/bot/waypoints.qc b/qcsrc/server/bot/waypoints.qc new file mode 100644 index 0000000000..8f50d0276a --- /dev/null +++ b/qcsrc/server/bot/waypoints.qc @@ -0,0 +1,1170 @@ +#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(); + } +} + diff --git a/qcsrc/server/bot/waypoints.qh b/qcsrc/server/bot/waypoints.qh new file mode 100644 index 0000000000..fde524bb94 --- /dev/null +++ b/qcsrc/server/bot/waypoints.qh @@ -0,0 +1,69 @@ +#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 diff --git a/qcsrc/server/cl_client.qc b/qcsrc/server/cl_client.qc index adf2542cbd..71efa5f9d9 100644 --- a/qcsrc/server/cl_client.qc +++ b/qcsrc/server/cl_client.qc @@ -20,7 +20,8 @@ #include "campaign.qh" #include "command/common.qh" -#include "bot/api.qh" +#include "bot/bot.qh" +#include "bot/navigation.qh" #include "../common/vehicles/all.qh" diff --git a/qcsrc/server/cl_impulse.qc b/qcsrc/server/cl_impulse.qc index 1789023750..a594c83c7c 100644 --- a/qcsrc/server/cl_impulse.qc +++ b/qcsrc/server/cl_impulse.qc @@ -1,9 +1,12 @@ #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" diff --git a/qcsrc/server/cl_player.qc b/qcsrc/server/cl_player.qc index 82c456f0ac..e94ba62832 100644 --- a/qcsrc/server/cl_player.qc +++ b/qcsrc/server/cl_player.qc @@ -1,6 +1,7 @@ #include "cl_player.qh" #include "_all.qh" +#include "bot/bot.qh" #include "cheats.qh" #include "g_damage.qh" #include "g_subs.qh" diff --git a/qcsrc/server/command/sv_cmd.qc b/qcsrc/server/command/sv_cmd.qc index 03d47c7471..d08588ff99 100644 --- a/qcsrc/server/command/sv_cmd.qc +++ b/qcsrc/server/command/sv_cmd.qc @@ -17,6 +17,10 @@ #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" diff --git a/qcsrc/server/defs.qh b/qcsrc/server/defs.qh index 102309a081..4a7edc2b94 100644 --- a/qcsrc/server/defs.qh +++ b/qcsrc/server/defs.qh @@ -45,6 +45,7 @@ float sv_clones; float sv_foginterval; float player_count; +float currentbots; float bots_would_leave; void UpdateFrags(entity player, float f); diff --git a/qcsrc/server/g_world.qc b/qcsrc/server/g_world.qc index b202f284f6..a0f9a3f131 100644 --- a/qcsrc/server/g_world.qc +++ b/qcsrc/server/g_world.qc @@ -3,6 +3,7 @@ #include "anticheat.qh" #include "antilag.qh" +#include "bot/bot.qh" #include "campaign.qh" #include "cheats.qh" #include "cl_client.qh" diff --git a/qcsrc/server/movelib.qc b/qcsrc/server/movelib.qc new file mode 100644 index 0000000000..acacba093a --- /dev/null +++ b/qcsrc/server/movelib.qc @@ -0,0 +1,237 @@ +#include "movelib.qh" + +#ifdef SVQC +.vector moveto; + +/** + Simulate drag + self.velocity = movelib_dragvec(self.velocity,0.02,0.5); +**/ +vector movelib_dragvec(float drag, float exp_) +{SELFPARAM(); + float lspeed,ldrag; + + lspeed = vlen(self.velocity); + ldrag = lspeed * drag; + ldrag = ldrag * (drag * exp_); + ldrag = 1 - (ldrag / lspeed); + + return self.velocity * ldrag; +} + +/** + Simulate drag + self.velocity *= movelib_dragflt(somespeed,0.01,0.7); +**/ +float movelib_dragflt(float fspeed,float drag,float exp_) +{ + float ldrag; + + ldrag = fspeed * drag; + ldrag = ldrag * ldrag * exp_; + ldrag = 1 - (ldrag / fspeed); + + return ldrag; +} + +/** + Do a inertia simulation based on velocity. + Basicaly, this allows you to simulate loss of steering with higher speed. + self.velocity = movelib_inertmove_byspeed(self.velocity,newvel,1000,0.1,0.9); +**/ +vector movelib_inertmove_byspeed(vector vel_new, float vel_max,float newmin,float oldmax) +{SELFPARAM(); + float influense; + + influense = vlen(self.velocity) * (1 / vel_max); + + influense = bound(newmin,influense,oldmax); + + return (vel_new * (1 - influense)) + (self.velocity * influense); +} + +vector movelib_inertmove(vector new_vel,float new_bias) +{SELFPARAM(); + return new_vel * new_bias + self.velocity * (1-new_bias); +} + +void movelib_move(vector force,float max_velocity,float drag,float theMass,float breakforce) +{SELFPARAM(); + float deltatime; + float acceleration; + float mspeed; + vector breakvec; + + deltatime = time - self.movelib_lastupdate; + if (deltatime > 0.15) deltatime = 0; + self.movelib_lastupdate = time; + if (!deltatime) return; + + mspeed = vlen(self.velocity); + + if (theMass) + acceleration = vlen(force) / theMass; + else + acceleration = vlen(force); + + if (self.flags & FL_ONGROUND) + { + if (breakforce) + { + breakvec = (normalize(self.velocity) * (breakforce / theMass) * deltatime); + self.velocity = self.velocity - breakvec; + } + + self.velocity = self.velocity + force * (acceleration * deltatime); + } + + if (drag) + self.velocity = movelib_dragvec(drag, 1); + + if (self.waterlevel > 1) + { + self.velocity = self.velocity + force * (acceleration * deltatime); + self.velocity = self.velocity + '0 0 0.05' * autocvar_sv_gravity * deltatime; + } + else + self.velocity = self.velocity + '0 0 -1' * autocvar_sv_gravity * deltatime; + + mspeed = vlen(self.velocity); + + if (max_velocity) + if (mspeed > max_velocity) + self.velocity = normalize(self.velocity) * (mspeed - 50);//* max_velocity; +} + +/* +.float mass; +.float side_friction; +.float ground_friction; +.float air_friction; +.float water_friction; +.float buoyancy; +float movelib_deltatime; + +void movelib_startupdate() +{ + movelib_deltatime = time - self.movelib_lastupdate; + + if (movelib_deltatime > 0.5) + movelib_deltatime = 0; + + self.movelib_lastupdate = time; +} + +void movelib_update(vector dir,float force) +{ + vector acceleration; + float old_speed; + float ffriction,v_z; + + vector breakvec; + vector old_dir; + vector ggravity; + vector old; + + if(!movelib_deltatime) + return; + v_z = self.velocity_z; + old_speed = vlen(self.velocity); + old_dir = normalize(self.velocity); + + //ggravity = (autocvar_sv_gravity / self.mass) * '0 0 100'; + acceleration = (force / self.mass) * dir; + //acceleration -= old_dir * (old_speed / self.mass); + acceleration -= ggravity; + + if(self.waterlevel > 1) + { + ffriction = self.water_friction; + acceleration += self.buoyancy * '0 0 1'; + } + else + if(self.flags & FL_ONGROUND) + ffriction = self.ground_friction; + else + ffriction = self.air_friction; + + acceleration *= ffriction; + //self.velocity = self.velocity * (ffriction * movelib_deltatime); + self.velocity += acceleration * movelib_deltatime; + self.velocity_z = v_z; + +} +*/ + +void movelib_beak_simple(float force) +{SELFPARAM(); + float mspeed; + vector mdir; + float vz; + + mspeed = max(0,vlen(self.velocity) - force); + mdir = normalize(self.velocity); + vz = self.velocity.z; + self.velocity = mdir * mspeed; + self.velocity_z = vz; +} + +/** +Pitches and rolls the entity to match the gound. +Yed need to set v_up and v_forward (generally by calling makevectors) before calling this. +**/ +#endif + +void movelib_groundalign4point(float spring_length, float spring_up, float blendrate, float _max) +{SELFPARAM(); + vector a, b, c, d, e, r, push_angle, ahead, side; + + push_angle.y = 0; + r = (self.absmax + self.absmin) * 0.5 + (v_up * spring_up); + e = v_up * spring_length; + + // Put springs slightly inside bbox + ahead = v_forward * (self.maxs.x * 0.8); + side = v_right * (self.maxs.y * 0.8); + + a = r + ahead + side; + b = r + ahead - side; + c = r - ahead + side; + d = r - ahead - side; + + traceline(a, a - e,MOVE_NORMAL,self); + a.z = (1 - trace_fraction); + r = trace_endpos; + + traceline(b, b - e,MOVE_NORMAL,self); + b.z = (1 - trace_fraction); + r += trace_endpos; + + traceline(c, c - e,MOVE_NORMAL,self); + c.z = (1 - trace_fraction); + r += trace_endpos; + + traceline(d, d - e,MOVE_NORMAL,self); + d.z = (1 - trace_fraction); + r += trace_endpos; + + a.x = r.z; + r = self.origin; + r.z = r.z; + + push_angle.x = (a.z - c.z) * _max; + push_angle.x += (b.z - d.z) * _max; + + push_angle.z = (b.z - a.z) * _max; + push_angle.z += (d.z - c.z) * _max; + + //self.angles_x += push_angle_x * 0.95; + //self.angles_z += push_angle_z * 0.95; + + self.angles_x = ((1-blendrate) * self.angles.x) + (push_angle.x * blendrate); + self.angles_z = ((1-blendrate) * self.angles.z) + (push_angle.z * blendrate); + + //a = self.origin; + setorigin(self,r); +} + diff --git a/qcsrc/server/movelib.qh b/qcsrc/server/movelib.qh new file mode 100644 index 0000000000..8a4bfd4883 --- /dev/null +++ b/qcsrc/server/movelib.qh @@ -0,0 +1,53 @@ +#ifndef MOVELIB_H +#define MOVELIB_H + +#ifdef SVQC +.vector moveto; + +/** + Simulate drag + self.velocity = movelib_dragvec(self.velocity,0.02,0.5); +**/ +vector movelib_dragvec(float drag, float exp_); + +/** + Simulate drag + self.velocity *= movelib_dragflt(somespeed,0.01,0.7); +**/ +float movelib_dragflt(float fspeed,float drag,float exp_); + +/** + Do a inertia simulation based on velocity. + Basicaly, this allows you to simulate loss of steering with higher speed. + self.velocity = movelib_inertmove_byspeed(self.velocity,newvel,1000,0.1,0.9); +**/ +vector movelib_inertmove_byspeed(vector vel_new, float vel_max,float newmin,float oldmax); + +vector movelib_inertmove(vector new_vel,float new_bias); + +.float movelib_lastupdate; +void movelib_move(vector force,float max_velocity,float drag,float theMass,float breakforce); + +/* +void movelib_move_simple(vector newdir,float velo,float blendrate) +{ + self.velocity = self.velocity * (1 - blendrate) + (newdir * blendrate) * velo; +} +*/ +#define movelib_move_simple(newdir,velo,blendrate) \ + self.velocity = self.velocity * (1 - blendrate) + (newdir * blendrate) * velo + +#define movelib_move_simple_gravity(newdir,velo,blendrate) \ + if(self.flags & FL_ONGROUND) movelib_move_simple(newdir,velo,blendrate) + +void movelib_beak_simple(float force); + +/** +Pitches and rolls the entity to match the gound. +Yed need to set v_up and v_forward (generally by calling makevectors) before calling this. +**/ +#endif + +void movelib_groundalign4point(float spring_length, float spring_up, float blendrate, float _max); + +#endif diff --git a/qcsrc/server/mutators/gamemode.qh b/qcsrc/server/mutators/gamemode.qh index 4a52ffabfb..d2066b9abd 100644 --- a/qcsrc/server/mutators/gamemode.qh +++ b/qcsrc/server/mutators/gamemode.qh @@ -12,6 +12,14 @@ #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" diff --git a/qcsrc/server/mutators/gamemode_assault.qh b/qcsrc/server/mutators/gamemode_assault.qh index 7635a9395e..1266492ca0 100644 --- a/qcsrc/server/mutators/gamemode_assault.qh +++ b/qcsrc/server/mutators/gamemode_assault.qh @@ -21,6 +21,9 @@ void() havocbot_role_ast_offense; 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; diff --git a/qcsrc/server/mutators/gamemode_keyhunt.qc b/qcsrc/server/mutators/gamemode_keyhunt.qc index 17f2da70b8..be45c3db0c 100644 --- a/qcsrc/server/mutators/gamemode_keyhunt.qc +++ b/qcsrc/server/mutators/gamemode_keyhunt.qc @@ -50,7 +50,7 @@ float kh_Team_ByID(float t) return 0; } -entity kh_worldkeylist; +//entity kh_worldkeylist; .entity kh_worldkeynext; entity kh_controller; //float kh_tracking_enabled; diff --git a/qcsrc/server/mutators/gamemode_nexball.qc b/qcsrc/server/mutators/gamemode_nexball.qc index e95231ea41..f65e058433 100644 --- a/qcsrc/server/mutators/gamemode_nexball.qc +++ b/qcsrc/server/mutators/gamemode_nexball.qc @@ -3,8 +3,6 @@ #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; diff --git a/qcsrc/server/mutators/mutator.qh b/qcsrc/server/mutators/mutator.qh index 70c9125620..39adb175c8 100644 --- a/qcsrc/server/mutators/mutator.qh +++ b/qcsrc/server/mutators/mutator.qh @@ -13,6 +13,14 @@ #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" diff --git a/qcsrc/server/mutators/mutators_include.qc b/qcsrc/server/mutators/mutators_include.qc index 67bb246b07..8564d34c59 100644 --- a/qcsrc/server/mutators/mutators_include.qc +++ b/qcsrc/server/mutators/mutators_include.qc @@ -70,6 +70,7 @@ #include "../playerdemo.qh" #include "../round_handler.qh" #include "../item_key.qh" + #include "../pathlib/pathlib.qh" #include "../../common/vehicles/all.qh" #endif diff --git a/qcsrc/server/pathlib/costs.qc b/qcsrc/server/pathlib/costs.qc new file mode 100644 index 0000000000..3e452f66e7 --- /dev/null +++ b/qcsrc/server/pathlib/costs.qc @@ -0,0 +1,146 @@ +#include "pathlib.qh" + +float pathlib_g_static(entity parent,vector to, float static_cost) +{ + return parent.pathlib_node_g + static_cost; +} + +float pathlib_g_static_water(entity parent,vector to, float static_cost) +{ + if(inwater(to)) + return parent.pathlib_node_g + static_cost * pathlib_movecost_waterfactor; + else + return parent.pathlib_node_g + static_cost; +} + +float pathlib_g_euclidean(entity parent,vector to, float static_cost) +{ + return parent.pathlib_node_g + vlen(parent.origin - to); +} + +float pathlib_g_euclidean_water(entity parent,vector to, float static_cost) +{ + if(inwater(to)) + return parent.pathlib_node_g + vlen(parent.origin - to) * pathlib_movecost_waterfactor; + else + return parent.pathlib_node_g + vlen(parent.origin - to); +} + + +/** + Manhattan Menas we expect to move up,down left or right + No diagonal moves espected. (like moving bewteen city blocks) +**/ +float pathlib_h_manhattan(vector a,vector b) +{ + //h(n) = D * (abs(n.x-goal.x) + abs(n.y-goal.y)) + + float h; + h = fabs(a.x - b.x); + h += fabs(a.y - b.y); + h *= pathlib_gridsize; + + return h; +} + +/** + This heuristic consider both stright and disagonal moves + to have teh same cost. +**/ +float pathlib_h_diagonal(vector a,vector b) +{ + //h(n) = D * max(abs(n.x-goal.x), abs(n.y-goal.y)) + float h,x,y; + + x = fabs(a.x - b.x); + y = fabs(a.y - b.y); + h = pathlib_movecost * max(x,y); + + return h; +} + +/** + This heuristic only considers the stright line distance. + Will usualy mean a lower H then G meaning A* Will speand more + and run slower. +**/ +float pathlib_h_euclidean(vector a,vector b) +{ + return vlen(a - b); +} + +/** + This heuristic consider both stright and disagonal moves, + But has a separate cost for diagonal moves. +**/ +float pathlib_h_diagonal2(vector a,vector b) +{ + float h_diag,h_str,h,x,y; + + /* + h_diagonal(n) = min(abs(n.x-goal.x), abs(n.y-goal.y)) + h_straight(n) = (abs(n.x-goal.x) + abs(n.y-goal.y)) + h(n) = D2 * h_diagonal(n) + D * (h_straight(n) - 2*h_diagonal(n))) + */ + + x = fabs(a.x - b.x); + y = fabs(a.y - b.y); + + h_diag = min(x,y); + h_str = x + y; + + h = pathlib_movecost_diag * h_diag; + h += pathlib_movecost * (h_str - 2 * h_diag); + + return h; +} + +/** + This heuristic consider both stright and disagonal moves, + But has a separate cost for diagonal moves. +**/ +float pathlib_h_diagonal2sdp(vector preprev,vector prev,vector point,vector end) +{ + float h_diag,h_str,h,x,y,z; + + //h_diagonal(n) = min(abs(n.x-goal.x), abs(n.y-goal.y)) + //h_straight(n) = (abs(n.x-goal.x) + abs(n.y-goal.y)) + //h(n) = D2 * h_diagonal(n) + D * (h_straight(n) - 2*h_diagonal(n))) + + x = fabs(point.x - end.x); + y = fabs(point.y - end.y); + z = fabs(point.z - end.z); + + h_diag = min3(x,y,z); + h_str = x + y + z; + + h = pathlib_movecost_diag * h_diag; + h += pathlib_movecost * (h_str - 2 * h_diag); + + float m; + vector d1,d2; + + d1 = normalize(preprev - point); + d2 = normalize(prev - point); + m = vlen(d1-d2); + + return h * m; +} + + +float pathlib_h_diagonal3(vector a,vector b) +{ + float h_diag,h_str,h,x,y,z; + + x = fabs(a.x - b.x); + y = fabs(a.y - b.y); + z = fabs(a.z - b.z); + + h_diag = min3(x,y,z); + h_str = x + y + z; + + h = pathlib_movecost_diag * h_diag; + h += pathlib_movecost * (h_str - 2 * h_diag); + + return h; +} diff --git a/qcsrc/server/pathlib/debug.qc b/qcsrc/server/pathlib/debug.qc new file mode 100644 index 0000000000..37e167aae7 --- /dev/null +++ b/qcsrc/server/pathlib/debug.qc @@ -0,0 +1,123 @@ +#include "pathlib.qh" + +MODEL(SQUARE, "models/pathlib/square.md3"); +MODEL(SQUARE_GOOD, "models/pathlib/goodsquare.md3"); +MODEL(SQUARE_BAD, "models/pathlib/badsquare.md3"); +MODEL(EDGE, "models/pathlib/edge.md3"); + +#ifdef TURRET_DEBUG +void mark_error(vector where,float lifetime); +void mark_info(vector where,float lifetime); +entity mark_misc(vector where,float lifetime); +#endif + +void pathlib_showpath(entity start) +{ + entity e; + e = start; + while(e.path_next) + { + te_lightning1(e,e.origin,e.path_next.origin); + e = e.path_next; + } +} + +void path_dbg_think() +{SELFPARAM(); + pathlib_showpath(self); + self.nextthink = time + 1; +} + +void __showpath2_think() +{SELFPARAM(); + #ifdef TURRET_DEBUG + mark_info(self.origin,1); + #endif + if(self.path_next) + { + self.path_next.think = __showpath2_think; + self.path_next.nextthink = time + 0.15; + } + else + { + self.owner.think = __showpath2_think; + self.owner.nextthink = time + 0.15; + } +} + +void pathlib_showpath2(entity path) +{ + path.think = __showpath2_think; + path.nextthink = time; +} + + +void pathlib_showsquare2(entity node ,vector ncolor,float align) +{ + + node.alpha = 0.25; + node.scale = pathlib_gridsize / 512.001; + node.solid = SOLID_NOT; + + setmodel(node, MDL_SQUARE); + setorigin(node,node.origin); + node.colormod = ncolor; + + if(align) + { + traceline(node.origin + '0 0 32', node.origin - '0 0 128', MOVE_WORLDONLY, node); + node.angles = vectoangles(trace_plane_normal); + node.angles_x -= 90; + } +} + +void SUB_Remove(); + +void pathlib_showsquare(vector where,float goodsquare,float _lifetime) +{ + entity s; + + if(!_lifetime) + _lifetime = time + 30; + else + _lifetime += time; + + s = spawn(); + s.alpha = 0.25; + s.think = SUB_Remove; + s.nextthink = _lifetime; + s.scale = pathlib_gridsize / 512.001; + s.solid = SOLID_NOT; + + setmodel(s, goodsquare ? MDL_SQUARE_GOOD : MDL_SQUARE_BAD); + + traceline(where + '0 0 32',where - '0 0 128',MOVE_WORLDONLY,s); + + s.angles = vectoangles(trace_plane_normal); + s.angles_x -= 90; + setorigin(s,where); +} + +void pathlib_showedge(vector where,float _lifetime,float rot) +{ + entity e; + + if(!_lifetime) + _lifetime = time + 30; + else + _lifetime += time; + + e = spawn(); + e.alpha = 0.25; + e.think = SUB_Remove; + e.nextthink = _lifetime; + e.scale = pathlib_gridsize / 512; + e.solid = SOLID_NOT; + setorigin(e,where); + setmodel(e, MDL_EDGE); + //traceline(where + '0 0 32',where - '0 0 128',MOVE_WORLDONLY,e); + //e.angles = vectoangles(trace_plane_normal); + e.angles_y = rot; + //e.angles_x += 90; + +} diff --git a/qcsrc/server/pathlib/expandnode.qc b/qcsrc/server/pathlib/expandnode.qc new file mode 100644 index 0000000000..1f095a7856 --- /dev/null +++ b/qcsrc/server/pathlib/expandnode.qc @@ -0,0 +1,235 @@ +#include "pathlib.qh" +#include "utility.qh" + +vector plib_points2[8]; +vector plib_points[8]; +float plib_fvals[8]; + +float pathlib_expandnode_starf(entity node, vector start, vector goal) +{ + vector where,f,r,t; + float fc,c; + entity nap; + + where = node.origin; + + f = PLIB_FORWARD * pathlib_gridsize; + r = PLIB_RIGHT * pathlib_gridsize; + + // Forward + plib_points[0] = where + f; + + // Back + plib_points[1] = where - f; + + // Right + plib_points[2] = where + r; + + // Left + plib_points[3] = where - r; + + // Forward-right + plib_points[4] = where + f + r; + + // Forward-left + plib_points[5] = where + f - r; + + // Back-right + plib_points[6] = where - f + r; + + // Back-left + plib_points[7] = where - f - r; + + for(int i=0;i < 8; ++i) + { + t = plib_points[i]; + fc = pathlib_heuristic(t,goal) + pathlib_cost(node, t, pathlib_gridsize); + plib_fvals[i] = fc; + + } + + fc = plib_fvals[0]; + plib_points2[0] = plib_points[0]; + vector bp; + bp = plib_points[0]; + int fc2 = 0; + for(int i = 0; i < 8; ++i) + { + c = 0; + nap = pathlib_nodeatpoint(plib_points[i]); + if(nap) + if(nap.owner == openlist) + c = 1; + else + c = 1; + + if(c) + if(plib_fvals[i] < fc) + { + bp = plib_points[i]; + fc = plib_fvals[i]; + plib_points2[fc2] = plib_points[i]; + ++fc2; + } + + /* + nap = pathlib_nodeatpoint(plib_points[i]); + if(nap) + if not nap.owner == closedlist) + { + } + */ + } + + pathlib_makenode(node, start, bp, goal, pathlib_gridsize); + + for(int i = 0; i < 3; ++i) + { + pathlib_makenode(node, start, plib_points2[i], goal, pathlib_gridsize); + } + + return pathlib_open_cnt; +} + +float pathlib_expandnode_star(entity node, vector start, vector goal) +{ + vector point, where, f, r; + + where = node.origin; + + f = PLIB_FORWARD * pathlib_gridsize; + r = PLIB_RIGHT * pathlib_gridsize; + + if (node.pathlib_node_edgeflags == pathlib_node_edgeflag_unknown) + node.pathlib_node_edgeflags = tile_check_plus2(node.origin); + + if(node.pathlib_node_edgeflags == pathlib_node_edgeflag_none) + { + LOG_TRACE("Node at ", vtos(node.origin), " not expanable"); + return pathlib_open_cnt; + } + + // Forward + if (node.pathlib_node_edgeflags & pathlib_node_edgeflag_forward) + { + point = where + f; + pathlib_makenode(node, start, point, goal, pathlib_movecost); + } + + // Back + if (node.pathlib_node_edgeflags & pathlib_node_edgeflag_back) + { + point = where - f; + pathlib_makenode(node, start, point, goal, pathlib_movecost); + } + + // Right + if (node.pathlib_node_edgeflags & pathlib_node_edgeflag_right) + { + point = where + r; + pathlib_makenode(node, start, point, goal, pathlib_movecost); + } + + // Left + if (node.pathlib_node_edgeflags & pathlib_node_edgeflag_left) + { + point = where - r; + pathlib_makenode(node, start, point, goal, pathlib_movecost); + + } + + // Forward-right + if (node.pathlib_node_edgeflags & pathlib_node_edgeflag_forwardright) + { + point = where + f + r; + pathlib_makenode(node, start, point, goal, pathlib_movecost_diag); + } + + // Forward-left + if (node.pathlib_node_edgeflags & pathlib_node_edgeflag_forwardleft) + { + point = where + f - r; + pathlib_makenode(node, start, point, goal, pathlib_movecost_diag); + + } + + // Back-right + if (node.pathlib_node_edgeflags & pathlib_node_edgeflag_backright) + { + point = where - f + r; + pathlib_makenode(node, start, point, goal, pathlib_movecost_diag); + } + + // Back-left + if (node.pathlib_node_edgeflags & pathlib_node_edgeflag_backleft) + { + point = where - f - r; + pathlib_makenode(node, start, point, goal, pathlib_movecost_diag); + } + + return pathlib_open_cnt; +} + +float pathlib_expandnode_octagon(entity node, vector start, vector goal) +{ + vector point,where,f,r; + + where = node.origin; + + f = PLIB_FORWARD * pathlib_gridsize; + r = PLIB_RIGHT * pathlib_gridsize; + + // Forward + point = where + f; + pathlib_makenode(node, start, point, goal, pathlib_movecost); + + // Back + point = where - f; + pathlib_makenode(node, start, point, goal, pathlib_movecost); + + // Right + point = where + r; + pathlib_makenode(node, start, point, goal, pathlib_movecost); + + // Left + point = where - r; + pathlib_makenode(node, start, point, goal, pathlib_movecost); + + f = PLIB_FORWARD * pathlib_gridsize * 0.5; + r = PLIB_RIGHT * pathlib_gridsize * 0.5; + + // Forward-right + point = where + f + r; + pathlib_makenode(node, start, point, goal, pathlib_movecost); + + + // Forward-left + point = where + f - r; + pathlib_makenode(node, start, point, goal, pathlib_movecost); + + + // Back-right + point = where - f + r; + pathlib_makenode(node, start, point, goal, pathlib_movecost); + + // Back-left + point = where - f - r; + pathlib_makenode(node, start, point, goal, pathlib_movecost); + + return pathlib_open_cnt; +} + +float pathlib_expandnode_box(entity node, vector start, vector goal) +{ + vector v; + + for(v.z = node.origin.z - pathlib_gridsize; v.z <= node.origin.z + pathlib_gridsize; v.z += pathlib_gridsize) + for(v.y = node.origin.y - pathlib_gridsize; v.y <= node.origin.y + pathlib_gridsize; v.y += pathlib_gridsize) + for(v.x = node.origin.x - pathlib_gridsize; v.x <= node.origin.x + pathlib_gridsize; v.x += pathlib_gridsize) + { + //if(vlen(v - node.origin)) + pathlib_makenode(node,start,v,goal,pathlib_movecost); + } + + return pathlib_open_cnt; +} diff --git a/qcsrc/server/pathlib/main.qc b/qcsrc/server/pathlib/main.qc new file mode 100644 index 0000000000..f097e8a0fe --- /dev/null +++ b/qcsrc/server/pathlib/main.qc @@ -0,0 +1,569 @@ +#include "../_all.qh" + +#include "pathlib.qh" +#include "utility.qh" +#include "../command/common.qh" + +void pathlib_deletepath(entity start) +{ + entity e; + + e = findchainentity(owner, start); + while(e) + { + e.think = SUB_Remove; + e.nextthink = time; + e = e.chain; + } +} + +//#define PATHLIB_NODEEXPIRE 0.05 +const float PATHLIB_NODEEXPIRE = 20; + +void dumpnode(entity n) +{ + n.is_path_node = false; + n.think = SUB_Remove; + n.nextthink = time; +} + +entity pathlib_mknode(vector where,entity parent) +{ + entity node; + + node = pathlib_nodeatpoint(where); + if(node) + { + #ifdef TURRET_DEBUG + mark_error(where, 60); + #endif + return node; + } + + node = spawn(); + + node.think = SUB_Remove; + node.nextthink = time + PATHLIB_NODEEXPIRE; + node.is_path_node = true; + node.owner = openlist; + node.path_prev = parent; + + + setsize(node, '0 0 0', '0 0 0'); + + setorigin(node, where); + node.medium = pointcontents(where); + pathlib_showsquare(where, 1 ,15); + + ++pathlib_made_cnt; + ++pathlib_open_cnt; + + return node; +} + +float pathlib_makenode_adaptive(entity parent,vector start, vector to, vector goal,float cost) +{ + entity node; + float h,g,f,doedge = 0; + vector where; + + ++pathlib_searched_cnt; + + if(inwater(parent.origin)) + { + LOG_TRACE("FromWater\n"); + pathlib_expandnode = pathlib_expandnode_box; + pathlib_movenode = pathlib_swimnode; + } + else + { + if(inwater(to)) + { + LOG_TRACE("ToWater\n"); + pathlib_expandnode = pathlib_expandnode_box; + pathlib_movenode = pathlib_walknode; + } + else + { + LOG_TRACE("LandToLoand\n"); + //if(edge_check(parent.origin)) + // return 0; + + pathlib_expandnode = pathlib_expandnode_star; + pathlib_movenode = pathlib_walknode; + doedge = 1; + } + } + + node = pathlib_nodeatpoint(to); + if(node) + { + LOG_TRACE("NodeAtPoint\n"); + ++pathlib_merge_cnt; + + if(node.owner == openlist) + { + h = pathlib_heuristic(node.origin,goal); + g = pathlib_cost(parent,node.origin,cost); + f = g + h; + + if(node.pathlib_node_g > g) + { + node.pathlib_node_h = h; + node.pathlib_node_g = g; + node.pathlib_node_f = f; + + node.path_prev = parent; + } + + if (!best_open_node) + best_open_node = node; + else if(best_open_node.pathlib_node_f > node.pathlib_node_f) + best_open_node = node; + } + + return 1; + } + + where = pathlib_movenode(parent.origin, to, 0); + + if (!pathlib_movenode_goodnode) + { + //pathlib_showsquare(where, 0 ,30); + //pathlib_showsquare(parent.origin, 1 ,30); + LOG_TRACE("pathlib_movenode_goodnode = 0\n"); + return 0; + } + + //pathlib_showsquare(where, 1 ,30); + + if(pathlib_nodeatpoint(where)) + { + LOG_TRACE("NAP WHERE :",vtos(where),"\n"); + LOG_TRACE("not NAP TO:",vtos(to),"\n"); + LOG_TRACE("NAP-NNAP:",ftos(vlen(to-where)),"\n\n"); + return 0; + } + + + if(doedge) + if (!tile_check(where)) + { + LOG_TRACE("tile_check fail\n"); + pathlib_showsquare(where, 0 ,30); + return 0; + } + + + h = pathlib_heuristic(where,goal); + g = pathlib_cost(parent,where,cost); + f = g + h; + + /* + node = findradius(where,pathlib_gridsize * 0.5); + while(node) + { + if(node.is_path_node == true) + { + ++pathlib_merge_cnt; + if(node.owner == openlist) + { + if(node.pathlib_node_g > g) + { + //pathlib_movenode(where,node.origin,0); + //if(pathlib_movenode_goodnode) + //{ + //mark_error(node.origin + '0 0 128',30); + node.pathlib_node_h = h; + node.pathlib_node_g = g; + node.pathlib_node_f = f; + node.path_prev = parent; + //} + } + + if (!best_open_node) + best_open_node = node; + else if(best_open_node.pathlib_node_f > node.pathlib_node_f) + best_open_node = node; + } + + return 1; + } + node = node.chain; + } + */ + + node = pathlib_mknode(where, parent); + node.pathlib_node_h = h; + node.pathlib_node_g = g; + node.pathlib_node_f = f; + + if (!best_open_node) + best_open_node = node; + else if(best_open_node.pathlib_node_f > node.pathlib_node_f) + best_open_node = node; + + return 1; +} + +entity pathlib_getbestopen() +{ + entity node; + entity bestnode; + + if(best_open_node) + { + ++pathlib_bestcash_hits; + pathlib_bestcash_saved += pathlib_open_cnt; + + return best_open_node; + } + + node = findchainentity(owner,openlist); + if(!node) + return world; + + bestnode = node; + while(node) + { + ++pathlib_bestopen_seached; + if(node.pathlib_node_f < bestnode.pathlib_node_f) + bestnode = node; + + node = node.chain; + } + + return bestnode; +} + +void pathlib_close_node(entity node,vector goal) +{ + + if(node.owner == closedlist) + { + LOG_TRACE("Pathlib: Tried to close a closed node!\n"); + return; + } + + if(node == best_open_node) + best_open_node = world; + + ++pathlib_closed_cnt; + --pathlib_open_cnt; + + node.owner = closedlist; + + if(vlen(node.origin - goal) <= pathlib_gridsize) + { + vector goalmove; + + goalmove = pathlib_walknode(node.origin,goal,1); + if(pathlib_movenode_goodnode) + { + goal_node = node; + pathlib_foundgoal = true; + } + } +} + +void pathlib_cleanup() +{ + best_open_node = world; + + //return; + + entity node; + + node = findfloat(world,is_path_node, true); + while(node) + { + /* + node.owner = openlist; + node.pathlib_node_g = 0; + node.pathlib_node_h = 0; + node.pathlib_node_f = 0; + node.path_prev = world; + */ + + dumpnode(node); + node = findfloat(node,is_path_node, true); + } + + if(openlist) + remove(openlist); + + if(closedlist) + remove(closedlist); + + openlist = world; + closedlist = world; + +} + +float Cosine_Interpolate(float a, float b, float x) +{ + float ft,f; + + ft = x * 3.1415927; + f = (1 - cos(ft)) * 0.5; + + return a*(1-f) + b*f; +} + +float buildpath_nodefilter_directional(vector n,vector c,vector p) +{ + vector d1,d2; + + d2 = normalize(p - c); + d1 = normalize(c - n); + + if(vlen(d1-d2) < 0.25) + { + //mark_error(c,30); + return 1; + } + //mark_info(c,30); + return 0; +} + +float buildpath_nodefilter_moveskip(vector n,vector c,vector p) +{ + pathlib_walknode(p,n,1); + if(pathlib_movenode_goodnode) + return 1; + + return 0; +} + +float buildpath_nodefilter_none(vector n,vector c,vector p) +{ + return 0; +} + +entity path_build(entity next, vector where, entity prev, entity start) +{ + entity path; + + if(prev && next) + if(buildpath_nodefilter) + if(buildpath_nodefilter(next.origin,where,prev.origin)) + return next; + + + path = spawn(); + path.owner = start; + path.path_next = next; + + setorigin(path,where); + + if(!next) + path.classname = "path_end"; + else + { + if(!prev) + path.classname = "path_start"; + else + path.classname = "path_node"; + } + + return path; +} + +entity pathlib_astar(vector from,vector to) +{SELFPARAM(); + entity path, start, end, open, n, ln; + float ptime, ftime, ctime; + + ptime = gettime(GETTIME_REALTIME); + pathlib_starttime = ptime; + + pathlib_cleanup(); + + // Select water<->land capable node make/link + pathlib_makenode = pathlib_makenode_adaptive; + + // Select XYZ cost estimate + //pathlib_heuristic = pathlib_h_diagonal3; + pathlib_heuristic = pathlib_h_diagonal; + + // Select distance + waterfactor cost + pathlib_cost = pathlib_g_euclidean_water; + + // Select star expander + pathlib_expandnode = pathlib_expandnode_star; + + // Select walk simulation movement test + pathlib_movenode = pathlib_walknode; + + // Filter final nodes by direction + buildpath_nodefilter = buildpath_nodefilter_directional; + + // Filter tiles with cross pattern + tile_check = tile_check_cross; + + // If the start is in water we need diffrent settings + if(inwater(from)) + { + // Select volumetric node expaner + pathlib_expandnode = pathlib_expandnode_box; + + // Water movement test + pathlib_movenode = pathlib_swimnode; + } + + if (!openlist) + openlist = spawn(); + + if (!closedlist) + closedlist = spawn(); + + pathlib_closed_cnt = 0; + pathlib_open_cnt = 0; + pathlib_made_cnt = 0; + pathlib_merge_cnt = 0; + pathlib_searched_cnt = 0; + pathlib_bestopen_seached = 0; + pathlib_bestcash_hits = 0; + pathlib_bestcash_saved = 0; + + pathlib_gridsize = 128; + pathlib_movecost = pathlib_gridsize; + pathlib_movecost_diag = vlen(('1 1 0' * pathlib_gridsize)); + pathlib_movecost_waterfactor = 25000000; + pathlib_foundgoal = 0; + + movenode_boxmax = self.maxs * 1.1; + movenode_boxmin = self.mins * 1.1; + + movenode_stepsize = pathlib_gridsize * 0.25; + + tile_check_size = pathlib_gridsize * 0.5; + tile_check_up = '0 0 2' * pathlib_gridsize; + tile_check_up = '0 0 128'; + tile_check_down = '0 0 3' * pathlib_gridsize; + tile_check_down = '0 0 256'; + + //movenode_stepup = '0 0 128'; + movenode_stepup = '0 0 36'; + movenode_maxdrop = '0 0 512'; + //movenode_maxdrop = '0 0 512'; + movenode_boxup = '0 0 72'; + + from.x = fsnap(from.x, pathlib_gridsize); + from.y = fsnap(from.y, pathlib_gridsize); + //from_z += 32; + + to.x = fsnap(to.x, pathlib_gridsize); + to.y = fsnap(to.y, pathlib_gridsize); + //to_z += 32; + + LOG_TRACE("AStar init\n"); + path = pathlib_mknode(from, world); + pathlib_close_node(path, to); + if(pathlib_foundgoal) + { + LOG_TRACE("AStar: Goal found on first node!\n"); + + open = spawn(); + open.owner = open; + open.classname = "path_end"; + setorigin(open,path.origin); + + pathlib_cleanup(); + + return open; + } + + if(pathlib_expandnode(path, from, to) <= 0) + { + LOG_TRACE("AStar path fail.\n"); + pathlib_cleanup(); + + return world; + } + + best_open_node = pathlib_getbestopen(); + n = best_open_node; + pathlib_close_node(best_open_node, to); + if(inwater(n.origin)) + pathlib_expandnode_box(n, from, to); + else + pathlib_expandnode_star(n, from, to); + + while(pathlib_open_cnt) + { + if((gettime(GETTIME_REALTIME) - pathlib_starttime) > pathlib_maxtime) + { + LOG_TRACE("Path took to long to compute!\n"); + LOG_TRACE("Nodes - created: ", ftos(pathlib_made_cnt),"\n"); + LOG_TRACE("Nodes - open: ", ftos(pathlib_open_cnt),"\n"); + LOG_TRACE("Nodes - merged: ", ftos(pathlib_merge_cnt),"\n"); + LOG_TRACE("Nodes - closed: ", ftos(pathlib_closed_cnt),"\n"); + + pathlib_cleanup(); + return world; + } + + best_open_node = pathlib_getbestopen(); + n = best_open_node; + pathlib_close_node(best_open_node,to); + + if(inwater(n.origin)) + pathlib_expandnode_box(n,from,to); + else + pathlib_expandnode(n,from,to); + + if(pathlib_foundgoal) + { + LOG_TRACE("Target found. Rebuilding and filtering path...\n"); + ftime = gettime(GETTIME_REALTIME); + ptime = ftime - ptime; + + start = path_build(world,path.origin,world,world); + end = path_build(world,goal_node.origin,world,start); + ln = end; + + open = goal_node; + for(open = goal_node; open.path_prev != path; open = open.path_prev) + { + n = path_build(ln,open.origin,open.path_prev,start); + ln.path_prev = n; + ln = n; + } + start.path_next = n; + n.path_prev = start; + ftime = gettime(GETTIME_REALTIME) - ftime; + + ctime = gettime(GETTIME_REALTIME); + pathlib_cleanup(); + ctime = gettime(GETTIME_REALTIME) - ctime; + + +#ifdef DEBUGPATHING + pathlib_showpath2(start); + + LOG_TRACE("Time used - pathfinding: ", ftos(ptime),"\n"); + LOG_TRACE("Time used - rebuild & filter: ", ftos(ftime),"\n"); + LOG_TRACE("Time used - cleanup: ", ftos(ctime),"\n"); + LOG_TRACE("Time used - total: ", ftos(ptime + ftime + ctime),"\n"); + LOG_TRACE("Time used - # frames: ", ftos(ceil((ptime + ftime + ctime) / sys_frametime)),"\n\n"); + LOG_TRACE("Nodes - created: ", ftos(pathlib_made_cnt),"\n"); + LOG_TRACE("Nodes - open: ", ftos(pathlib_open_cnt),"\n"); + LOG_TRACE("Nodes - merged: ", ftos(pathlib_merge_cnt),"\n"); + LOG_TRACE("Nodes - closed: ", ftos(pathlib_closed_cnt),"\n"); + LOG_TRACE("Nodes - searched: ", ftos(pathlib_searched_cnt),"\n"); + LOG_TRACE("Nodes bestopen searched: ", ftos(pathlib_bestopen_seached),"\n"); + LOG_TRACE("Nodes bestcash - hits: ", ftos(pathlib_bestcash_hits),"\n"); + LOG_TRACE("Nodes bestcash - save: ", ftos(pathlib_bestcash_saved),"\n"); + LOG_TRACE("AStar done.\n"); +#endif + return start; + } + } + + LOG_TRACE("A* Faild to find a path! Try a smaller gridsize.\n"); + + pathlib_cleanup(); + + return world; +} diff --git a/qcsrc/server/pathlib/main.qh b/qcsrc/server/pathlib/main.qh new file mode 100644 index 0000000000..177c432cf3 --- /dev/null +++ b/qcsrc/server/pathlib/main.qh @@ -0,0 +1,5 @@ +#ifndef PATHLIB_MAIN_H +#define PATHLIB_MAIN_H +float buildpath_nodefilter_none(vector n,vector c,vector p); +entity path_build(entity next, vector where, entity prev, entity start); +#endif diff --git a/qcsrc/server/pathlib/movenode.qc b/qcsrc/server/pathlib/movenode.qc new file mode 100644 index 0000000000..6645d71266 --- /dev/null +++ b/qcsrc/server/pathlib/movenode.qc @@ -0,0 +1,154 @@ +#include "../_all.qh" + +#include "pathlib.qh" +#include "utility.qh" + +vector pathlib_wateroutnode(vector start,vector end, float doedge) +{SELFPARAM(); + vector surface; + + pathlib_movenode_goodnode = 0; + + end.x = fsnap(end.x, pathlib_gridsize); + end.y = fsnap(end.y, pathlib_gridsize); + + traceline(end + ('0 0 0.25' * pathlib_gridsize),end - ('0 0 1' * pathlib_gridsize),MOVE_WORLDONLY,self); + end = trace_endpos; + + if (!(pointcontents(end - '0 0 1') == CONTENT_SOLID)) + return end; + + for(surface = start ; surface.z < (end.z + 32); ++surface.z) + { + if(pointcontents(surface) == CONTENT_EMPTY) + break; + } + + if(pointcontents(surface + '0 0 1') != CONTENT_EMPTY) + return end; + + tracebox(start + '0 0 64', movenode_boxmin,movenode_boxmax, end + '0 0 64', MOVE_WORLDONLY, self); + if(trace_fraction == 1) + pathlib_movenode_goodnode = 1; + + if(fabs(surface.z - end.z) > 32) + pathlib_movenode_goodnode = 0; + + return end; +} + +vector pathlib_swimnode(vector start,vector end, float doedge) +{SELFPARAM(); + pathlib_movenode_goodnode = 0; + + if(pointcontents(start) != CONTENT_WATER) + return end; + + end.x = fsnap(end.x, pathlib_gridsize); + end.y = fsnap(end.y, pathlib_gridsize); + + if(pointcontents(end) == CONTENT_EMPTY) + return pathlib_wateroutnode( start, end, doedge); + + tracebox(start, movenode_boxmin,movenode_boxmax, end, MOVE_WORLDONLY, self); + if(trace_fraction == 1) + pathlib_movenode_goodnode = 1; + + return end; +} + +vector pathlib_flynode(vector start,vector end, float doedge) +{SELFPARAM(); + pathlib_movenode_goodnode = 0; + + end.x = fsnap(end.x, pathlib_gridsize); + end.y = fsnap(end.y, pathlib_gridsize); + + tracebox(start, movenode_boxmin,movenode_boxmax, end, MOVE_WORLDONLY, self); + if(trace_fraction == 1) + pathlib_movenode_goodnode = 1; + + return end; +} + +void a_think() +{SELFPARAM(); + te_lightning1(self,self.origin, self.pos1); + if(self.cnt < time) + remove(self); + else + self.nextthink = time + 0.2; +} + +vector pathlib_walknode(vector start,vector end,float doedge) +{SELFPARAM(); + vector direction,point,last_point,s,e; + float steps, distance, i; + + LOG_TRACE("Walking node from ", vtos(start), " to ", vtos(end), "\n"); + + pathlib_movenode_goodnode = 0; + + end.x = fsnap(end.x,pathlib_gridsize); + end.y = fsnap(end.y,pathlib_gridsize); + start.x = fsnap(start.x,pathlib_gridsize); + start.y = fsnap(start.y,pathlib_gridsize); + + // Find the floor + traceline(start + movenode_stepup, start - movenode_maxdrop, MOVE_WORLDONLY, self); + if(trace_fraction == 1.0) + { + entity a; + a = spawn(); + a.think = a_think; + a.nextthink = time; + setorigin(a,start + movenode_stepup); + a.pos1 = trace_endpos; + //start - movenode_maxdrop + a.cnt = time + 10; + + LOG_TRACE("I cant walk on air!\n"); + return trace_endpos; + } + + start = trace_endpos; + + // Find the direcion, without Z + s = start; e = end; + //e_z = 0; s_z = 0; + direction = normalize(e - s); + + distance = vlen(start - end); + steps = rint(distance / movenode_stepsize); + + last_point = start; + for(i = 1; i < steps; ++i) + { + point = last_point + (direction * movenode_stepsize); + traceline(point + movenode_stepup,point - movenode_maxdrop,MOVE_WORLDONLY,self); + if(trace_fraction == 1.0) + return trace_endpos; + + last_point = trace_endpos; + } + + point = last_point + (direction * movenode_stepsize); + point.x = fsnap(point.x,pathlib_gridsize); + point.y = fsnap(point.y,pathlib_gridsize); + + //dprint("end_x: ",ftos(end_x), " end_y: ",ftos(end_y),"\n"); + //dprint("point_x:",ftos(point_x)," point_y:",ftos(point_y),"\n\n"); + + traceline(point + movenode_stepup, point - movenode_maxdrop,MOVE_WORLDONLY,self); + if(trace_fraction == 1.0) + return trace_endpos; + + last_point = trace_endpos; + + tracebox(start + movenode_boxup, movenode_boxmin,movenode_boxmax, last_point + movenode_boxup, MOVE_WORLDONLY, self); + if(trace_fraction != 1.0) + return trace_endpos; + + pathlib_movenode_goodnode = 1; + return last_point; +} diff --git a/qcsrc/server/pathlib/path_waypoint.qc b/qcsrc/server/pathlib/path_waypoint.qc new file mode 100644 index 0000000000..4c3a7a1aad --- /dev/null +++ b/qcsrc/server/pathlib/path_waypoint.qc @@ -0,0 +1,250 @@ +#include "../bot/waypoints.qh" + +#include "pathlib.qh" +#include "main.qh" + +var float pathlib_wpp_open(entity wp, entity child, float cost); + +void pathlib_wpp_close(entity wp) +{ + --pathlib_open_cnt; + ++pathlib_closed_cnt; + + wp.pathlib_list = closedlist; + + if(wp == best_open_node) + best_open_node = world; + + if(wp == goal_node) + pathlib_foundgoal = true; +} + +float pathlib_wpp_opencb(entity wp, entity child, float cost) +{ + + if(child.pathlib_list == closedlist) + return false; + + // FIXME! wp.wp##mincost is NOT distance. Make it distance or add a field for distance to be used here (for better speed) + cost = vlen(child.origin - wp.origin); + + child.path_prev = wp; + child.pathlib_list = openlist; + child.pathlib_node_g = wp.pathlib_node_g + cost; + child.pathlib_node_h = pathlib_heuristic(child.origin, goal_node.origin); + child.pathlib_node_c = pathlib_wpp_waypointcallback(child, wp); + child.pathlib_node_f = child.pathlib_node_g + child.pathlib_node_h + child.pathlib_node_c; + + + if(child == goal_node) + pathlib_foundgoal = true; + + ++pathlib_open_cnt; + + if(best_open_node.pathlib_node_f > child.pathlib_node_f) + best_open_node = child; + + return true; +} + +float pathlib_wpp_openncb(entity wp, entity child, float cost) +{ + + if(child.pathlib_list == closedlist) + return false; + + // FIXME! wp.wp##mincost is NOT distance. Make it distance or add a field for distance to be used here (for better speed) + cost = vlen(child.origin - wp.origin); + + child.path_prev = wp; + child.pathlib_list = openlist; + child.pathlib_node_g = wp.pathlib_node_g + cost; + child.pathlib_node_h = pathlib_heuristic(child.origin, goal_node.origin); + child.pathlib_node_f = child.pathlib_node_g + child.pathlib_node_h; + + if(child == goal_node) + pathlib_foundgoal = true; + + ++pathlib_open_cnt; + + if(best_open_node.pathlib_node_f > child.pathlib_node_f) + best_open_node = child; + + return true; +} + +float pathlib_wpp_expand(entity wp) +{ + if(wp.wp00) pathlib_wpp_open(wp,wp.wp00,wp.wp00mincost); else return 0; + if(wp.wp01) pathlib_wpp_open(wp,wp.wp01,wp.wp01mincost); else return 1; + if(wp.wp02) pathlib_wpp_open(wp,wp.wp02,wp.wp02mincost); else return 2; + if(wp.wp03) pathlib_wpp_open(wp,wp.wp03,wp.wp03mincost); else return 3; + if(wp.wp04) pathlib_wpp_open(wp,wp.wp04,wp.wp04mincost); else return 4; + if(wp.wp05) pathlib_wpp_open(wp,wp.wp05,wp.wp05mincost); else return 5; + if(wp.wp06) pathlib_wpp_open(wp,wp.wp06,wp.wp06mincost); else return 6; + if(wp.wp07) pathlib_wpp_open(wp,wp.wp07,wp.wp07mincost); else return 7; + if(wp.wp08) pathlib_wpp_open(wp,wp.wp08,wp.wp08mincost); else return 8; + if(wp.wp09) pathlib_wpp_open(wp,wp.wp09,wp.wp09mincost); else return 9; + if(wp.wp10) pathlib_wpp_open(wp,wp.wp10,wp.wp10mincost); else return 10; + if(wp.wp11) pathlib_wpp_open(wp,wp.wp11,wp.wp11mincost); else return 11; + if(wp.wp12) pathlib_wpp_open(wp,wp.wp12,wp.wp12mincost); else return 12; + if(wp.wp13) pathlib_wpp_open(wp,wp.wp13,wp.wp13mincost); else return 13; + if(wp.wp14) pathlib_wpp_open(wp,wp.wp14,wp.wp14mincost); else return 14; + if(wp.wp15) pathlib_wpp_open(wp,wp.wp15,wp.wp15mincost); else return 15; + if(wp.wp16) pathlib_wpp_open(wp,wp.wp16,wp.wp16mincost); else return 16; + if(wp.wp17) pathlib_wpp_open(wp,wp.wp17,wp.wp17mincost); else return 17; + if(wp.wp18) pathlib_wpp_open(wp,wp.wp18,wp.wp18mincost); else return 18; + if(wp.wp19) pathlib_wpp_open(wp,wp.wp19,wp.wp19mincost); else return 19; + if(wp.wp20) pathlib_wpp_open(wp,wp.wp20,wp.wp20mincost); else return 20; + if(wp.wp21) pathlib_wpp_open(wp,wp.wp21,wp.wp21mincost); else return 21; + if(wp.wp22) pathlib_wpp_open(wp,wp.wp22,wp.wp22mincost); else return 22; + if(wp.wp23) pathlib_wpp_open(wp,wp.wp23,wp.wp23mincost); else return 23; + if(wp.wp24) pathlib_wpp_open(wp,wp.wp24,wp.wp24mincost); else return 24; + if(wp.wp25) pathlib_wpp_open(wp,wp.wp25,wp.wp25mincost); else return 25; + if(wp.wp26) pathlib_wpp_open(wp,wp.wp26,wp.wp26mincost); else return 26; + if(wp.wp27) pathlib_wpp_open(wp,wp.wp27,wp.wp27mincost); else return 27; + if(wp.wp28) pathlib_wpp_open(wp,wp.wp28,wp.wp28mincost); else return 28; + if(wp.wp29) pathlib_wpp_open(wp,wp.wp29,wp.wp29mincost); else return 29; + if(wp.wp30) pathlib_wpp_open(wp,wp.wp30,wp.wp30mincost); else return 30; + if(wp.wp31) pathlib_wpp_open(wp,wp.wp31,wp.wp31mincost); else return 31; + + return 32; +} + +entity pathlib_wpp_bestopen() +{ + entity n, best; + + if(best_open_node) + return best_open_node; + + n = findchainentity(pathlib_list, openlist); + best = n; + while(n) + { + if(n.pathlib_node_f < best.pathlib_node_f) + best = n; + + n = n.chain; + } + + return best; + +} + +entity pathlib_waypointpath(entity wp_from, entity wp_to, float callback) +{ + entity n; + float ptime; + + ptime = gettime(GETTIME_REALTIME); + pathlib_starttime = ptime; + pathlib_movecost = 300; + pathlib_movecost_diag = vlen('1 1 0' * pathlib_movecost); + + if (!pathlib_wpp_waypointcallback) + callback = false; + + if (callback) + pathlib_wpp_open = pathlib_wpp_opencb; + else + pathlib_wpp_open = pathlib_wpp_openncb; + + pathlib_heuristic = pathlib_h_none; + + if (!openlist) + openlist = spawn(); + + if (!closedlist) + closedlist = spawn(); + + pathlib_closed_cnt = 0; + pathlib_open_cnt = 0; + pathlib_searched_cnt = 0; + pathlib_foundgoal = false; + + LOG_TRACE("pathlib_waypointpath init\n"); + + // Initialize waypoint grid + // FIXME! presisted chain for better preformance + for(n = findchain(classname, "waypoint"); n; n = n.chain) + { + n.pathlib_list = world; + n.pathlib_node_g = 0; + n.pathlib_node_f = 0; + n.pathlib_node_h = 0; + + //setmodel(n, "models/runematch/rune.mdl"); + //n.effects = EF_LOWPRECISION; + //n.colormod = '0 0 0'; + //n.scale = 1; + + } + + goal_node = wp_to; + start_node = wp_from; + + start_node.pathlib_list = closedlist; + LOG_TRACE("Expanding ",ftos(pathlib_wpp_expand(start_node))," links\n"); + if(pathlib_open_cnt <= 0) + { + LOG_TRACE("pathlib_waypointpath: Start waypoint not linked! aborting.\n"); + return world; + } + + return world; +} + +entity pathlib_waypointpath_step() +{ + entity n; + + n = pathlib_wpp_bestopen(); + if(!n) + { + LOG_TRACE("Cannot find best open node, abort.\n"); + return world; + } + pathlib_wpp_close(n); + LOG_TRACE("Expanding ",ftos(pathlib_wpp_expand(n))," links\n"); + + if(pathlib_foundgoal) + { + entity start, end, open, ln; + + LOG_TRACE("Target found. Rebuilding and filtering path...\n"); + + buildpath_nodefilter = buildpath_nodefilter_none; + start = path_build(world, start_node.origin, world, world); + end = path_build(world, goal_node.origin, world, start); + ln = end; + + for(open = goal_node; open.path_prev != start_node; open = open.path_prev) + { + n = path_build(ln,open.origin,open.path_prev,start); + ln.path_prev = n; + ln = n; + } + start.path_next = n; + n.path_prev = start; + + return start; + } + + return world; +} +void plas_think() +{SELFPARAM(); + pathlib_waypointpath_step(); + if(pathlib_foundgoal) + return; + self.nextthink = time + 0.1; +} + +void pathlib_waypointpath_autostep() +{ + entity n; + n = spawn(); + n.think = plas_think; + n.nextthink = time + 0.1; +} diff --git a/qcsrc/server/pathlib/pathlib.qh b/qcsrc/server/pathlib/pathlib.qh new file mode 100644 index 0000000000..dbf785266a --- /dev/null +++ b/qcsrc/server/pathlib/pathlib.qh @@ -0,0 +1,116 @@ +#ifndef PATHLIB_H +#define PATHLIB_H + +.entity pathlib_list; +.entity path_next; +.entity path_prev; + +#define inwater(point) (pointcontents(point) == CONTENT_WATER) +#define medium spawnshieldtime + +const vector PLIB_FORWARD = '0 1 0'; +//#define PLIB_BACK '0 -1 0' +const vector PLIB_RIGHT = '1 0 0'; +//#define PLIB_LEFT '-1 0 0' + +#define DEBUGPATHING +#ifdef DEBUGPATHING +void pathlib_showpath(entity start); +void pathlib_showpath2(entity path); +#endif + +entity openlist; +entity closedlist; + +entity goal_node; +entity start_node; + +.float is_path_node; +.float pathlib_node_g; +.float pathlib_node_h; +.float pathlib_node_f; +.float pathlib_node_c; + +const float pathlib_node_edgeflag_unknown = 0; +const float pathlib_node_edgeflag_left = 2; +const float pathlib_node_edgeflag_right = 4; +const float pathlib_node_edgeflag_forward = 8; +const float pathlib_node_edgeflag_back = 16; +const float pathlib_node_edgeflag_backleft = 32; +const float pathlib_node_edgeflag_backright = 64; +const float pathlib_node_edgeflag_forwardleft = 128; +const float pathlib_node_edgeflag_forwardright = 256; +const float pathlib_node_edgeflag_none = 512; +.float pathlib_node_edgeflags; + +float pathlib_open_cnt; +float pathlib_closed_cnt; +float pathlib_made_cnt; +float pathlib_merge_cnt; +float pathlib_searched_cnt; +float pathlib_bestopen_seached; +float pathlib_bestcash_hits; +float pathlib_bestcash_saved; +float pathlib_gridsize; +float pathlib_movecost; +float pathlib_movecost_diag; +float pathlib_movecost_waterfactor; +float pathlib_foundgoal; + +float pathlib_starttime; +const float pathlib_maxtime = 60; + +entity best_open_node; + +vector tile_check_up; +vector tile_check_down; +float tile_check_size; +float tile_check_cross(vector where); +float tile_check_plus(vector where); +float tile_check_star(vector where); +var float tile_check(vector where); + +float movenode_stepsize; +vector movenode_stepup; +vector movenode_maxdrop; +vector movenode_boxup; +vector movenode_boxmax; +vector movenode_boxmin; +float pathlib_movenode_goodnode; + +vector pathlib_wateroutnode(vector start, vector end, float doedge); +vector pathlib_swimnode(vector start, vector end, float doedge); +vector pathlib_flynode(vector start, vector end, float doedge); +vector pathlib_walknode(vector start, vector end, float doedge); +var vector pathlib_movenode(vector start, vector end, float doedge); + +float pathlib_expandnode_star(entity node, vector start, vector goal); +float pathlib_expandnode_box(entity node, vector start, vector goal); +float pathlib_expandnode_octagon(entity node, vector start, vector goal); +var float pathlib_expandnode(entity node, vector start, vector goal); + +float pathlib_g_static(entity parent, vector to, float static_cost); +float pathlib_g_static_water(entity parent, vector to, float static_cost); +float pathlib_g_euclidean(entity parent, vector to, float static_cost); +float pathlib_g_euclidean_water(entity parent, vector to, float static_cost); +var float pathlib_cost(entity parent, vector to, float static_cost); + +float pathlib_h_manhattan(vector a, vector b); +float pathlib_h_diagonal(vector a, vector b); +float pathlib_h_euclidean(vector a,vector b); +float pathlib_h_diagonal2(vector a, vector b); +float pathlib_h_diagonal3(vector a, vector b); +float pathlib_h_diagonal2sdp(vector preprev, vector prev, vector point, vector end); +float pathlib_h_none(vector preprev, vector prev) { return 0; } +var float pathlib_heuristic(vector from, vector to); + +var float pathlib_makenode(entity parent,vector start, vector to, vector goal,float cost); +var float buildpath_nodefilter(vector n,vector c,vector p); + +var float pathlib_wpp_waypointcallback(entity wp, entity wp_prev); + +#ifdef DEBUGPATHING + #include "debug.qc" +#endif + +#endif diff --git a/qcsrc/server/pathlib/utility.qc b/qcsrc/server/pathlib/utility.qc new file mode 100644 index 0000000000..9028f85e4f --- /dev/null +++ b/qcsrc/server/pathlib/utility.qc @@ -0,0 +1,245 @@ +#include "utility.qh" + +#include "pathlib.qh" + +float location_isok(vector point, float water_isok, float air_isok) +{ + float pc,pc2; + + if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_SKY) + return 0; + + pc = pointcontents(point); + pc2 = pointcontents(point - '0 0 1'); + + switch(pc) + { + case CONTENT_SOLID: + break; + + case CONTENT_SLIME: + break; + + case CONTENT_LAVA: + break; + + case CONTENT_SKY: + break; + + case CONTENT_EMPTY: + if (pc2 == CONTENT_SOLID) + return 1; + + if (pc2 == CONTENT_EMPTY) + if(air_isok) + return 1; + + if (pc2 == CONTENT_WATER) + if(water_isok) + return 1; + + break; + + case CONTENT_WATER: + if (water_isok) + return 1; + + break; + } + + return 0; +} + +entity pathlib_nodeatpoint(vector where) +{ + entity node; + + ++pathlib_searched_cnt; + + where.x = fsnap(where.x,pathlib_gridsize); + where.y = fsnap(where.y,pathlib_gridsize); + + node = findradius(where,pathlib_gridsize * 0.5); + while(node) + { + if(node.is_path_node == true) + return node; + + node = node.chain; + } + + return world; +} + +float tile_check_cross(vector where) +{SELFPARAM(); + vector p,f,r; + + f = PLIB_FORWARD * tile_check_size; + r = PLIB_RIGHT * tile_check_size; + + + // forward-right + p = where + f + r; + traceline(p + tile_check_up, p - tile_check_down, MOVE_WORLDONLY, self); + if (!location_isok(trace_endpos, 1, 0)) + return 0; + + // Forward-left + p = where + f - r; + traceline(p + tile_check_up, p - tile_check_down, MOVE_WORLDONLY, self); + if (!location_isok(trace_endpos, 1, 0)) + return 0; + + // Back-right + p = where - f + r; + traceline(p + tile_check_up, p - tile_check_down, MOVE_WORLDONLY, self); + if (!location_isok(trace_endpos, 1 ,0)) + return 0; + + //Back-left + p = where - f - r; + traceline(p + tile_check_up, p - tile_check_down, MOVE_WORLDONLY, self); + if (!location_isok(trace_endpos, 1, 0)) + return 0; + + return 1; +} + +float tile_check_plus(vector where) +{SELFPARAM(); + vector p,f,r; + + f = PLIB_FORWARD * tile_check_size; + r = PLIB_RIGHT * tile_check_size; + + // forward + p = where + f; + traceline(p+tile_check_up,p-tile_check_down,MOVE_WORLDONLY,self); + if (!location_isok(trace_endpos,1,0)) + return 0; + + + //left + p = where - r; + traceline(p+tile_check_up,p-tile_check_down,MOVE_WORLDONLY,self); + if (!location_isok(trace_endpos,1,0)) + return 0; + + // Right + p = where + r; + traceline(p+tile_check_up,p-tile_check_down,MOVE_WORLDONLY,self); + if (!location_isok(trace_endpos,1,0)) + return 0; + + //Back + p = where - f; + traceline(p+tile_check_up,p-tile_check_down,MOVE_WORLDONLY,self); + if (!location_isok(trace_endpos,1,0)) + return 0; + + return 1; +} + +float tile_check_plus2(vector where) +{SELFPARAM(); + vector p,f,r; + float i = 0, e = 0; + + f = PLIB_FORWARD * pathlib_gridsize; + r = PLIB_RIGHT * pathlib_gridsize; + +//#define pathlib_node_edgeflag_left 2 +//#define pathlib_node_edgeflag_right 4 +//#define pathlib_node_edgeflag_forward 8 +//#define pathlib_node_edgeflag_back 16 + + // forward + p = where + f; + traceline(p+tile_check_up,p-tile_check_down,MOVE_WORLDONLY,self); + if (location_isok(trace_endpos,1,0)) + { + ++i; + e |= pathlib_node_edgeflag_forward; + } + + + //left + p = where - r; + traceline(p+tile_check_up,p-tile_check_down,MOVE_WORLDONLY,self); + if (location_isok(trace_endpos,1,0)) + { + ++i; + e |= pathlib_node_edgeflag_left; + } + + + // Right + p = where + r; + traceline(p+tile_check_up,p-tile_check_down,MOVE_WORLDONLY,self); + if (location_isok(trace_endpos,1,0)) + { + ++i; + e |= pathlib_node_edgeflag_right; + } + + //Back + p = where - f; + traceline(p+tile_check_up,p-tile_check_down,MOVE_WORLDONLY,self); + if (location_isok(trace_endpos,1,0)) + { + ++i; + e |= pathlib_node_edgeflag_back; + } + + // forward-right + p = where + f + r; + traceline(p + tile_check_up, p - tile_check_down, MOVE_WORLDONLY, self); + if (location_isok(trace_endpos, 1, 0)) + { + ++i; + e |= pathlib_node_edgeflag_forwardright; + } + + // Forward-left + p = where + f - r; + traceline(p + tile_check_up, p - tile_check_down, MOVE_WORLDONLY, self); + if (location_isok(trace_endpos, 1, 0)) + { + ++i; + e |= pathlib_node_edgeflag_forwardleft; + } + + // Back-right + p = where - f + r; + traceline(p + tile_check_up, p - tile_check_down, MOVE_WORLDONLY, self); + if (location_isok(trace_endpos, 1 ,0)) + { + ++i; + e |= pathlib_node_edgeflag_backright; + } + + //Back-left + p = where - f - r; + traceline(p + tile_check_up, p - tile_check_down, MOVE_WORLDONLY, self); + if (location_isok(trace_endpos, 1, 0)) + { + ++i; + e |= pathlib_node_edgeflag_backleft; + } + + + if(i == 0) + e = pathlib_node_edgeflag_none; + + return e; +} + +float tile_check_star(vector where) +{ + if(tile_check_plus(where)) + return tile_check_cross(where); + + return 0; +} + diff --git a/qcsrc/server/pathlib/utility.qh b/qcsrc/server/pathlib/utility.qh new file mode 100644 index 0000000000..bf72549a03 --- /dev/null +++ b/qcsrc/server/pathlib/utility.qh @@ -0,0 +1,6 @@ +#ifndef PATHLIB_UTILITY +#define PATHLIB_UTILITY +float fsnap(float val,float fsize); +entity pathlib_nodeatpoint(vector where); +float tile_check_plus2(vector where); +#endif diff --git a/qcsrc/server/progs.inc b/qcsrc/server/progs.inc index ad1e855e50..adb8cf6323 100644 --- a/qcsrc/server/progs.inc +++ b/qcsrc/server/progs.inc @@ -7,8 +7,6 @@ #include "../lib/_all.inc" -#include "bot/lib/steerlib/steerlib.qh" - #include "anticheat.qc" #include "antilag.qc" #include "campaign.qc" @@ -31,6 +29,7 @@ #include "item_key.qc" #include "mapvoting.qc" #include "miscfunctions.qc" +#include "movelib.qc" #include "playerdemo.qc" #include "portals.qc" #include "race.qc" @@ -38,21 +37,36 @@ #include "scores.qc" #include "scores_rules.qc" #include "spawnpoints.qc" +#include "steerlib.qc" #include "sv_main.qc" #include "teamplay.qc" - -#include "bot/lib/all.inc" - #include "t_halflife.qc" #include "t_items.qc" #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 "mutators/mutators.qc" +#include "pathlib/costs.qc" +#include "pathlib/expandnode.qc" +#include "pathlib/main.qc" +#include "pathlib/movenode.qc" +#include "pathlib/path_waypoint.qc" +#include "pathlib/utility.qc" + #include "weapons/accuracy.qc" #include "weapons/common.qc" #include "weapons/csqcprojectile.qc" // TODO @@ -106,8 +120,6 @@ #include "../warpzonelib/server.qc" #include "../warpzonelib/util_server.qc" -#include "bot/impl.qc" - #if BUILD_MOD #include "../../mod/server/progs.inc" #endif diff --git a/qcsrc/server/race.qc b/qcsrc/server/race.qc index 4c451caa73..fbf1eaa185 100644 --- a/qcsrc/server/race.qc +++ b/qcsrc/server/race.qc @@ -5,6 +5,8 @@ #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" diff --git a/qcsrc/server/steerlib.qc b/qcsrc/server/steerlib.qc new file mode 100644 index 0000000000..fbf84da32b --- /dev/null +++ b/qcsrc/server/steerlib.qc @@ -0,0 +1,672 @@ +#if defined(CSQC) +#elif defined(MENUQC) +#elif defined(SVQC) + #include "../dpdefs/progsdefs.qh" + #include "../dpdefs/dpextensions.qh" +#endif + +/** + Uniform pull towards a point +**/ +vector steerlib_pull(vector point) +{SELFPARAM(); + return normalize(point - self.origin); +} + +/** + Uniform push from a point +**/ +#define steerlib_push(point) normalize(self.origin - point) +/* +vector steerlib_push(vector point) +{ + return normalize(self.origin - point); +} +*/ +/** + Pull toward a point, The further away, the stronger the pull. +**/ +vector steerlib_arrive(vector point,float maximal_distance) +{SELFPARAM(); + float distance; + vector direction; + + distance = bound(0.001,vlen(self.origin - point),maximal_distance); + direction = normalize(point - self.origin); + return direction * (distance / maximal_distance); +} + +/** + Pull toward a point increasing the pull the closer we get +**/ +vector steerlib_attract(vector point, float maximal_distance) +{SELFPARAM(); + float distance; + vector direction; + + distance = bound(0.001,vlen(self.origin - point),maximal_distance); + direction = normalize(point - self.origin); + + return direction * (1-(distance / maximal_distance)); +} + +vector steerlib_attract2(vector point, float min_influense,float max_distance,float max_influense) +{SELFPARAM(); + float distance; + vector direction; + float influense; + + distance = bound(0.00001,vlen(self.origin - point),max_distance); + direction = normalize(point - self.origin); + + influense = 1 - (distance / max_distance); + influense = min_influense + (influense * (max_influense - min_influense)); + + return direction * influense; +} + +/* +vector steerlib_attract2(vector point, float maximal_distance,float min_influense,float max_influense,float distance) +{ + //float distance; + vector current_direction; + vector target_direction; + float i_target,i_current; + + if(!distance) + distance = vlen(self.origin - point); + + distance = bound(0.001,distance,maximal_distance); + + target_direction = normalize(point - self.origin); + current_direction = normalize(self.velocity); + + i_target = bound(min_influense,(1-(distance / maximal_distance)),max_influense); + i_current = 1 - i_target; + + // i_target = bound(min_influense,(1-(distance / maximal_distance)),max_influense); + + string s; + s = ftos(i_target); + bprint("IT: ",s,"\n"); + s = ftos(i_current); + bprint("IC : ",s,"\n"); + + return normalize((target_direction * i_target) + (current_direction * i_current)); +} +*/ +/** + Move away from a point. +**/ +vector steerlib_repell(vector point,float maximal_distance) +{SELFPARAM(); + float distance; + vector direction; + + distance = bound(0.001,vlen(self.origin - point),maximal_distance); + direction = normalize(self.origin - point); + + return direction * (1-(distance / maximal_distance)); +} + +/** + Try to keep at ideal_distance away from point +**/ +vector steerlib_standoff(vector point,float ideal_distance) +{SELFPARAM(); + float distance; + vector direction; + + distance = vlen(self.origin - point); + + + if(distance < ideal_distance) + { + direction = normalize(self.origin - point); + return direction * (distance / ideal_distance); + } + + direction = normalize(point - self.origin); + return direction * (ideal_distance / distance); + +} + +/** + A random heading in a forward halfcicrle + + use like: + self.target = steerlib_wander(256,32,self.target) + + where range is the cicrle radius and tresh is how close we need to be to pick a new heading. +**/ +vector steerlib_wander(float range,float tresh,vector oldpoint) +{SELFPARAM(); + vector wander_point; + wander_point = v_forward - oldpoint; + + if (vlen(wander_point) > tresh) + return oldpoint; + + range = bound(0,range,1); + + wander_point = self.origin + v_forward * 128; + wander_point = wander_point + randomvec() * (range * 128) - randomvec() * (range * 128); + + return normalize(wander_point - self.origin); +} + +/** + Dodge a point. dont work to well. +**/ +vector steerlib_dodge(vector point,vector dodge_dir,float min_distance) +{SELFPARAM(); + float distance; + + distance = max(vlen(self.origin - point),min_distance); + if (min_distance < distance) + return '0 0 0'; + + return dodge_dir * (min_distance/distance); +} + +/** + flocking by .flock_id + Group will move towards the unified direction while keeping close to eachother. +**/ +.float flock_id; +vector steerlib_flock(float _radius, float standoff,float separation_force,float flock_force) +{SELFPARAM(); + entity flock_member; + vector push = '0 0 0', pull = '0 0 0'; + float ccount = 0; + + flock_member = findradius(self.origin, _radius); + while(flock_member) + { + if(flock_member != self) + if(flock_member.flock_id == self.flock_id) + { + ++ccount; + push = push + (steerlib_repell(flock_member.origin,standoff) * separation_force); + pull = pull + (steerlib_arrive(flock_member.origin + flock_member.velocity, _radius) * flock_force); + } + flock_member = flock_member.chain; + } + return push + (pull* (1 / ccount)); +} + +/** + flocking by .flock_id + Group will move towards the unified direction while keeping close to eachother. + xy only version (for ground movers). +**/ +vector steerlib_flock2d(float _radius, float standoff,float separation_force,float flock_force) +{SELFPARAM(); + entity flock_member; + vector push = '0 0 0', pull = '0 0 0'; + float ccount = 0; + + flock_member = findradius(self.origin,_radius); + while(flock_member) + { + if(flock_member != self) + if(flock_member.flock_id == self.flock_id) + { + ++ccount; + push = push + (steerlib_repell(flock_member.origin, standoff) * separation_force); + pull = pull + (steerlib_arrive(flock_member.origin + flock_member.velocity, _radius) * flock_force); + } + flock_member = flock_member.chain; + } + + push.z = 0; + pull.z = 0; + + return push + (pull * (1 / ccount)); +} + +/** + All members want to be in the center, and keep away from eachother. + The furtehr form the center the more they want to be there. + + This results in a aligned movement (?!) much like flocking. +**/ +vector steerlib_swarm(float _radius, float standoff,float separation_force,float swarm_force) +{SELFPARAM(); + entity swarm_member; + vector force = '0 0 0', center = '0 0 0'; + float ccount = 0; + + swarm_member = findradius(self.origin,_radius); + + while(swarm_member) + { + if(swarm_member.flock_id == self.flock_id) + { + ++ccount; + center = center + swarm_member.origin; + force = force + (steerlib_repell(swarm_member.origin,standoff) * separation_force); + } + swarm_member = swarm_member.chain; + } + + center = center * (1 / ccount); + force = force + (steerlib_arrive(center,_radius) * swarm_force); + + return force; +} + +/** + Steer towards the direction least obstructed. + Run four tracelines in a forward funnel, bias each diretion negative if something is found there. + You need to call makevectors() (or equivalent) before this function to set v_forward and v_right +**/ +vector steerlib_traceavoid(float pitch,float length) +{SELFPARAM(); + vector vup_left,vup_right,vdown_left,vdown_right; + float fup_left,fup_right,fdown_left,fdown_right; + vector upwish,downwish,leftwish,rightwish; + vector v_left,v_down; + + + v_left = v_right * -1; + v_down = v_up * -1; + + vup_left = (v_forward + (v_left * pitch + v_up * pitch)) * length; + traceline(self.origin, self.origin + vup_left,MOVE_NOMONSTERS,self); + fup_left = trace_fraction; + + //te_lightning1(world,self.origin, trace_endpos); + + vup_right = (v_forward + (v_right * pitch + v_up * pitch)) * length; + traceline(self.origin,self.origin + vup_right ,MOVE_NOMONSTERS,self); + fup_right = trace_fraction; + + //te_lightning1(world,self.origin, trace_endpos); + + vdown_left = (v_forward + (v_left * pitch + v_down * pitch)) * length; + traceline(self.origin,self.origin + vdown_left,MOVE_NOMONSTERS,self); + fdown_left = trace_fraction; + + //te_lightning1(world,self.origin, trace_endpos); + + vdown_right = (v_forward + (v_right * pitch + v_down * pitch)) * length; + traceline(self.origin,self.origin + vdown_right,MOVE_NOMONSTERS,self); + fdown_right = trace_fraction; + + //te_lightning1(world,self.origin, trace_endpos); + upwish = v_up * (fup_left + fup_right); + downwish = v_down * (fdown_left + fdown_right); + leftwish = v_left * (fup_left + fdown_left); + rightwish = v_right * (fup_right + fdown_right); + + return (upwish+leftwish+downwish+rightwish) * 0.25; + +} + +/** + Steer towards the direction least obstructed. + Run tracelines in a forward trident, bias each direction negative if something is found there. +**/ +vector steerlib_traceavoid_flat(float pitch, float length, vector vofs) +{SELFPARAM(); + vector vt_left, vt_right,vt_front; + float f_left, f_right,f_front; + vector leftwish, rightwish,frontwish, v_left; + + v_left = v_right * -1; + + + vt_front = v_forward * length; + traceline(self.origin + vofs, self.origin + vofs + vt_front,MOVE_NOMONSTERS,self); + f_front = trace_fraction; + + vt_left = (v_forward + (v_left * pitch)) * length; + traceline(self.origin + vofs, self.origin + vofs + vt_left,MOVE_NOMONSTERS,self); + f_left = trace_fraction; + + //te_lightning1(world,self.origin, trace_endpos); + + vt_right = (v_forward + (v_right * pitch)) * length; + traceline(self.origin + vofs, self.origin + vofs + vt_right ,MOVE_NOMONSTERS,self); + f_right = trace_fraction; + + //te_lightning1(world,self.origin, trace_endpos); + + leftwish = v_left * f_left; + rightwish = v_right * f_right; + frontwish = v_forward * f_front; + + return normalize(leftwish + rightwish + frontwish); +} + +float beamsweep_badpoint(vector point,float waterok) +{ + float pc,pc2; + + if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_SKY) + return 1; + + pc = pointcontents(point); + pc2 = pointcontents(point - '0 0 1'); + + switch(pc) + { + case CONTENT_SOLID: break; + case CONTENT_SLIME: break; + case CONTENT_LAVA: break; + + case CONTENT_SKY: + return 1; + + case CONTENT_EMPTY: + if (pc2 == CONTENT_SOLID) + return 0; + + if (pc2 == CONTENT_WATER) + if(waterok) + return 0; + + break; + + case CONTENT_WATER: + if(waterok) + return 0; + + break; + } + + return 1; +} + +//#define BEAMSTEER_VISUAL +float beamsweep(vector from, vector dir,float length, float step,float step_up, float step_down) +{SELFPARAM(); + float i; + vector a,b,u,d; + + u = '0 0 1' * step_up; + d = '0 0 1' * step_down; + + traceline(from + u, from - d,MOVE_NORMAL,self); + if(trace_fraction == 1.0) + return 0; + + if(beamsweep_badpoint(trace_endpos,0)) + return 0; + + a = trace_endpos; + for(i = 0; i < length; i += step) + { + + b = a + dir * step; + tracebox(a + u,'-4 -4 -4','4 4 4', b + u,MOVE_NORMAL,self); + if(trace_fraction != 1.0) + return i / length; + + traceline(b + u, b - d,MOVE_NORMAL,self); + if(trace_fraction == 1.0) + return i / length; + + if(beamsweep_badpoint(trace_endpos,0)) + return i / length; +#ifdef BEAMSTEER_VISUAL + te_lightning1(world,a+u,b+u); + te_lightning1(world,b+u,b-d); +#endif + a = trace_endpos; + } + + return 1; +} + +vector steerlib_beamsteer(vector dir, float length, float step, float step_up, float step_down) +{SELFPARAM(); + float bm_forward, bm_right, bm_left,p; + vector vr,vl; + + dir.z *= 0.15; + vr = vectoangles(dir); + //vr_x *= -1; + + tracebox(self.origin + '0 0 1' * step_up, self.mins, self.maxs, ('0 0 1' * step_up) + self.origin + (dir * length), MOVE_NOMONSTERS, self); + if(trace_fraction == 1.0) + { + //te_lightning1(self,self.origin,self.origin + (dir * length)); + return dir; + } + + + + + makevectors(vr); + bm_forward = beamsweep(self.origin, v_forward, length, step, step_up, step_down); + + vr = normalize(v_forward + v_right * 0.125); + vl = normalize(v_forward - v_right * 0.125); + + bm_right = beamsweep(self.origin, vr, length, step, step_up, step_down); + bm_left = beamsweep(self.origin, vl, length, step, step_up, step_down); + + + p = bm_left + bm_right; + if(p == 2) + { + //te_lightning1(self,self.origin + '0 0 32',self.origin + '0 0 32' + vr * length); + //te_lightning1(self.tur_head,self.origin + '0 0 32',self.origin + '0 0 32' + vl * length); + + return v_forward; + } + + p = 2 - p; + + vr = normalize(v_forward + v_right * p); + vl = normalize(v_forward - v_right * p); + bm_right = beamsweep(self.origin, vr, length, step, step_up, step_down); + bm_left = beamsweep(self.origin, vl, length, step, step_up, step_down); + + + if(bm_left + bm_right < 0.15) + { + vr = normalize((v_forward*-1) + v_right * 0.75); + vl = normalize((v_forward*-1) - v_right * 0.75); + + bm_right = beamsweep(self.origin, vr, length, step, step_up, step_down); + bm_left = beamsweep(self.origin, vl, length, step, step_up, step_down); + } + + //te_lightning1(self,self.origin + '0 0 32',self.origin + '0 0 32' + vr * length); + //te_lightning1(self.tur_head,self.origin + '0 0 32',self.origin + '0 0 32' + vl * length); + + bm_forward *= bm_forward; + bm_right *= bm_right; + bm_left *= bm_left; + + vr = vr * bm_right; + vl = vl * bm_left; + + return normalize(vr + vl); + +} + + +////////////////////////////////////////////// +// Testting // +// Everything below this point is a mess :D // +////////////////////////////////////////////// +//#define TLIBS_TETSLIBS +#ifdef TLIBS_TETSLIBS +void flocker_die() +{SELFPARAM(); + Send_Effect(EFFECT_ROCKET_EXPLODE, self.origin, '0 0 0', 1); + + self.owner.cnt += 1; + self.owner = world; + + self.nextthink = time; + self.think = SUB_Remove; +} + + +void flocker_think() +{SELFPARAM(); + vector dodgemove,swarmmove; + vector reprellmove,wandermove,newmove; + + self.angles_x = self.angles.x * -1; + makevectors(self.angles); + self.angles_x = self.angles.x * -1; + + dodgemove = steerlib_traceavoid(0.35,1000); + swarmmove = steerlib_flock(500,75,700,500); + reprellmove = steerlib_repell(self.owner.enemy.origin+self.enemy.velocity,2000) * 700; + + if(vlen(dodgemove) == 0) + { + self.pos1 = steerlib_wander(0.5,0.1,self.pos1); + wandermove = self.pos1 * 50; + } + else + self.pos1 = normalize(self.velocity); + + dodgemove = dodgemove * vlen(self.velocity) * 5; + + newmove = swarmmove + reprellmove + wandermove + dodgemove; + self.velocity = movelib_inertmove_byspeed(newmove,300,0.2,0.9); + //self.velocity = movelib_inertmove(dodgemove,0.65); + + self.velocity = movelib_dragvec(0.01,0.6); + + self.angles = vectoangles(self.velocity); + + if(self.health <= 0) + flocker_die(); + else + self.nextthink = time + 0.1; +} + +MODEL(FLOCKER, "models/turrets/rocket.md3"); + +void spawn_flocker() +{SELFPARAM(); + entity flocker; + + flocker = spawn (); + + setorigin(flocker, self.origin + '0 0 32'); + setmodel (flocker, MDL_FLOCKER); + setsize (flocker, '-3 -3 -3', '3 3 3'); + + flocker.flock_id = self.flock_id; + flocker.classname = "flocker"; + flocker.owner = self; + flocker.think = flocker_think; + flocker.nextthink = time + random() * 5; + PROJECTILE_MAKETRIGGER(flocker); + flocker.movetype = MOVETYPE_BOUNCEMISSILE; + flocker.effects = EF_LOWPRECISION; + flocker.velocity = randomvec() * 300; + flocker.angles = vectoangles(flocker.velocity); + flocker.health = 10; + flocker.pos1 = normalize(flocker.velocity + randomvec() * 0.1); + + self.cnt = self.cnt -1; + +} + +void flockerspawn_think() +{SELFPARAM(); + + + if(self.cnt > 0) + spawn_flocker(); + + self.nextthink = time + self.delay; + +} + +void flocker_hunter_think() +{SELFPARAM(); + vector dodgemove,attractmove,newmove; + entity e,ee; + float d,bd; + + self.angles_x = self.angles.x * -1; + makevectors(self.angles); + self.angles_x = self.angles.x * -1; + + if(self.enemy) + if(vlen(self.enemy.origin - self.origin) < 64) + { + ee = self.enemy; + ee.health = -1; + self.enemy = world; + + } + + if(!self.enemy) + { + e = findchainfloat(flock_id,self.flock_id); + while(e) + { + d = vlen(self.origin - e.origin); + + if(e != self.owner) + if(e != ee) + if(d > bd) + { + self.enemy = e; + bd = d; + } + e = e.chain; + } + } + + if(self.enemy) + attractmove = steerlib_attract(self.enemy.origin+self.enemy.velocity * 0.1,5000) * 1250; + else + attractmove = normalize(self.velocity) * 200; + + dodgemove = steerlib_traceavoid(0.35,1500) * vlen(self.velocity); + + newmove = dodgemove + attractmove; + self.velocity = movelib_inertmove_byspeed(newmove,1250,0.3,0.7); + self.velocity = movelib_dragvec(0.01,0.5); + + + self.angles = vectoangles(self.velocity); + self.nextthink = time + 0.1; +} + + +float globflockcnt; +spawnfunc(flockerspawn) +{SELFPARAM(); + ++globflockcnt; + + if(!self.cnt) self.cnt = 20; + if(!self.delay) self.delay = 0.25; + if(!self.flock_id) self.flock_id = globflockcnt; + + self.think = flockerspawn_think; + self.nextthink = time + 0.25; + + self.enemy = spawn(); + + setmodel(self.enemy, MDL_FLOCKER); + setorigin(self.enemy,self.origin + '0 0 768' + (randomvec() * 128)); + + self.enemy.classname = "FLock Hunter"; + self.enemy.scale = 3; + self.enemy.effects = EF_LOWPRECISION; + self.enemy.movetype = MOVETYPE_BOUNCEMISSILE; + PROJECTILE_MAKETRIGGER(self.enemy); + self.enemy.think = flocker_hunter_think; + self.enemy.nextthink = time + 10; + self.enemy.flock_id = self.flock_id; + self.enemy.owner = self; +} +#endif + + + diff --git a/qcsrc/server/steerlib.qh b/qcsrc/server/steerlib.qh new file mode 100644 index 0000000000..fcd35ba783 --- /dev/null +++ b/qcsrc/server/steerlib.qh @@ -0,0 +1,10 @@ +#ifndef STEERLIB_H +#define STEERLIB_H + +.vector steerto; + +vector steerlib_arrive(vector point,float maximal_distance); +vector steerlib_attract2(vector point, float min_influense,float max_distance,float max_influense); +vector steerlib_pull(vector point); + +#endif diff --git a/qcsrc/server/sv_main.qc b/qcsrc/server/sv_main.qc index 9dfd6b807d..138051827b 100644 --- a/qcsrc/server/sv_main.qc +++ b/qcsrc/server/sv_main.qc @@ -4,6 +4,9 @@ #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" diff --git a/qcsrc/server/t_items.qc b/qcsrc/server/t_items.qc index 964b05e1bd..0a5785037f 100644 --- a/qcsrc/server/t_items.qc +++ b/qcsrc/server/t_items.qc @@ -5,6 +5,9 @@ #if defined(SVQC) #include "_all.qh" + #include "bot/bot.qh" + #include "bot/waypoints.qh" + #include "mutators/mutators_include.qh" #include "weapons/common.qh" diff --git a/qcsrc/server/teamplay.qc b/qcsrc/server/teamplay.qc index 35a764c56d..2fbbd50b32 100644 --- a/qcsrc/server/teamplay.qc +++ b/qcsrc/server/teamplay.qc @@ -6,6 +6,8 @@ #include "scores.qh" #include "scores_rules.qh" +#include "bot/bot.qh" + #include "command/vote.qh" #include "mutators/mutators_include.qh"