[nexuiz-commits] r7851 - in trunk/data/qcsrc/server: . bot bot/havocbot
DONOTREPLY at icculus.org
DONOTREPLY at icculus.org
Sun Sep 20 17:43:30 EDT 2009
Author: mand1nga
Date: 2009-09-20 17:43:29 -0400 (Sun, 20 Sep 2009)
New Revision: 7851
Added:
trunk/data/qcsrc/server/bot/
trunk/data/qcsrc/server/bot/aim.qc
trunk/data/qcsrc/server/bot/aim.qh
trunk/data/qcsrc/server/bot/bot.qc
trunk/data/qcsrc/server/bot/bot.qh
trunk/data/qcsrc/server/bot/havocbot/
trunk/data/qcsrc/server/bot/havocbot/havocbot.qc
trunk/data/qcsrc/server/bot/havocbot/havocbot.qh
trunk/data/qcsrc/server/bot/havocbot/role_ctf.qc
trunk/data/qcsrc/server/bot/havocbot/role_keyhunt.qc
trunk/data/qcsrc/server/bot/havocbot/role_onslaught.qc
trunk/data/qcsrc/server/bot/havocbot/roles.qc
trunk/data/qcsrc/server/bot/navigation.qc
trunk/data/qcsrc/server/bot/navigation.qh
trunk/data/qcsrc/server/bot/scripting.qc
trunk/data/qcsrc/server/bot/waypoints.qc
trunk/data/qcsrc/server/bot/waypoints.qh
Removed:
trunk/data/qcsrc/server/bots.qc
trunk/data/qcsrc/server/bots_scripting.qc
trunk/data/qcsrc/server/havocbot.qc
trunk/data/qcsrc/server/havocbot_ctf.qc
trunk/data/qcsrc/server/havocbot_ons.qc
trunk/data/qcsrc/server/havocbot_roles.qc
Modified:
trunk/data/qcsrc/server/progs.src
Log:
Major reorganization of bots code (not finished yet)
Copied: trunk/data/qcsrc/server/bot/aim.qc (from rev 7849, trunk/data/qcsrc/server/bots.qc)
===================================================================
--- trunk/data/qcsrc/server/bot/aim.qc (rev 0)
+++ trunk/data/qcsrc/server/bot/aim.qc 2009-09-20 21:43:29 UTC (rev 7851)
@@ -0,0 +1,387 @@
+
+// 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)
+{
+ local float c, savesolid, shottime;
+ local vector dir, end, v;
+ 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;
+ shottime = ((vlen(targ.origin - org) / shotspeed) + shotdelay);
+ v = targ.velocity * shottime + targ.origin;
+ tracebox(targ.origin, 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()
+{
+ float i;
+ for(i=0;i<LAG_QUEUE_LENGTH;++i)
+ {
+ if (self.lag_time[i])
+ if (time > self.lag_time[i])
+ {
+ self.lag_func(
+ self.lag_time[i], self.lag_float1[i], self.lag_float2[i],
+ self.lag_entity1[i], self.lag_vec1[i], self.lag_vec2[i], self.lag_vec3[i],
+ self.lag_vec4[i]
+ );
+ // Clear this position on the queue
+ self.(lag_time[i]) = 0;
+ }
+ }
+ };
+
+ float lag_additem(float t, float f1, float f2, entity e1, vector v1, vector v2, vector v3, vector v4)
+ {
+ float i;
+ for(i=0;i<LAG_QUEUE_LENGTH;++i)
+ {
+ // Find a free position on the queue
+ if (self.lag_time[i] == 0)
+ {
+ self.(lag_time[i]) = t;
+ self.(lag_vec1[i]) = v1;
+ self.(lag_vec2[i]) = v2;
+ self.(lag_vec3[i]) = v3;
+ self.(lag_vec4[i]) = v4;
+ self.(lag_float1[i]) = f1;
+ self.(lag_float2[i]) = f2;
+ self.(lag_entity1[i]) = e1;
+ return TRUE;
+ }
+ }
+ return FALSE;
+ };
+
+float bot_shouldattack(entity e)
+{
+ if (e.team == self.team)
+ {
+ if (e == self)
+ return FALSE;
+ if (teams_matter)
+ if (e.team != 0)
+ return FALSE;
+ }
+
+ if(teams_matter)
+ {
+ if(e.team==0)
+ return FALSE;
+ }
+ else if(bot_ignore_bots)
+ if(clienttype(e) == CLIENTTYPE_BOT)
+ return FALSE;
+
+ if (!e.takedamage)
+ return FALSE;
+ if (e.deadflag)
+ return FALSE;
+ if (e.BUTTON_CHAT)
+ return FALSE;
+ if(g_minstagib)
+ if(e.items & IT_STRENGTH)
+ return FALSE;
+ if(e.flags & FL_NOTARGET)
+ return FALSE;
+ return TRUE;
+};
+
+void bot_lagfunc(float t, float f1, float f2, entity e1, vector v1, vector v2, vector v3, vector v4)
+{
+ 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)
+{
+ local float dist, delta_t, blend;
+ local 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) * cvar("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, cvar("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, cvar("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, cvar("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, cvar("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, cvar("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,10)*0.1;
+ desiredang = desiredang + blend *
+ (
+ self.bot_1st_order_aimfilter * cvar("bot_ai_aimskill_order_mix_1st")
+ + self.bot_2nd_order_aimfilter * cvar("bot_ai_aimskill_order_mix_2nd")
+ + self.bot_3th_order_aimfilter * cvar("bot_ai_aimskill_order_mix_3th")
+ + self.bot_4th_order_aimfilter * cvar("bot_ai_aimskill_order_mix_4th")
+ + self.bot_5th_order_aimfilter * cvar("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,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,cvar("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
+ local float r, fixedrate, blendrate;
+ fixedrate = cvar("bot_ai_aimskill_fixedrate") / bound(1,dist,1000);
+ blendrate = cvar("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,cvar("bot_ai_aimskill_mouse"),1) + desiredang * bound(0,(1-cvar("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, 10) || random()*random()>bound(0,skill*0.05,1))
+ self.bot_firetimer = time + bound(0.1, 0.5-skill*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)
+{
+ local float f, r;
+ local vector v;
+ /*
+ eprint(self);
+ dprint("bot_aim(", ftos(shotspeed));
+ dprint(", ", ftos(shotspeedupward));
+ dprint(", ", ftos(maxshottime));
+ dprint(", ", ftos(applygravity));
+ dprint(");\n");
+ */
+ shotspeed *= g_weaponspeedfactor;
+ shotspeedupward *= g_weaponspeedfactor;
+ if (!shotspeed)
+ {
+ dprint("bot_aim: WARNING: weapon ", W_Name(self.weapon), " shotspeed is zero!\n");
+ shotspeed = 1000000;
+ }
+ if (!maxshottime)
+ {
+ dprint("bot_aim: WARNING: weapon ", W_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);
+ local float distanceratio;
+ distanceratio =sqrt(bound(0,skill,10000))*0.3*(vlen(v-shotorg)-100)/cvar("bot_ai_aimskill_firetolerance_distdegrees");
+ distanceratio = bound(0,distanceratio,1);
+ r = (cvar("bot_ai_aimskill_firetolerance_maxdegrees")-cvar("bot_ai_aimskill_firetolerance_mindegrees"))
+ * (1-distanceratio) + cvar("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))
+ 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))
+ return FALSE;
+ }
+ if (r > maxshottime * shotspeed)
+ return FALSE;
+ return f;
+};
Property changes on: trunk/data/qcsrc/server/bot/aim.qc
___________________________________________________________________
Name: svn:mergeinfo
+
Name: svn:eol-style
+ native
Added: trunk/data/qcsrc/server/bot/aim.qh
===================================================================
--- trunk/data/qcsrc/server/bot/aim.qh (rev 0)
+++ trunk/data/qcsrc/server/bot/aim.qh 2009-09-20 21:43:29 UTC (rev 7851)
@@ -0,0 +1,71 @@
+/*
+ * Globals and Fields
+ */
+
+entity tracetossent;
+entity tracetossfaketarget;
+vector findtrajectory_velocity;
+
+// Lag simulation
+#define LAG_QUEUE_LENGTH 4
+
+.float lag_time[LAG_QUEUE_LENGTH];
+.float lag_float1[LAG_QUEUE_LENGTH];
+.float lag_float2[LAG_QUEUE_LENGTH];
+.vector lag_vec1[LAG_QUEUE_LENGTH];
+.vector lag_vec2[LAG_QUEUE_LENGTH];
+.vector lag_vec3[LAG_QUEUE_LENGTH];
+.vector lag_vec4[LAG_QUEUE_LENGTH];
+.entity lag_entity1[LAG_QUEUE_LENGTH];
+
+FTEQCC_YOU_SUCK_THIS_IS_NOT_UNREFERENCED(lag_time);
+FTEQCC_YOU_SUCK_THIS_IS_NOT_UNREFERENCED(lag_float1);
+FTEQCC_YOU_SUCK_THIS_IS_NOT_UNREFERENCED(lag_float2);
+FTEQCC_YOU_SUCK_THIS_IS_NOT_UNREFERENCED(lag_vec1);
+FTEQCC_YOU_SUCK_THIS_IS_NOT_UNREFERENCED(lag_vec2);
+FTEQCC_YOU_SUCK_THIS_IS_NOT_UNREFERENCED(lag_vec3);
+FTEQCC_YOU_SUCK_THIS_IS_NOT_UNREFERENCED(lag_vec4);
+FTEQCC_YOU_SUCK_THIS_IS_NOT_UNREFERENCED(lag_entity1);
+
+vector shotorg;
+vector shotdir;
+
+//
+
+.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
+ */
+
+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;
Copied: trunk/data/qcsrc/server/bot/bot.qc (from rev 7849, trunk/data/qcsrc/server/bots.qc)
===================================================================
--- trunk/data/qcsrc/server/bot/bot.qc (rev 0)
+++ trunk/data/qcsrc/server/bot/bot.qc 2009-09-20 21:43:29 UTC (rev 7851)
@@ -0,0 +1,623 @@
+#include "bot.qh"
+#include "aim.qh"
+#include "navigation.qh"
+#include "waypoints.qh"
+
+#include "aim.qc"
+#include "navigation.qc"
+#include "waypoints.qc"
+#include "scripting.qc"
+
+#include "havocbot/havocbot.qc"
+
+entity bot_spawn()
+{
+ local entity oldself, bot;
+ bot = spawnclient();
+ if (bot)
+ {
+ currentbots = currentbots + 1;
+ oldself = self;
+ self = bot;
+ bot_setnameandstuff();
+ ClientConnect();
+ PutClientInServer();
+ self = oldself;
+ }
+ return bot;
+};
+
+void bot_think()
+{
+ if (self.bot_nextthink > time)
+ return;
+
+ self.flags &~= FL_GODMODE;
+ if(cvar("bot_god"))
+ self.flags |= FL_GODMODE;
+
+ self.bot_nextthink = self.bot_nextthink + cvar("bot_ai_thinkinterval");
+ //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(time < game_starttime || ((cvar("g_campaign") && !campaign_bots_may_start)))
+ {
+ self.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 * 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 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;
+ }
+ }
+
+ // now call the current bot AI (havocbot for example)
+ self.bot_ai();
+};
+
+void bot_setnameandstuff()
+{
+ 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(cvar("g_campaign"))
+ {
+ prefix = "";
+ suffix = "";
+ }
+ else
+ {
+ prefix = cvar_string("bot_prefix");
+ suffix = cvar_string("bot_suffix");
+ }
+
+ file = fopen(cvar_string("bot_config_file"), FILE_READ);
+
+ if(file < 0)
+ print(strcat("Error: Can not open the bot configuration file '",cvar_string("bot_config_file"),"'\n"));
+ else
+ {
+ RandomSelection_Init();
+ for(;;)
+ {
+ readfile = fgets(file);
+ if(!readfile)
+ break;
+ if(substring(readfile, 0, 2) == "//")
+ continue;
+ if(substring(readfile, 0, 1) == "#")
+ continue;
+ tokens = tokenizebyseparator(readfile, "\t");
+ s = argv(0);
+ prio = 1;
+ FOR_EACH_CLIENT(p)
+ {
+ if(strcat(prefix, s, suffix) == p.netname)
+ {
+ 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 = "marine";
+
+ 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));
+ 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 (cvar("bot_usemodelnames"))
+ name = bot_model;
+ else
+ name = bot_name;
+
+ // pick the model and skin
+ if(substring(bot_model, -4, 1) != ".")
+ bot_model = strcat(bot_model, ".zym");
+ self.playermodel = self.playermodel_freeme = strzone(strcat("models/player/", bot_model));
+ self.playerskin = self.playerskin_freeme = strzone(bot_skin);
+
+ self.netname = self.netname_freeme = strzone(strcat(prefix, name, suffix));
+};
+
+void bot_custom_weapon_priority_setup()
+{
+ local float tokens, i, c, w;
+
+ bot_custom_weapon = FALSE;
+
+ if( cvar_string("bot_ai_custom_weapon_priority_far") == "" ||
+ cvar_string("bot_ai_custom_weapon_priority_mid") == "" ||
+ cvar_string("bot_ai_custom_weapon_priority_close") == "" ||
+ cvar_string("bot_ai_custom_weapon_priority_distances") == ""
+ )
+ return;
+
+ // Parse distances
+ tokens = tokenizebyseparator(cvar_string("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(cvar_string("bot_ai_custom_weapon_priority_far")," ");
+
+ c = 0;
+ for(i=0; i < tokens && c < WEP_COUNT; ++i){
+ w = stof(argv(i));
+ if ( w >= WEP_FIRST && w <= WEP_LAST) {
+ bot_weapons_far[c] = w;
+ ++c;
+ }
+ }
+ if(c < WEP_COUNT)
+ bot_weapons_far[c] = -1;
+
+ // Parse mid distance weapon priorities
+ tokens = tokenizebyseparator(cvar_string("bot_ai_custom_weapon_priority_mid")," ");
+
+ c = 0;
+ for(i=0; i < tokens && c < WEP_COUNT; ++i){
+ w = stof(argv(i));
+ if ( w >= WEP_FIRST && w <= WEP_LAST) {
+ bot_weapons_mid[c] = w;
+ ++c;
+ }
+ }
+ if(c < WEP_COUNT)
+ bot_weapons_mid[c] = -1;
+
+ // Parse close distance weapon priorities
+ tokens = tokenizebyseparator(cvar_string("bot_ai_custom_weapon_priority_close")," ");
+
+ c = 0;
+ for(i=0; i < tokens && i < WEP_COUNT; ++i){
+ w = stof(argv(i));
+ if ( w >= WEP_FIRST && w <= WEP_LAST) {
+ bot_weapons_close[c] = w;
+ ++c;
+ }
+ }
+ if(c < WEP_COUNT)
+ bot_weapons_close[c] = -1;
+
+ bot_custom_weapon = TRUE;
+};
+
+void bot_endgame()
+{
+ local 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()
+{
+ local entity e;
+ local 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 (clienttype(e) == CLIENTTYPE_BOT)
+ {
+ if (prevbot)
+ prevbot.nextbot = e;
+ else
+ {
+ bot_list = e;
+ bot_list.nextbot = world;
+ }
+ prevbot = e;
+ currentbots = currentbots + 1;
+ }
+ e = e.chain;
+ }
+ dprint(strcat("relink: ", ftos(currentbots), " bots seen.\n"));
+ bot_strategytoken = bot_list;
+ bot_strategytoken_taken = TRUE;
+};
+
+void bot_clientdisconnect()
+{
+ if (clienttype(self) != CLIENTTYPE_BOT)
+ return;
+ 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.netname_freeme = string_null;
+ self.playermodel_freeme = string_null;
+ self.playerskin_freeme = string_null;
+}
+
+void bot_clientconnect()
+{
+ if (clienttype(self) != CLIENTTYPE_BOT)
+ return;
+ self.bot_preferredcolors = self.clientcolors;
+ self.bot_nextthink = time - random();
+ self.lag_func = bot_lagfunc;
+ self.isbot = TRUE;
+ self.createdtime = self.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 = COLOR_TEAM1;
+ else if(self.bot_forced_team==2)
+ self.team = COLOR_TEAM2;
+ else if(self.bot_forced_team==3)
+ self.team = COLOR_TEAM3;
+ else if(self.bot_forced_team==4)
+ self.team = COLOR_TEAM4;
+ else
+ JoinBestTeam(self, FALSE, TRUE);
+
+ havocbot_setupbot();
+ self.bot_mouseskill=random()-0.5;
+ self.bot_thinkskill=random()-0.5;
+ self.bot_predictionskill=random()-0.5;
+ self.bot_offsetskill=random()-0.5;
+};
+
+void bot_removefromlargestteam()
+{
+ local float besttime, bestcount, thiscount;
+ local 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 == COLOR_TEAM1)
+ thiscount = c1;
+ else if(head.team == COLOR_TEAM2)
+ thiscount = c2;
+ else if(head.team == COLOR_TEAM3)
+ thiscount = c3;
+ else if(head.team == COLOR_TEAM4)
+ 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()
+{
+ local float besttime;
+ local entity best, head;
+
+ if(teams_matter)
+ {
+ 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(clienttype(head) == CLIENTTYPE_REAL)
+ bestplayer = max(bestplayer, head.totalfrags - head.totalfrags_lastcheck);
+ else
+ bestbot = max(bestbot, head.totalfrags - head.totalfrags_lastcheck);
+ }
+
+ dprint("autoskill: best player got ", ftos(bestplayer), ", ");
+ dprint("best bot got ", ftos(bestbot), "; ");
+ if(bestbot < 0 || bestplayer < 0)
+ {
+ dprint("not doing anything\n");
+ // don't return, let it reset all counters below
+ }
+ else if(bestbot <= bestplayer * factor - 2)
+ {
+ if(cvar("skill") < 17)
+ {
+ dprint("2 frags difference, increasing skill\n");
+ cvar_set("skill", ftos(cvar("skill") + 1));
+ bprint("^2SKILL UP!^7 Now at level ", ftos(cvar("skill")), "\n");
+ }
+ }
+ else if(bestbot >= bestplayer * factor + 2)
+ {
+ if(cvar("skill") > 0)
+ {
+ dprint("2 frags difference, decreasing skill\n");
+ cvar_set("skill", ftos(cvar("skill") - 1));
+ bprint("^1SKILL DOWN!^7 Now at level ", ftos(cvar("skill")), "\n");
+ }
+ }
+ else
+ {
+ dprint("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_serverframe()
+{
+ float realplayers, bots, activerealplayers;
+ entity head;
+
+ if (intermission_running)
+ return;
+
+ if (time < 2)
+ return;
+
+ stepheightvec = cvar("sv_stepheight") * '0 0 1';
+ bot_navigation_movemode = ((cvar("bot_navigation_ignoreplayers")) ? MOVE_NOMONSTERS : MOVE_NORMAL);
+
+ if(time > autoskill_nextthink)
+ {
+ float a;
+ a = cvar("skill_auto");
+ if(a)
+ autoskill(a);
+ autoskill_nextthink = time + 5;
+ }
+
+ activerealplayers = 0;
+ realplayers = 0;
+
+ FOR_EACH_REALCLIENT(head)
+ {
+ if(head.classname == "player" || g_lms || g_arena)
+ ++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 ((realplayers || cvar("bot_join_empty") || (currentbots > 0 && time < 5)))
+ {
+ float realminplayers, minplayers;
+ realminplayers = cvar("minplayers");
+ minplayers = max(0, floor(realminplayers));
+
+ float realminbots, minbots;
+ if(teamplay && cvar("bot_vs_human"))
+ realminbots = ceil(fabs(cvar("bot_vs_human")) * activerealplayers);
+ else
+ realminbots = cvar("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;
+ }
+
+ bot_ignore_bots = cvar("bot_ignore_bots");
+
+ // 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");
+ botframe_nextthink = time + 10;
+ break;
+ }
+ }
+ while (currentbots > bots)
+ bot_removenewest();
+ }
+
+ if(botframe_spawnedwaypoints)
+ {
+ if(cvar("waypoint_benchmark"))
+ localcmd("quit\n");
+ }
+
+ if (currentbots > 0 || cvar("g_waypointeditor"))
+ if (botframe_spawnedwaypoints)
+ {
+ if(botframe_cachedwaypointlinks)
+ {
+ if(!botframe_loadedforcedlinks)
+ waypoint_load_links_hardwired();
+ }
+ else
+ {
+ // TODO: Make this check cleaner
+ local 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)
+ {
+ local float interval;
+ interval = cvar("bot_ai_dangerdetectioninterval");
+ if (botframe_nextdangertime < time - interval * 1.5)
+ botframe_nextdangertime = time;
+ botframe_nextdangertime = botframe_nextdangertime + interval;
+ botframe_updatedangerousobjects(cvar("bot_ai_dangerdetectionupdates"));
+ }
+ }
+
+ if (cvar("g_waypointeditor"))
+ botframe_showwaypointlinks();
+
+ if(time > bot_cvar_nextthink)
+ {
+ if(currentbots>0)
+ bot_custom_weapon_priority_setup();
+ bot_cvar_nextthink = time + 5;
+ }
+};
Property changes on: trunk/data/qcsrc/server/bot/bot.qc
___________________________________________________________________
Name: svn:mergeinfo
+
Name: svn:eol-style
+ native
Added: trunk/data/qcsrc/server/bot/bot.qh
===================================================================
--- trunk/data/qcsrc/server/bot/bot.qh (rev 0)
+++ trunk/data/qcsrc/server/bot/bot.qh 2009-09-20 21:43:29 UTC (rev 7851)
@@ -0,0 +1,102 @@
+/*
+ * Globals and Fields
+ */
+
+float AI_STATUS_ROAMING = 1; // Bot is just crawling the map. No enemies at sight
+float AI_STATUS_ATTACKING = 2; // There are enemies at sight
+float AI_STATUS_RUNNING = 4; // Bot is bunny hopping
+float AI_STATUS_DANGER_AHEAD = 8; // There is lava/slime/trigger_hurt ahead
+float AI_STATUS_OUT_JUMPPAD = 16; // Trying to get out of a "vertical" jump pad
+float AI_STATUS_OUT_WATER = 32; // Trying to get out of water
+float AI_STATUS_WAYPOINT_PERSONAL_LINKING = 64; // Waiting for the personal waypoint to be linked
+float AI_STATUS_WAYPOINT_PERSONAL_GOING = 128; // Going to a personal waypoint
+float AI_STATUS_WAYPOINT_PERSONAL_REACHED = 256; // Personal waypoint reached
+float AI_STATUS_JETPACK_FLYING = 512;
+float AI_STATUS_JETPACK_LANDING = 1024;
+
+.float isbot; // true if this client is actually a bot
+.float aistatus;
+
+// Skill system
+float skill;
+float autoskill_nextthink;
+.float bot_thinkskill;
+.float bot_mouseskill;
+.float bot_predictionskill;
+.float bot_offsetskill;
+.float totalfrags_lastcheck;
+
+// Custom weapon priorities
+float bot_custom_weapon;
+float bot_distance_far;
+float bot_distance_close;
+
+float bot_weapons_far[WEP_LAST];
+float bot_weapons_mid[WEP_LAST];
+float bot_weapons_close[WEP_LAST];
+
+entity bot_list;
+entity player_list;
+.entity nextbot;
+.entity nextplayer;
+.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();
+
+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
+ */
+
+float sv_maxspeed;
+
+void() havocbot_setupbot;
+
+float c1, c2, c3, c4;
+void CheckAllowedTeams(entity for_whom); void GetTeamCounts(entity other);
+float JoinBestTeam(entity pl, float only_return_best, float forcebestteam);
Copied: trunk/data/qcsrc/server/bot/havocbot/havocbot.qc (from rev 7849, trunk/data/qcsrc/server/havocbot.qc)
===================================================================
--- trunk/data/qcsrc/server/bot/havocbot/havocbot.qc (rev 0)
+++ trunk/data/qcsrc/server/bot/havocbot/havocbot.qc 2009-09-20 21:43:29 UTC (rev 7851)
@@ -0,0 +1,1348 @@
+#include "havocbot.qh"
+#include "role_ctf.qc"
+#include "role_onslaught.qc"
+#include "role_keyhunt.qc"
+#include "roles.qc"
+
+void havocbot_ai()
+{
+ 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
+ {
+ 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
+ local entity newgoal, head;
+ local float bestdistance, distance;
+
+ newgoal = world;
+ bestdistance = 10000;
+ for (head = findchain(classname, "waypoint"); head; head = head.chain)
+ {
+ distance = vlen(head.origin - self.origin);
+ if(distance>10000)
+ continue;
+
+ if(head.origin_z < self.origin_z)
+ continue;
+
+ if(head.origin_z - self.origin_z - self.view_ofs_z > 100)
+ continue;
+
+ if (pointcontents(head.origin + head.maxs + '0 0 1') != CONTENT_EMPTY)
+ continue;
+
+ traceline(self.origin + self.view_ofs , head.origin, TRUE, head);
+
+ if(trace_fraction<1)
+ continue;
+
+ if(distance<bestdistance)
+ {
+ newgoal = head;
+ bestdistance = distance;
+ }
+ }
+
+ if(newgoal)
+ {
+ // te_wizspike(newgoal.origin);
+ navigation_pushroute(newgoal);
+ }
+ }
+
+ // token has been used this frame
+ bot_strategytoken_taken = TRUE;
+ }
+
+ if(self.deadflag != DEAD_NO)
+ return;
+
+ havocbot_chooseenemy();
+ if (self.bot_chooseweapontime < time )
+ {
+ self.bot_chooseweapontime = time + cvar("bot_ai_chooseweaponinterval");
+ havocbot_chooseweapon();
+ }
+ havocbot_aim();
+ lag_update();
+ if (self.bot_aimtarg)
+ {
+ self.aistatus |= AI_STATUS_ATTACKING;
+ self.aistatus &~= AI_STATUS_ROAMING;
+
+ if(self.weapons)
+ {
+ weapon_action(self.weapon, WR_AIM);
+ if (cvar("bot_nofire") || IS_INDEPENDENT_PLAYER(self))
+ {
+ self.BUTTON_ATCK = FALSE;
+ self.BUTTON_ATCK2 = FALSE;
+ }
+ else
+ {
+ if(self.BUTTON_ATCK||self.BUTTON_ATCK2)
+ self.lastfiredweapon = self.weapon;
+ }
+ }
+ else
+ {
+ if(self.bot_aimtarg.classname=="player")
+ bot_aimdir(self.bot_aimtarg.origin + self.bot_aimtarg.view_ofs - self.origin - self.view_ofs , -1);
+ }
+ }
+ else if (self.goalcurrent)
+ {
+ self.aistatus |= AI_STATUS_ROAMING;
+ self.aistatus &~= AI_STATUS_ATTACKING;
+
+ local vector now,v,next;//,heading;
+ local float aimdistance,skillblend,distanceblend,blend;
+ next = now = self.goalcurrent.origin - (self.origin + self.view_ofs);
+ aimdistance = vlen(now);
+ //heading = self.velocity;
+ //dprint(self.goalstack01.classname,etos(self.goalstack01),"\n");
+ if(
+ self.goalstack01 != self && self.goalstack01 != world && self.aistatus & AI_STATUS_RUNNING == 0 &&
+ !(self.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT)
+ )
+ next = self.goalstack01.origin - (self.origin + self.view_ofs);
+
+ skillblend=bound(0,(skill-2.5)*0.5,1); //lower skill player can't preturn
+ distanceblend=bound(0,aimdistance/cvar("bot_ai_keyboard_distance"),1);
+ blend = skillblend * (1-distanceblend);
+ //v = (now * (distanceblend) + next * (1-distanceblend)) * (skillblend) + now * (1-skillblend);
+ //v = now * (distanceblend) * (skillblend) + next * (1-distanceblend) * (skillblend) + now * (1-skillblend);
+ //v = now * ((1-skillblend) + (distanceblend) * (skillblend)) + next * (1-distanceblend) * (skillblend);
+ v = now + blend * (next - now);
+ //dprint(etos(self), " ");
+ //dprint(vtos(now), ":", vtos(next), "=", vtos(v), " (blend ", ftos(blend), ")\n");
+ //v = now * (distanceblend) + next * (1-distanceblend);
+ if (self.waterlevel < WATERLEVEL_SWIMMING)
+ v_z = 0;
+ //dprint("walk at:", vtos(v), "\n");
+ //te_lightning2(world, self.origin, self.goalcurrent.origin);
+ bot_aimdir(v, -1);
+ }
+ havocbot_movetogoal();
+};
+
+void havocbot_keyboard_movement(vector destorg)
+{
+ local vector keyboard;
+ local float blend, maxspeed;
+
+ maxspeed = cvar("sv_maxspeed");
+
+ if (time < self.havocbot_keyboardtime)
+ return;
+
+ self.havocbot_keyboardtime =
+ max(
+ self.havocbot_keyboardtime
+ + bound(0,0.05/(skill+self.havocbot_keyboardskill),0.05)
+ +random()*bound(0,0.025/(skill+self.havocbot_keyboardskill),100)
+ , time);
+ keyboard = self.movement * (1.0 / maxspeed);
+
+ local float trigger, trigger1;
+ blend = bound(0,skill*0.1,1);
+ trigger = cvar("bot_ai_keyboard_treshold");
+ 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 (skill < 2.5)
+ keyboard_y = 0;
+ }
+ else if (keyboard_x < trigger1 && skill > 1.5)
+ {
+ keyboard_x = -1;
+ if (skill < 4.5)
+ keyboard_y = 0;
+ }
+ else
+ {
+ keyboard_x = 0;
+ if (skill < 1.5)
+ keyboard_y = 0;
+ }
+ if (skill < 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)/cvar("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)
+{
+ local float bunnyhopdistance;
+ local vector deviation;
+ local float maxspeed;
+
+ if(cvar("g_midair"))
+ return;
+
+ // Don't jump when using some weapons
+ if(self.aistatus & AI_STATUS_ATTACKING)
+ if(self.weapon & WEP_CAMPINGRIFLE)
+ return;
+
+ if(self.goalcurrent.classname == "player")
+ return;
+
+ maxspeed = cvar("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;
+ }
+
+ bunnyhopdistance = vlen(self.origin - self.goalcurrent.origin);
+
+ // 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 > cvar("bot_ai_bunnyhop_firstjumpdelay"))
+ {
+ local float checkdistance;
+ checkdistance = TRUE;
+
+ // don't run if it is too close
+ if(self.bot_canruntogoal==0)
+ {
+ if(bunnyhopdistance > cvar("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 not(self.goalcurrent.wpflags & WAYPOINTFLAG_PERSONAL)
+ if(fabs(self.goalcurrent.origin_z - self.origin_z) < self.maxs_z - self.mins_z)
+ if(self.goalstack01!=world)
+ {
+ deviation = vectoangles(self.goalstack01.origin - self.origin) - vectoangles(self.goalcurrent.origin - 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 - self.goalstack01.origin))
+ if(fabs(self.goalstack01.origin_z - self.goalcurrent.origin_z) < self.maxs_z - self.mins_z)
+ {
+ if(vlen(self.goalcurrent.origin - self.goalstack01.origin) > cvar("bot_ai_bunnyhop_startdistance"))
+ if(checkpvs(self.origin + self.view_ofs, self.goalstack01))
+ {
+ checkdistance = FALSE;
+ }
+ }
+ }
+
+ if(checkdistance)
+ {
+ self.aistatus &~= AI_STATUS_RUNNING;
+ if(bunnyhopdistance > cvar("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;
+ }
+
+ // Release jump button
+ if(self.flags & FL_ONGROUND == 0)
+ {
+ if(self.velocity_z < 0 || vlen(self.velocity)<maxspeed)
+ self.BUTTON_JUMP = FALSE;
+
+ // Strafe
+ if(self.aistatus & AI_STATUS_RUNNING)
+ if(vlen(self.velocity)>maxspeed)
+ {
+ deviation = vectoangles(dir) - vectoangles(self.velocity);
+ while (deviation_y < -180) deviation_y = deviation_y + 360;
+ while (deviation_y > 180) deviation_y = deviation_y - 360;
+
+ if(fabs(deviation_y)>10)
+ self.movement_x = 0;
+
+ if(deviation_y>10)
+ self.movement_y = maxspeed * -1;
+ else if(deviation_y<10)
+ self.movement_y = maxspeed;
+
+ }
+ }
+};
+
+void havocbot_movetogoal()
+{
+ local vector destorg;
+ local vector diff;
+ local vector dir;
+ local vector flatdir;
+ local vector m1;
+ local vector m2;
+ local vector evadeobstacle;
+ local vector evadelava;
+ local float s;
+ local float maxspeed;
+ //local float dist;
+ local vector dodge;
+ //if (self.goalentity)
+ // te_lightning2(self, self.origin, (self.goalentity.absmin + self.goalentity.absmax) * 0.5);
+ self.movement = '0 0 0';
+ maxspeed = cvar("sv_maxspeed");
+
+ // Jetpack navigation
+ if(self.navigation_jetpack_goal)
+ if(self.goalcurrent==self.navigation_jetpack_goal)
+ if(self.ammo_fuel)
+ {
+ #ifdef DEBUG_BOT_GOALSTACK
+ debuggoalstack();
+ te_wizspike(self.navigation_jetpack_point);
+ #endif
+
+ // Take off
+ if not(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.origin; dxy_z = 0;
+ d = vlen(dxy);
+ v = vlen(self.velocity - self.velocity_z * '0 0 1');
+ db = (pow(v,2) / (cvar("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(self.flags & FL_ONGROUND)
+ {
+ self.jumppadcount = FALSE;
+ if(self.aistatus & AI_STATUS_OUT_JUMPPAD)
+ self.aistatus &~= AI_STATUS_OUT_JUMPPAD;
+ }
+
+ // If got stuck on the jump pad try to reach the farther visible item
+ if(self.aistatus & AI_STATUS_OUT_JUMPPAD)
+ {
+ if(fabs(self.velocity_z)<50)
+ {
+ local entity head, newgoal;
+ local float distance, bestdistance;
+
+ for (head = findchainfloat(bot_pickup, TRUE); head; head = head.chain)
+ {
+ if(head.classname=="worldspawn")
+ continue;
+
+ distance = vlen(head.origin - self.origin);
+ if(distance>1000)
+ continue;
+
+ traceline(self.origin + self.view_ofs , head.origin, TRUE, world);
+
+ if(trace_fraction<1)
+ continue;
+
+ if(distance>bestdistance)
+ {
+ newgoal = head;
+ bestdistance = distance;
+ }
+ }
+
+ if(newgoal)
+ {
+ self.ignoregoal = self.goalcurrent;
+ self.ignoregoaltime = time + cvar("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)
+ {
+ local float threshold;
+ threshold = maxspeed * 0.2;
+ if(fabs(self.velocity_x) < threshold && fabs(self.velocity_y) < threshold)
+ self.aistatus |= AI_STATUS_OUT_JUMPPAD;
+ return;
+ }
+ }
+ }
+
+ // If there is a trigger_hurt right below try to use the jetpack or make a rocketjump
+ if(skill>6)
+ if not(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.origin - self.origin);
+
+ local vector xyvelocity = self.velocity; xyvelocity_z = 0;
+ local 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>cvar("g_balance_rocketlauncher_damage")*0.5)
+ {
+ if(self.velocity_z < 0)
+ if(client_hasweapon(self, WEP_ROCKET_LAUNCHER, TRUE, FALSE))
+ {
+ self.movement_x = maxspeed;
+
+ if(self.rocketjumptime)
+ {
+ if(time > self.rocketjumptime)
+ {
+ self.BUTTON_ATCK2 = TRUE;
+ self.rocketjumptime = 0;
+ }
+ return;
+ }
+
+ self.switchweapon = WEP_ROCKET_LAUNCHER;
+ self.v_angle_x = 90;
+ self.BUTTON_ATCK = TRUE;
+ self.rocketjumptime = time + cvar("g_balance_rocketlauncher_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;
+ }
+
+#ifdef DEBUG_BOT_GOALSTACK
+ debuggoalstack();
+#endif
+
+ 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);
+
+ //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 && self.goalcurrent.origin_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 + '0 0 16', self.mins, self.maxs, self.origin + self.velocity * 0.2 + '0 0 16', FALSE, self);
+ if (trace_fraction < s + 0.01)
+ if (trace_plane_normal_z < 0.7)
+ {
+ s = trace_fraction;
+ tracebox(self.origin + '0 0 48', self.mins, self.maxs, self.origin + self.velocity * 0.2 + '0 0 48', FALSE, self);
+ if (trace_fraction > s)
+ self.BUTTON_JUMP = 1;
+ }
+ }
+
+ // avoiding dangers and obstacles
+ local 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 + cvar("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)
+ 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
+ if (self.goalcurrent.classname == "player" || self.goalcurrent.classname == "droppedweapon")
+ navigation_poproute();
+ // try to stop
+ flatdir = '0 0 0';
+ evadeobstacle = normalize(self.velocity) * -1;
+ }
+ }
+ }
+ }
+
+ 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,3+skill*0.1,1);
+ evadelava = evadelava * bound(1,3-skill,3); //Noobs fear lava a lot and take more distance from it
+ traceline(self.origin, self.enemy.origin, TRUE, world);
+ if(trace_ent.classname == "player")
+ dir = dir * bound(0,skill/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(skill >= cvar("bot_ai_bunnyhop_skilloffset"))
+ havocbot_bunnyhop(dir);
+
+ if ((dir * v_up) >= cvar("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)*0.1,1)) self.BUTTON_JUMP=TRUE;
+ if (((dodge * v_up) < 0) && random()*frametime >= 0.5*bound(0,(10-skill)*0.1,1)) self.havocbot_ducktime=time+0.3/bound(0.1,skill,10);
+};
+
+void havocbot_chooseenemy()
+{
+ local entity head, best, head2;
+ local float rating, bestrating, i, f;
+ local vector eye, v;
+ if (cvar("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.origin+self.enemy.view_ofs*0.5,FALSE,world);
+ if (trace_ent == self.enemy || trace_fraction == 1)
+ if (vlen(self.enemy.origin - 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 + cvar("bot_ai_enemydetectioninterval");
+ 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 + cvar("bot_ai_enemydetectioninterval");
+ eye = self.origin + self.view_ofs;
+ best = world;
+ bestrating = 100000000;
+ head = head2 = findchainfloat(bot_attack, TRUE);
+
+ // Search for enemies, if no enemy can be seen directly try to look through transparent objects
+ for(;;)
+ {
+ while (head)
+ {
+ v = (head.absmin + head.absmax) * 0.5;
+ rating = vlen(v - eye);
+ if (rating<cvar("bot_ai_enemydetectionradius"))
+ if (bestrating > rating)
+ if (bot_shouldattack(head))
+ {
+ traceline(eye, v, TRUE, self);
+ if (trace_ent == head || trace_fraction >= 1)
+ {
+ best = head;
+ bestrating = rating;
+ }
+ }
+ head = head.chain;
+ }
+
+ // 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_CAMPINGRIFLE
+ break;
+ if(i)
+ break;
+
+ // Set flags to see through transparent objects
+ f = self.dphitcontentsmask;
+ self.dphitcontentsmask = DPCONTENTS_OPAQUE;
+
+ head = head2;
+ ++i;
+ }
+
+ // Restore hit flags if needed
+ if(i)
+ self.dphitcontentsmask = f;
+
+ self.enemy = best;
+ self.havocbot_stickenemy = TRUE;
+};
+
+void havocbot_chooseweapon()
+{
+ local float i;
+
+ // ;)
+ if(g_weaponarena == WEPBIT_TUBA)
+ {
+ self.switchweapon = WEP_TUBA;
+ 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_LASER + 1; i < WEP_COUNT ; ++i)
+ {
+ if(client_hasweapon(self, i, TRUE, FALSE))
+ {
+ self.switchweapon = i;
+ return;
+ }
+ }
+ return;
+ }
+
+ // Do not change weapon during the next second after a combo
+ i = time - self.lastcombotime;
+ if(i < 1)
+ return;
+
+ // Workaround for rifle reloading (..)
+ if(self.weapon == WEP_CAMPINGRIFLE)
+ if(i < cvar("g_balance_campingrifle_reloadtime") + 1)
+ return;
+
+ local float w;
+ local float rocket ; rocket =-1000;
+ local float nex ; nex =-1000;
+ local float hagar ; hagar =-1000;
+ local float grenade ; grenade =-1000;
+ local float electro ; electro =-1000;
+ local float crylink ; crylink =-1000;
+ local float uzi ; uzi =-1000;
+ local float shotgun ; shotgun =-1000;
+ local float campingrifle ; campingrifle =-1000;
+ local float laser ; laser =-1000;
+ local float minstanex ; minstanex =-1000;
+ local float bestscore; bestscore = 0;
+ local float bestweapon; bestweapon=self.switchweapon;
+ local float distance; distance=bound(10,vlen(self.origin-self.enemy.origin)-200,10000);
+ local float maxdelaytime=0.5;
+ local float spreadpenalty=10;
+
+ // Should it do a weapon combo?
+ local float af, ct, combo_time, combo;
+
+ af = ATTACK_FINISHED(self);
+ ct = cvar("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)+3));
+
+ combo = FALSE;
+
+ if(cvar("bot_ai_weapon_combo"))
+ if(self.weapon == self.lastfiredweapon)
+ if(af > combo_time)
+ {
+ combo = TRUE;
+ self.lastcombotime = time;
+ }
+
+ // 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 < WEP_COUNT && bot_weapons_far[i] != -1 ; ++i){
+ w = bot_weapons_far[i];
+ if ( client_hasweapon(self, w, TRUE, FALSE) ){
+ if ( self.weapon == w && combo)
+ continue;
+ self.switchweapon = w;
+ return;
+ }
+ }
+ }
+
+ // Choose weapons for mid distance
+ if ( distance > bot_distance_close ) {
+ for(i=0; i < WEP_COUNT && bot_weapons_mid[i] != -1 ; ++i){
+ w = bot_weapons_mid[i];
+ if ( client_hasweapon(self, w, TRUE, FALSE) ){
+ if ( self.weapon == w && combo)
+ continue;
+ self.switchweapon = w;
+ return;
+ }
+ }
+ }
+
+ // Choose weapons for close distance
+ for(i=0; i < WEP_COUNT && bot_weapons_close[i] != -1 ; ++i){
+ w = bot_weapons_close[i];
+ if ( client_hasweapon(self, w, TRUE, FALSE) ){
+ if ( self.weapon == w && combo)
+ continue;
+ self.switchweapon = w;
+ return;
+ }
+ }
+ }
+
+#ifdef 0
+ // TODO: This disabled code is not working well and got replaced by custom weapon priorities.
+ // However, this logic should be refactored and moved to weapons code so each new weapon can be
+ // evaluated dynamically by bots without updating the "ai" or config files. --mand1nga
+ float s, distancefromfloor, currentscore;
+
+
+ // Formula:
+ // (Damage/Sec * Weapon spefic change to get that damage)
+ // *(Time to get to target * weapon specfic hitchange bonus) / (in a time of maxdelaytime)
+ // *(Spread change of hit) // if it applies
+ // *(Penality for target beeing in air)
+ // %weaponaddpoint
+
+ traceline(self.enemy.origin,self.enemy.origin-'0 0 1000',TRUE,world);
+ distancefromfloor = self.enemy.origin_z - trace_endpos_z;
+
+ if (client_hasweapon(self, WEP_MINSTANEX, TRUE, FALSE))
+ minstanex = (1000/cvar("g_balance_minstanex_refire")*1.0)
+ * (0.5);
+
+ if (client_hasweapon(self, WEP_ROCKET_LAUNCHER, TRUE, FALSE) &&
+ !( cvar("bot_ai_weapon_combo") && self.weapon == WEP_ROCKET_LAUNCHER &&
+ af > combo_time
+ )
+ )
+ rocket = (cvar("g_balance_rocketlauncher_damage")/cvar("g_balance_rocketlauncher_refire")*0.75)
+ * bound(0,(cvar("g_balance_rocketlauncher_speed")/distance*maxdelaytime),1)*1.5;
+
+ if (client_hasweapon(self, WEP_NEX, TRUE, FALSE) &&
+ !( cvar("bot_ai_weapon_combo") && self.weapon == WEP_NEX &&
+ af > combo_time
+ )
+ )
+ nex = (cvar("g_balance_nex_damage")/cvar("g_balance_nex_refire")*1.0)
+ * (0.5);
+
+ if (client_hasweapon(self, WEP_HAGAR, TRUE, FALSE) ) // &&
+ // !( cvar("bot_ai_weapon_combo") && self.weapon == WEP_HAGAR && time < self.bot_lastshot + cvar("g_balance_hagar_primary_refire") ))
+ hagar = (cvar("g_balance_hagar_primary_damage")/cvar("g_balance_hagar_primary_refire")*1.0)
+ * bound(0,(cvar("g_balance_hagar_primary_speed")/distance*maxdelaytime),1)*0.2;
+
+ if (client_hasweapon(self, WEP_GRENADE_LAUNCHER, TRUE, FALSE) &&
+ !(
+ cvar("bot_ai_weapon_combo") && self.weapon == WEP_GRENADE_LAUNCHER &&
+ af > combo_time
+ )
+ )
+ grenade = (cvar("g_balance_grenadelauncher_primary_damage")/cvar("g_balance_grenadelauncher_primary_refire")*1.0)
+ * bound(0,(cvar("g_balance_grenadelauncher_primary_speed")/distance*maxdelaytime),1)*1.1;
+
+ if (client_hasweapon(self, WEP_ELECTRO, TRUE, FALSE) &&
+ !( cvar("bot_ai_weapon_combo") && self.weapon == WEP_ELECTRO &&
+ af > combo_time
+ )
+ )
+ electro = (cvar("g_balance_electro_primary_damage")/cvar("g_balance_electro_primary_refire")*0.75)
+ * bound(0,(cvar("g_balance_electro_primary_speed")/distance*maxdelaytime),1)*1.0;
+
+ if (client_hasweapon(self, WEP_CRYLINK, TRUE, FALSE) ) // &&
+ // !( self.weapon == WEP_CRYLINK && time < self.bot_lastshot + cvar("g_balance_crylink_primary_refire") ))
+ crylink = (cvar("g_balance_crylink_primary_damage")/cvar("g_balance_crylink_primary_refire")*1.0)
+ * bound(0,(cvar("g_balance_crylink_primary_speed")/distance*maxdelaytime),1)*(64/(32+cvar("g_balance_crylink_primary_spread")*distance))*1.0;
+
+ if (client_hasweapon(self, WEP_UZI, TRUE, FALSE) ) // &&
+ // !( self.weapon == WEP_UZI && time < self.bot_lastshot + cvar("g_balance_uzi_sustained_refire") ))
+ uzi = (cvar("g_balance_uzi_sustained_damage")/cvar("g_balance_uzi_sustained_refire")*1.0)
+ * bound(0,32/(32+cvar("g_balance_uzi_sustained_spread")*distance),1);
+
+ if (client_hasweapon(self, WEP_SHOTGUN, TRUE, FALSE) &&
+ !( cvar("bot_ai_weapon_combo") && self.weapon == WEP_SHOTGUN &&
+ af > combo_time
+ )
+ )
+ shotgun = (cvar("g_balance_shotgun_primary_damage")*cvar("g_balance_shotgun_primary_bullets")/cvar("g_balance_shotgun_primary_refire")*1.0)
+ * bound(0,32/(32+cvar("g_balance_shotgun_primary_spread")*distance),1);
+
+ if (client_hasweapon(self, WEP_LASER, FALSE, FALSE) &&
+ !( cvar("bot_ai_weapon_combo") && self.weapon == WEP_LASER &&
+ af > combo_time
+ )
+ )
+ laser = (cvar("g_balance_laser_primary_damage")/cvar("g_balance_laser_primary_refire")*1.0)
+ * bound(0,cvar("g_balance_laser_primary_speed")/distance*maxdelaytime,1);
+
+ if((self.enemy.flags & FL_ONGROUND)==FALSE){
+ rocket = rocket * (1.5-bound(0, distancefromfloor/cvar("g_balance_rocketlauncher_radius" ),0.9)); //slight bigger change
+ grenade = grenade * (1.5-bound(0,distancefromfloor/cvar("g_balance_grenadelauncher_primary_radius"),0.95));
+ electro = electro * (1.5-bound(0,distancefromfloor/cvar("g_balance_electro_primary_radius" ),0.95));
+ laser = laser * (1.5-bound(0,distancefromfloor/cvar("g_balance_laser_primary_radius" ),0.95));
+ }
+ /*
+ dprint("Floor distance: ",ftos(distancefromfloor),"\n");
+ dprint("Rocket: " , ftos(rocket ), "\n");
+ dprint("Nex: " , ftos(nex ), "\n");
+ dprint("Hagar: " , ftos(hagar ), "\n");
+ dprint("Grenade: ", ftos(grenade ), "\n");
+ dprint("Electro: ", ftos(electro ), "\n");
+ dprint("Crylink: ", ftos(crylink ), "\n");
+ dprint("Uzi: " , ftos(uzi ), "\n");
+ dprint("Shotgun :", ftos(shotgun ), "\n");
+ dprint("Laser :", ftos(laser ), "\n\n");
+ */
+ currentscore = -1;
+ w = WEP_MINSTANEX ;s = minstanex;if (s > bestscore){bestscore = s;bestweapon = w;} if (self.switchweapon == w) currentscore = s;
+ w = WEP_ROCKET_LAUNCHER ;s = rocket ;if (s > bestscore){bestscore = s;bestweapon = w;} if (self.switchweapon == w) currentscore = s;
+ w = WEP_NEX ;s = nex ;if (s > bestscore){bestscore = s;bestweapon = w;} if (self.switchweapon == w) currentscore = s;
+ w = WEP_HAGAR ;s = hagar ;if (s > bestscore){bestscore = s;bestweapon = w;} if (self.switchweapon == w) currentscore = s;
+ w = WEP_GRENADE_LAUNCHER ;s = grenade ;if (s > bestscore){bestscore = s;bestweapon = w;} if (self.switchweapon == w) currentscore = s;
+ w = WEP_ELECTRO ;s = electro ;if (s > bestscore){bestscore = s;bestweapon = w;} if (self.switchweapon == w) currentscore = s;
+ w = WEP_CRYLINK ;s = crylink ;if (s > bestscore){bestscore = s;bestweapon = w;} if (self.switchweapon == w) currentscore = s;
+ w = WEP_UZI ;s = uzi ;if (s > bestscore){bestscore = s;bestweapon = w;} if (self.switchweapon == w) currentscore = s;
+ w = WEP_SHOTGUN ;s = shotgun ;if (s > bestscore){bestscore = s;bestweapon = w;} if (self.switchweapon == w) currentscore = s;
+ w = WEP_LASER ;s = laser ;if (s > bestscore){bestscore = s;bestweapon = w;} if (self.switchweapon == w) currentscore = s;
+
+ // switch if the best weapon would provide a significant damage increase
+ if (bestscore > currentscore*1.5){
+ self.switchweapon = bestweapon;
+
+ // buys time for detonating the rocket. not tested yet
+ if ( cvar("bot_ai_weapon_combo") && bestweapon == WEP_ROCKET_LAUNCHER )
+ self.bot_chooseweapontime += (distance / cvar("g_balance_rocketlauncher_speed"));
+ }
+#endif
+};
+
+void havocbot_aim()
+{
+ local 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.origin, enemyvel);
+ }
+ else
+ lag_additem(time + self.ping, 0, 0, world, self.origin, selfvel, self.goalcurrent.origin, '0 0 0');
+};
+
+float havocbot_moveto_refresh_route()
+{
+ // 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)
+{
+ local entity wp;
+
+ if(self.aistatus & AI_STATUS_WAYPOINT_PERSONAL_GOING)
+ {
+ // Step 4: Move to waypoint
+ if(self.havocbot_personal_waypoint==world)
+ {
+ dprint("Error: ", self.netname, " trying to walk to a non existent personal waypoint\n");
+ self.aistatus &~= AI_STATUS_WAYPOINT_PERSONAL_GOING;
+ return CMD_STATUS_ERROR;
+ }
+
+ if (!bot_strategytoken_taken)
+ if(self.havocbot_personal_waypoint_searchtime<time)
+ {
+ bot_strategytoken_taken = TRUE;
+ if(havocbot_moveto_refresh_route())
+ {
+ dprint(self.netname, " walking to its personal waypoint (after ", ftos(self.havocbot_personal_waypoint_failcounter), " failed attempts)\n");
+ self.havocbot_personal_waypoint_searchtime = time + 10;
+ self.havocbot_personal_waypoint_failcounter = 0;
+ }
+ else
+ {
+ self.havocbot_personal_waypoint_failcounter += 1;
+ self.havocbot_personal_waypoint_searchtime = time + 2;
+ if(self.havocbot_personal_waypoint_failcounter >= 30)
+ {
+ dprint("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
+ dprint(self.netname, " can't walk to its personal waypoint (after ", ftos(self.havocbot_personal_waypoint_failcounter), " failed attempts), trying later\n");
+ }
+ }
+
+ #ifdef DEBUG_BOT_GOALSTACK
+ debuggoalstack();
+ #endif
+
+ // Heading
+ local vector dir = self.goalcurrent.origin - (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
+ dprint(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)
+ {
+ dprint(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
+ dprint(self.netname, " walking to its personal waypoint\n");
+
+ return CMD_STATUS_EXECUTING;
+ }
+
+ // Step 1: Spawning waypoint
+ wp = waypoint_spawnpersonal(pos);
+ if(wp==world)
+ {
+ dprint("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;
+
+ return CMD_STATUS_EXECUTING;
+}
+
+float havocbot_resetgoal()
+{
+ navigation_clearroute();
+ return CMD_STATUS_FINISHED;
+}
+
+void havocbot_setupbot()
+{
+ self.bot_ai = havocbot_ai;
+ self.cmd_moveto = havocbot_moveto;
+ self.cmd_resetgoal = havocbot_resetgoal;
+
+ // will be updated by think code
+ //Generate some random skill levels
+ self.havocbot_keyboardskill = random() - 0.5;
+ havocbot_chooserole();
+}
+
+vector havocbot_dodge()
+{
+ // LordHavoc: disabled because this is too expensive
+ return '0 0 0';
+ /*
+ local entity head;
+ local vector dodge, v, n;
+ local 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 > 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;
+ */
+};
Property changes on: trunk/data/qcsrc/server/bot/havocbot/havocbot.qc
___________________________________________________________________
Name: svn:mergeinfo
+
Name: svn:eol-style
+ native
Added: trunk/data/qcsrc/server/bot/havocbot/havocbot.qh
===================================================================
--- trunk/data/qcsrc/server/bot/havocbot/havocbot.qh (rev 0)
+++ trunk/data/qcsrc/server/bot/havocbot/havocbot.qh 2009-09-20 21:43:29 UTC (rev 7851)
@@ -0,0 +1,58 @@
+/*
+ * 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;
+
+.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;
+
+/*
+ * Imports
+ */
+
+.entity draggedby;
+.float ladder_time;
+.entity ladder_entity;
+
Copied: trunk/data/qcsrc/server/bot/havocbot/role_ctf.qc (from rev 7849, trunk/data/qcsrc/server/havocbot_ctf.qc)
===================================================================
--- trunk/data/qcsrc/server/bot/havocbot/role_ctf.qc (rev 0)
+++ trunk/data/qcsrc/server/bot/havocbot/role_ctf.qc 2009-09-20 21:43:29 UTC (rev 7851)
@@ -0,0 +1,680 @@
+#define HAVOCBOT_CTF_ROLE_NONE 0
+#define HAVOCBOT_CTF_ROLE_DEFENSE 2
+#define HAVOCBOT_CTF_ROLE_MIDDLE 4
+#define HAVOCBOT_CTF_ROLE_OFFENSE 8
+#define HAVOCBOT_CTF_ROLE_CARRIER 16
+#define HAVOCBOT_CTF_ROLE_RETRIEVER 32
+#define HAVOCBOT_CTF_ROLE_ESCORT 64
+
+.void() havocbot_role;
+.void() havocbot_previous_role;
+
+void() havocbot_role_ctf_middle;
+void() havocbot_role_ctf_defense;
+void() havocbot_role_ctf_offense;
+void() havocbot_role_ctf_carrier;
+void() havocbot_role_ctf_retriever;
+void() havocbot_role_ctf_escort;
+
+void(entity bot) havocbot_ctf_reset_role;
+void(float ratingscale, vector org, float sradius) havocbot_goalrating_items;
+void(float ratingscale, vector org, float sradius) havocbot_goalrating_enemyplayers;
+
+.float havocbot_cantfindflag;
+.float havocbot_role_timeout;
+.entity ctf_worldflagnext;
+.entity basewaypoint;
+
+entity ctf_worldflaglist;
+vector havocbot_ctf_middlepoint;
+float havocbot_ctf_middlepoint_radius;
+
+entity havocbot_ctf_find_flag(entity bot)
+{
+ entity f;
+ f = ctf_worldflaglist;
+ while (f)
+ {
+ if (bot.team == f.team)
+ return f;
+ f = f.ctf_worldflagnext;
+ }
+ return world;
+};
+
+entity havocbot_ctf_find_enemy_flag(entity bot)
+{
+ entity f;
+ f = ctf_worldflaglist;
+ while (f)
+ {
+ if (bot.team != f.team)
+ return f;
+ f = f.ctf_worldflagnext;
+ }
+ return world;
+};
+
+float havocbot_ctf_teamcount(entity bot, vector org, float radius)
+{
+ if not(teams_matter)
+ return 0;
+
+ float c;
+ entity head;
+
+ FOR_EACH_PLAYER(head)
+ {
+ if(head.team!=bot.team || head.deadflag != DEAD_NO || head == bot)
+ continue;
+
+ if(vlen(head.origin - org) < radius)
+ ++c;
+ }
+
+ return c;
+};
+
+void havocbot_goalrating_ctf_ourflag(float ratingscale)
+{
+ local entity head;
+ head = ctf_worldflaglist;
+ while (head)
+ {
+ if (self.team == head.team)
+ break;
+ head = head.ctf_worldflagnext;
+ }
+ if (head)
+ navigation_routerating(head, ratingscale, 10000);
+};
+
+void havocbot_goalrating_ctf_ourbase(float ratingscale)
+{
+ local entity head;
+ head = ctf_worldflaglist;
+ while (head)
+ {
+ if (self.team == head.team)
+ break;
+ head = head.ctf_worldflagnext;
+ }
+ if not(head)
+ return;
+
+ navigation_routerating(head.basewaypoint, ratingscale, 10000);
+};
+
+void havocbot_goalrating_ctf_enemyflag(float ratingscale)
+{
+ local entity head;
+ head = ctf_worldflaglist;
+ while (head)
+ {
+ if (self.team != head.team)
+ break;
+ head = head.ctf_worldflagnext;
+ }
+ if (head)
+ navigation_routerating(head, ratingscale, 10000);
+};
+
+void havocbot_goalrating_ctf_enemybase(float ratingscale)
+{
+ if not(bot_waypoints_for_items)
+ {
+ havocbot_goalrating_ctf_enemyflag(ratingscale);
+ return;
+ }
+
+ local entity head;
+
+ head = havocbot_ctf_find_enemy_flag(self);
+
+ if not(head)
+ return;
+
+ navigation_routerating(head.basewaypoint, ratingscale, 10000);
+};
+
+void havocbot_goalrating_ctf_ourstolenflag(float ratingscale)
+{
+ local entity mf;
+
+ mf = havocbot_ctf_find_flag(self);
+
+ if(mf.cnt == FLAG_BASE)
+ return;
+
+ navigation_routerating(mf, ratingscale, 10000);
+};
+
+void havocbot_goalrating_ctf_droppedflags(float ratingscale, vector org, float radius)
+{
+ local entity head;
+ head = ctf_worldflaglist;
+ while (head)
+ {
+ // flag is out in the field
+ if(head.cnt != FLAG_BASE)
+ if(head.tag_entity==world) // dropped
+ {
+ if(radius)
+ {
+ if(vlen(org-head.origin)<radius)
+ navigation_routerating(head, ratingscale, 10000);
+ }
+ else
+ navigation_routerating(head, ratingscale, 10000);
+ }
+
+ head = head.ctf_worldflagnext;
+ }
+};
+
+void havocbot_goalrating_ctf_carrieritems(float ratingscale, vector org, float sradius)
+{
+ local entity head;
+ local float t;
+ head = findchainfloat(bot_pickup, TRUE);
+ while (head)
+ {
+ // gather health and armor only
+ if (head.solid)
+ if (head.health || head.armorvalue)
+ if (vlen(head.origin - org) < sradius)
+ {
+ // get the value of the item
+ t = head.bot_pickupevalfunc(self, head) * 0.0001;
+ if (t > 0)
+ navigation_routerating(head, t * ratingscale, 500);
+ }
+ head = head.chain;
+ }
+};
+
+void havocbot_role_ctf_setrole(entity bot, float role)
+{
+ dprint(strcat(bot.netname," switched to "));
+ switch(role)
+ {
+ case HAVOCBOT_CTF_ROLE_CARRIER:
+ dprint("carrier");
+ bot.havocbot_role = havocbot_role_ctf_carrier;
+ bot.havocbot_role_timeout = 0;
+ bot.havocbot_cantfindflag = time + 10;
+ break;
+ case HAVOCBOT_CTF_ROLE_DEFENSE:
+ dprint("defense");
+ bot.havocbot_role = havocbot_role_ctf_defense;
+ bot.havocbot_role_timeout = 0;
+ break;
+ case HAVOCBOT_CTF_ROLE_MIDDLE:
+ dprint("middle");
+ bot.havocbot_role = havocbot_role_ctf_middle;
+ bot.havocbot_role_timeout = 0;
+ break;
+ case HAVOCBOT_CTF_ROLE_OFFENSE:
+ dprint("offense");
+ bot.havocbot_role = havocbot_role_ctf_offense;
+ bot.havocbot_role_timeout = 0;
+ break;
+ case HAVOCBOT_CTF_ROLE_RETRIEVER:
+ dprint("retriever");
+ bot.havocbot_previous_role = bot.havocbot_role;
+ bot.havocbot_role = havocbot_role_ctf_retriever;
+ bot.havocbot_role_timeout = time + 10;
+ break;
+ case HAVOCBOT_CTF_ROLE_ESCORT:
+ dprint("escort");
+ bot.havocbot_previous_role = bot.havocbot_role;
+ bot.havocbot_role = havocbot_role_ctf_escort;
+ bot.havocbot_role_timeout = time + 30;
+ break;
+ }
+ dprint("\n");
+};
+
+void havocbot_role_ctf_carrier()
+{
+ if(self.deadflag != DEAD_NO)
+ {
+ havocbot_ctf_reset_role(self);
+ return;
+ }
+
+ if (self.flagcarried == world)
+ {
+ havocbot_ctf_reset_role(self);
+ return;
+ }
+
+ if (self.bot_strategytime < time)
+ {
+ self.bot_strategytime = time + cvar("bot_ai_strategyinterval");
+
+ navigation_goalrating_start();
+ havocbot_goalrating_ctf_ourbase(50000);
+
+ if(self.health<100)
+ havocbot_goalrating_ctf_carrieritems(1000, self.origin, 1000);
+
+ navigation_goalrating_end();
+
+ if (self.navigation_hasgoals)
+ self.havocbot_cantfindflag = time + 10;
+ else if (time > self.havocbot_cantfindflag)
+ {
+ // Can't navigate to my own base, suicide!
+ // TODO: drop it and wander around
+ Damage(self, self, self, 100000, DEATH_KILL, self.origin, '0 0 0');
+ return;
+ }
+ }
+};
+
+void havocbot_role_ctf_escort()
+{
+ local entity mf, ef;
+
+ if(self.deadflag != DEAD_NO)
+ {
+ havocbot_ctf_reset_role(self);
+ return;
+ }
+
+ if (self.flagcarried)
+ {
+ havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
+ return;
+ }
+
+ // If enemy flag is back on the base switch to previous role
+ ef = havocbot_ctf_find_enemy_flag(self);
+ if(ef.cnt==FLAG_BASE)
+ {
+ self.havocbot_role = self.havocbot_previous_role;
+ self.havocbot_role_timeout = 0;
+ return;
+ }
+
+ // If the flag carrier reached the base switch to defense
+ mf = havocbot_ctf_find_flag(self);
+ if(mf.cnt!=FLAG_BASE)
+ if(vlen(ef.origin - mf.dropped_origin) < 300)
+ {
+ havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_DEFENSE);
+ return;
+ }
+
+ // Set the role timeout if necessary
+ if (!self.havocbot_role_timeout)
+ {
+ self.havocbot_role_timeout = time + random() * 30 + 60;
+ }
+
+ // If nothing happened just switch to previous role
+ if (time > self.havocbot_role_timeout)
+ {
+ self.havocbot_role = self.havocbot_previous_role;
+ self.havocbot_role_timeout = 0;
+ return;
+ }
+
+ // Chase the flag carrier
+ if (self.bot_strategytime < time)
+ {
+ self.bot_strategytime = time + cvar("bot_ai_strategyinterval");
+ navigation_goalrating_start();
+ havocbot_goalrating_ctf_enemyflag(30000);
+ havocbot_goalrating_ctf_ourstolenflag(40000);
+ havocbot_goalrating_items(10000, self.origin, 10000);
+ navigation_goalrating_end();
+ }
+};
+
+void havocbot_role_ctf_offense()
+{
+ local entity mf, ef;
+ local vector pos;
+
+ if(self.deadflag != DEAD_NO)
+ {
+ havocbot_ctf_reset_role(self);
+ return;
+ }
+
+ if (self.flagcarried)
+ {
+ havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
+ return;
+ }
+
+ // Check flags
+ mf = havocbot_ctf_find_flag(self);
+ ef = havocbot_ctf_find_enemy_flag(self);
+
+ // Own flag stolen
+ if(mf.cnt!=FLAG_BASE)
+ {
+ if(mf.tag_entity)
+ pos = mf.tag_entity.origin;
+ else
+ pos = mf.origin;
+
+ // Try to get it if closer than the enemy base
+ if(vlen(self.origin-ef.dropped_origin)>vlen(self.origin-pos))
+ {
+ havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
+ return;
+ }
+ }
+
+ // Escort flag carrier
+ if(ef.cnt!=FLAG_BASE)
+ {
+ if(ef.tag_entity)
+ pos = ef.tag_entity.origin;
+ else
+ pos = ef.origin;
+
+ if(vlen(pos-mf.dropped_origin)>700)
+ {
+ havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_ESCORT);
+ return;
+ }
+ }
+
+ // About to fail, switch to middlefield
+ if(self.health<50)
+ {
+ havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_MIDDLE);
+ return;
+ }
+
+ // Set the role timeout if necessary
+ if (!self.havocbot_role_timeout)
+ self.havocbot_role_timeout = time + 120;
+
+ if (time > self.havocbot_role_timeout)
+ {
+ havocbot_ctf_reset_role(self);
+ return;
+ }
+
+ if (self.bot_strategytime < time)
+ {
+ self.bot_strategytime = time + cvar("bot_ai_strategyinterval");
+ navigation_goalrating_start();
+ havocbot_goalrating_ctf_ourstolenflag(50000);
+ havocbot_goalrating_ctf_enemybase(20000);
+ havocbot_goalrating_items(5000, self.origin, 1000);
+ havocbot_goalrating_items(1000, self.origin, 10000);
+ navigation_goalrating_end();
+ }
+};
+
+// Retriever (temporary role):
+void havocbot_role_ctf_retriever()
+{
+ local entity mf;
+
+ if(self.deadflag != DEAD_NO)
+ {
+ havocbot_ctf_reset_role(self);
+ return;
+ }
+
+ if (self.flagcarried)
+ {
+ havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
+ return;
+ }
+
+ // If flag is back on the base switch to previous role
+ mf = havocbot_ctf_find_flag(self);
+ if(mf.cnt==FLAG_BASE)
+ {
+ havocbot_ctf_reset_role(self);
+ return;
+ }
+
+ if (!self.havocbot_role_timeout)
+ self.havocbot_role_timeout = time + 20;
+
+ if (time > self.havocbot_role_timeout)
+ {
+ havocbot_ctf_reset_role(self);
+ return;
+ }
+
+ if (self.bot_strategytime < time)
+ {
+ local float radius;
+ radius = 10000;
+
+ self.bot_strategytime = time + cvar("bot_ai_strategyinterval");
+ navigation_goalrating_start();
+ havocbot_goalrating_ctf_ourstolenflag(50000);
+ havocbot_goalrating_ctf_droppedflags(40000, self.origin, radius);
+ havocbot_goalrating_ctf_enemybase(30000);
+ havocbot_goalrating_items(500, self.origin, radius);
+ navigation_goalrating_end();
+ }
+};
+
+void havocbot_role_ctf_middle()
+{
+ local entity mf;
+
+ if(self.deadflag != DEAD_NO)
+ {
+ havocbot_ctf_reset_role(self);
+ return;
+ }
+
+ if (self.flagcarried)
+ {
+ havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
+ return;
+ }
+
+ mf = havocbot_ctf_find_flag(self);
+ if(mf.cnt!=FLAG_BASE)
+ {
+ havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
+ return;
+ }
+
+ if (!self.havocbot_role_timeout)
+ self.havocbot_role_timeout = time + 10;
+
+ if (time > self.havocbot_role_timeout)
+ {
+ havocbot_ctf_reset_role(self);
+ return;
+ }
+
+ if (self.bot_strategytime < time)
+ {
+ local vector org;
+
+ org = havocbot_ctf_middlepoint;
+ org_z = self.origin_z;
+
+ self.bot_strategytime = time + cvar("bot_ai_strategyinterval");
+ navigation_goalrating_start();
+ havocbot_goalrating_ctf_ourstolenflag(50000);
+ havocbot_goalrating_ctf_droppedflags(30000, self.origin, 10000);
+ havocbot_goalrating_enemyplayers(10000, org, havocbot_ctf_middlepoint_radius * 0.5);
+ havocbot_goalrating_items(5000, org, havocbot_ctf_middlepoint_radius * 0.5);
+ havocbot_goalrating_items(2500, self.origin, 10000);
+ havocbot_goalrating_ctf_enemybase(2500);
+ navigation_goalrating_end();
+ }
+};
+
+void havocbot_role_ctf_defense()
+{
+ local entity mf;
+
+ if(self.deadflag != DEAD_NO)
+ {
+ havocbot_ctf_reset_role(self);
+ return;
+ }
+
+ if (self.flagcarried)
+ {
+ havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
+ return;
+ }
+
+ // If own flag was captured
+ mf = havocbot_ctf_find_flag(self);
+ if(mf.cnt!=FLAG_BASE)
+ {
+ havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
+ return;
+ }
+
+ if (!self.havocbot_role_timeout)
+ self.havocbot_role_timeout = time + 30;
+
+ if (time > self.havocbot_role_timeout)
+ {
+ havocbot_ctf_reset_role(self);
+ return;
+ }
+ if (self.bot_strategytime < time)
+ {
+ local float radius;
+ local vector org;
+
+ org = mf.dropped_origin;
+ radius = havocbot_ctf_middlepoint_radius;
+
+ self.bot_strategytime = time + cvar("bot_ai_strategyinterval");
+ navigation_goalrating_start();
+
+ // if enemies are closer to our base, go there
+ local entity head, closestplayer;
+ local float distance, bestdistance;
+ distance = 10000;
+ FOR_EACH_PLAYER(head)
+ {
+ if(head.deadflag!=DEAD_NO)
+ continue;
+
+ distance = vlen(org - head.origin);
+ if(distance<bestdistance)
+ {
+ closestplayer = head;
+ bestdistance = distance;
+ }
+ }
+
+ if(closestplayer)
+ if(closestplayer.team!=self.team)
+ if(vlen(org - self.origin)>1000)
+ if(checkpvs(self.origin,closestplayer)||random()<0.5)
+ havocbot_goalrating_ctf_ourbase(30000);
+
+ havocbot_goalrating_ctf_ourstolenflag(20000);
+ havocbot_goalrating_ctf_droppedflags(20000, org, radius);
+ havocbot_goalrating_enemyplayers(15000, org, radius);
+ havocbot_goalrating_items(10000, org, radius);
+ havocbot_goalrating_items(5000, self.origin, 10000);
+ navigation_goalrating_end();
+ }
+};
+
+void havocbot_calculate_middlepoint()
+{
+ entity f;
+ vector p1, p2;
+
+ f = ctf_worldflaglist;
+ while (f)
+ {
+ if(p1)
+ p2 = f.origin;
+ else
+ p1 = f.origin;
+
+ f = f.ctf_worldflagnext;
+ }
+ havocbot_ctf_middlepoint = p1 + ((p2-p1) * 0.5);
+ havocbot_ctf_middlepoint_radius = vlen(p2-p1) * 0.5;
+};
+
+void havocbot_ctf_reset_role(entity bot)
+{
+ local float cdefense, cmiddle, coffense;
+ local entity mf, ef, head;
+ local float c;
+
+ if(bot.deadflag != DEAD_NO)
+ return;
+
+ if(vlen(havocbot_ctf_middlepoint)==0)
+ havocbot_calculate_middlepoint();
+
+ // Check ctf flags
+ if (bot.flagcarried)
+ {
+ havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_CARRIER);
+ return;
+ }
+
+ mf = havocbot_ctf_find_flag(bot);
+ ef = havocbot_ctf_find_enemy_flag(bot);
+
+ // Retrieve stolen flag
+ if(mf.cnt!=FLAG_BASE)
+ {
+ havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_RETRIEVER);
+ return;
+ }
+
+ // If enemy flag is taken go to the middle to intercept pursuers
+ if(ef.cnt!=FLAG_BASE)
+ {
+ havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
+ return;
+ }
+
+ // if there is only me on the team switch to offense
+ c = 0;
+ FOR_EACH_PLAYER(head)
+ if(head.team==bot.team)
+ ++c;
+
+ if(c==1)
+ {
+ havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
+ return;
+ }
+
+ // Evaluate best position to take
+ // Count mates on middle position
+ cmiddle = havocbot_ctf_teamcount(bot, havocbot_ctf_middlepoint, havocbot_ctf_middlepoint_radius * 0.5);
+
+ // Count mates on defense position
+ cdefense = havocbot_ctf_teamcount(bot, mf.dropped_origin, havocbot_ctf_middlepoint_radius * 0.5);
+
+ // Count mates on offense position
+ coffense = havocbot_ctf_teamcount(bot, ef.dropped_origin, havocbot_ctf_middlepoint_radius);
+
+ if(cdefense<=coffense)
+ havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_DEFENSE);
+ else if(coffense<=cmiddle)
+ havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
+ else
+ havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
+};
+
+void havocbot_chooserole_ctf()
+{
+ havocbot_ctf_reset_role(self);
+};
Property changes on: trunk/data/qcsrc/server/bot/havocbot/role_ctf.qc
___________________________________________________________________
Name: svn:mergeinfo
+
Name: svn:eol-style
+ native
Added: trunk/data/qcsrc/server/bot/havocbot/role_keyhunt.qc
===================================================================
--- trunk/data/qcsrc/server/bot/havocbot/role_keyhunt.qc (rev 0)
+++ trunk/data/qcsrc/server/bot/havocbot/role_keyhunt.qc 2009-09-20 21:43:29 UTC (rev 7851)
@@ -0,0 +1,212 @@
+void() havocbot_role_kh_carrier;
+void() havocbot_role_kh_defense;
+void() havocbot_role_kh_offense;
+void() havocbot_role_kh_freelancer;
+
+entity kh_worldkeylist;
+.entity kh_worldkeynext;
+
+void havocbot_goalrating_kh(float ratingscale_team, float ratingscale_dropped, float ratingscale_enemy)
+{
+ local 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()
+{
+ if(self.deadflag != DEAD_NO)
+ return;
+
+ if (!(self.kh_next))
+ {
+ dprint("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 + cvar("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()
+{
+ if(self.deadflag != DEAD_NO)
+ return;
+
+ if (self.kh_next)
+ {
+ dprint("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)
+ {
+ dprint("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 + cvar("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()
+{
+ if(self.deadflag != DEAD_NO)
+ return;
+
+ if (self.kh_next)
+ {
+ dprint("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)
+ {
+ dprint("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 + cvar("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()
+{
+ if(self.deadflag != DEAD_NO)
+ return;
+
+ if (self.kh_next)
+ {
+ dprint("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)
+ {
+ dprint("changing role to offense\n");
+ self.havocbot_role = havocbot_role_kh_offense;
+ }
+ else
+ {
+ dprint("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 + cvar("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()
+{
+ local 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;
+};
Copied: trunk/data/qcsrc/server/bot/havocbot/role_onslaught.qc (from rev 7849, trunk/data/qcsrc/server/havocbot_ons.qc)
===================================================================
--- trunk/data/qcsrc/server/bot/havocbot/role_onslaught.qc (rev 0)
+++ trunk/data/qcsrc/server/bot/havocbot/role_onslaught.qc 2009-09-20 21:43:29 UTC (rev 7851)
@@ -0,0 +1,366 @@
+#define HAVOCBOT_ONS_ROLE_NONE 0
+#define HAVOCBOT_ONS_ROLE_DEFENSE 2
+#define HAVOCBOT_ONS_ROLE_ASSISTANT 4
+#define HAVOCBOT_ONS_ROLE_OFFENSE 8
+
+.float havocbot_role_flags;
+.float havocbot_attack_time;
+
+.void() havocbot_role;
+.void() havocbot_previous_role;
+
+void() havocbot_role_ons_defense;
+void() havocbot_role_ons_offense;
+void() havocbot_role_ons_assistant;
+
+void(entity bot) havocbot_ons_reset_role;
+void(float ratingscale, vector org, float sradius) havocbot_goalrating_items;
+void(float ratingscale, vector org, float sradius) havocbot_goalrating_enemyplayers;
+
+.float isshielded;
+.float iscaptured;
+.float islinked;
+.float isgenneighbor_blue, iscpneighbor_blue;
+.float isgenneighbor_red, iscpneighbor_red;
+
+.entity havocbot_ons_target;
+
+void havocbot_goalrating_ons_offenseitems(float ratingscale, vector org, float sradius)
+{
+ local entity head;
+ local float t, i, c, needarmor, needweapons;
+
+ // Needs armor/health?
+ if(self.health<100)
+ needarmor = TRUE;
+
+ // Needs weapons?
+ for(i = WEP_FIRST; i < WEP_LAST ; ++i)
+ {
+ // Find weapon
+ if((get_weaponinfo(i)).weapons & self.weapons)
+ if(++c>=4)
+ break;
+ }
+
+ if(c<4)
+ needweapons = TRUE;
+
+ if(!needweapons && !needarmor)
+ return;
+
+// dprint(self.netname, " needs weapons ", ftos(needweapons) , "\n");
+// dprint(self.netname, " needs armor ", ftos(needarmor) , "\n");
+
+ // See what is around
+ head = findchainfloat(bot_pickup, TRUE);
+ while (head)
+ {
+ // gather health and armor only
+ if (head.solid)
+ if ( ((head.health || head.armorvalue) && needarmor) || (head.weapons && needweapons ) )
+ if (vlen(head.origin - org) < sradius)
+ {
+ t = head.bot_pickupevalfunc(self, head);
+ if (t > 0)
+ navigation_routerating(head, t * ratingscale, 500);
+ }
+ head = head.chain;
+ }
+};
+
+void havocbot_role_ons_setrole(entity bot, float role)
+{
+ dprint(strcat(bot.netname," switched to "));
+ switch(role)
+ {
+ case HAVOCBOT_ONS_ROLE_DEFENSE:
+ dprint("defense");
+ bot.havocbot_role = havocbot_role_ons_defense;
+ bot.havocbot_role_flags = HAVOCBOT_ONS_ROLE_DEFENSE;
+ bot.havocbot_role_timeout = 0;
+ break;
+ case HAVOCBOT_ONS_ROLE_ASSISTANT:
+ dprint("assistant");
+ bot.havocbot_role = havocbot_role_ons_assistant;
+ bot.havocbot_role_flags = HAVOCBOT_ONS_ROLE_ASSISTANT;
+ bot.havocbot_role_timeout = 0;
+ break;
+ case HAVOCBOT_ONS_ROLE_OFFENSE:
+ dprint("offense");
+ bot.havocbot_role = havocbot_role_ons_offense;
+ bot.havocbot_role_flags = HAVOCBOT_ONS_ROLE_OFFENSE;
+ bot.havocbot_role_timeout = 0;
+ break;
+ }
+ dprint("\n");
+};
+
+float havocbot_ons_teamcount(entity bot, float role)
+{
+ local float c;
+ local entity head;
+
+ FOR_EACH_PLAYER(head)
+ if(head.team==self.team)
+ if(head.havocbot_role_flags & role)
+ ++c;
+
+ return c;
+};
+
+void havocbot_goalrating_ons_controlpoints_attack(float ratingscale)
+{
+ entity cp, cp1, cp2, best, pl, wp;
+ float radius, found, bestvalue, c;
+
+ cp1 = cp2 = findchain(classname, "onslaught_controlpoint");
+
+ // Filter control points
+ for (; cp2; cp2 = cp2.chain)
+ {
+ cp2.wpcost = c = 0;
+ cp2.wpconsidered = FALSE;
+
+ if(cp2.isshielded)
+ continue;
+
+ // Ignore owned controlpoints
+ if(self.team == COLOR_TEAM1)
+ {
+ if( (cp2.isgenneighbor_blue || cp2.iscpneighbor_blue) && !(cp2.isgenneighbor_red || cp2.iscpneighbor_red) )
+ continue;
+ }
+ else if(self.team == COLOR_TEAM2)
+ {
+ if( (cp2.isgenneighbor_red || cp2.iscpneighbor_red) && !(cp2.isgenneighbor_blue || cp2.iscpneighbor_blue) )
+ continue;
+ }
+
+ // Count team mates interested in this control point
+ // (easier and cleaner than keeping counters per cp and teams)
+ FOR_EACH_PLAYER(pl)
+ if(pl.team==self.team)
+ if(pl.havocbot_role_flags & HAVOCBOT_ONS_ROLE_OFFENSE)
+ if(pl.havocbot_ons_target==cp2)
+ ++c;
+
+ // NOTE: probably decrease the cost of attackable control points
+ cp2.wpcost = c;
+ cp2.wpconsidered = TRUE;
+ }
+
+ // We'll consider only the best case
+ bestvalue = 99999999999;
+ for (; cp1; cp1 = cp1.chain)
+ {
+ if not(cp1.wpconsidered)
+ continue;
+
+ if(cp1.wpcost<bestvalue)
+ {
+ bestvalue = cp1.wpcost;
+ cp = cp1;
+ self.havocbot_ons_target = cp1;
+ }
+ }
+
+ if not(cp)
+ return;
+
+// dprint(self.netname, " chose cp ranked ", ftos(bestvalue), "\n");
+
+ if(cp.goalentity)
+ {
+ // Should be attacked
+ // Rate waypoints near it
+ found = FALSE;
+ best = world;
+ bestvalue = 99999999999;
+ for(radius=0; radius<1000 && !found; radius+=500)
+ {
+ for(wp=findradius(cp.origin,radius); wp; wp=wp.chain)
+ {
+ if(!(wp.wpflags & WAYPOINTFLAG_GENERATED))
+ if(wp.classname=="waypoint")
+ if(checkpvs(wp.origin,cp))
+ {
+ found = TRUE;
+ if(wp.cnt<bestvalue)
+ {
+ best = wp;
+ bestvalue = wp.cnt;
+ }
+ }
+ }
+ }
+
+ if(best)
+ {
+ navigation_routerating(best, ratingscale, 10000);
+ best.cnt += 1;
+
+ self.havocbot_attack_time = 0;
+ if(checkpvs(self.view_ofs,cp))
+ if(checkpvs(self.view_ofs,best))
+ self.havocbot_attack_time = time + 2;
+ }
+ else
+ {
+ navigation_routerating(cp, ratingscale, 10000);
+ }
+ // dprint(self.netname, " found an attackable controlpoint at ", vtos(cp.origin) ,"\n");
+ }
+ else
+ {
+ // Should be touched
+ // dprint(self.netname, " found a touchable controlpoint at ", vtos(cp.origin) ,"\n");
+
+ // Look for auto generated waypoint
+ if not(bot_waypoints_for_items)
+ for (wp = findradius(cp.origin,100); wp; wp = wp.chain)
+ {
+ if(wp.classname=="waypoint")
+ {
+ navigation_routerating(wp, ratingscale, 10000);
+ found = TRUE;
+ }
+ }
+
+ // Nothing found, rate the controlpoint itself
+ if not(found)
+ navigation_routerating(cp, ratingscale, 10000);
+ }
+};
+
+float havocbot_goalrating_ons_generator_attack(float ratingscale)
+{
+ local entity g, wp, bestwp;
+ local float found, best;
+
+ for (g = findchain(classname, "onslaught_generator"); g; g = g.chain)
+ {
+ if(g.team == self.team || g.isshielded)
+ continue;
+
+ // Should be attacked
+ // Rate waypoints near it
+ found = FALSE;
+ bestwp = world;
+ best = 99999999999;
+
+ for(wp=findradius(g.origin,400); wp; wp=wp.chain)
+ {
+ if(wp.classname=="waypoint")
+ if(checkpvs(wp.origin,g))
+ {
+ found = TRUE;
+ if(wp.cnt<best)
+ {
+ bestwp = wp;
+ best = wp.cnt;
+ }
+ }
+ }
+
+ if(bestwp)
+ {
+ // dprint("waypoints found around generator\n");
+ navigation_routerating(bestwp, ratingscale, 10000);
+ bestwp.cnt += 1;
+
+ self.havocbot_attack_time = 0;
+ if(checkpvs(self.view_ofs,g))
+ if(checkpvs(self.view_ofs,bestwp))
+ self.havocbot_attack_time = time + 5;
+
+ return TRUE;
+ }
+ else
+ {
+ // dprint("generator found without waypoints around\n");
+ // if there aren't waypoints near the generator go straight to it
+ navigation_routerating(g, ratingscale, 10000);
+ self.havocbot_attack_time = 0;
+ return TRUE;
+ }
+ }
+ return FALSE;
+};
+
+void havocbot_role_ons_offense()
+{
+ if(self.deadflag != DEAD_NO)
+ {
+ self.havocbot_attack_time = 0;
+ havocbot_ons_reset_role(self);
+ return;
+ }
+
+ // Set the role timeout if necessary
+ if (!self.havocbot_role_timeout)
+ self.havocbot_role_timeout = time + 120;
+
+ if (time > self.havocbot_role_timeout)
+ {
+ havocbot_ons_reset_role(self);
+ return;
+ }
+
+ if(self.havocbot_attack_time>time)
+ return;
+
+ if (self.bot_strategytime < time)
+ {
+ navigation_goalrating_start();
+ havocbot_goalrating_enemyplayers(20000, self.origin, 650);
+ if(!havocbot_goalrating_ons_generator_attack(20000))
+ havocbot_goalrating_ons_controlpoints_attack(20000);
+ havocbot_goalrating_ons_offenseitems(10000, self.origin, 10000);
+ navigation_goalrating_end();
+
+ self.bot_strategytime = time + cvar("bot_ai_strategyinterval");
+ }
+};
+
+void havocbot_role_ons_assistant()
+{
+ havocbot_ons_reset_role(self);
+};
+
+void havocbot_role_ons_defense()
+{
+ havocbot_ons_reset_role(self);
+};
+
+void havocbot_ons_reset_role(entity bot)
+{
+ local entity head;
+ local float c;
+
+ if(self.deadflag != DEAD_NO)
+ return;
+
+ bot.havocbot_ons_target = world;
+
+ // TODO: Defend control points or generator if necessary
+
+ // if there is only me on the team switch to offense
+ c = 0;
+ FOR_EACH_PLAYER(head)
+ if(head.team==self.team)
+ ++c;
+
+ if(c==1)
+ {
+ havocbot_role_ons_setrole(bot, HAVOCBOT_ONS_ROLE_OFFENSE);
+ return;
+ }
+
+ havocbot_role_ons_setrole(bot, HAVOCBOT_ONS_ROLE_OFFENSE);
+};
+
+void havocbot_chooserole_ons()
+{
+ havocbot_ons_reset_role(self);
+};
Property changes on: trunk/data/qcsrc/server/bot/havocbot/role_onslaught.qc
___________________________________________________________________
Name: svn:mergeinfo
+
Name: svn:eol-style
+ native
Copied: trunk/data/qcsrc/server/bot/havocbot/roles.qc (from rev 7849, trunk/data/qcsrc/server/havocbot_roles.qc)
===================================================================
--- trunk/data/qcsrc/server/bot/havocbot/roles.qc (rev 0)
+++ trunk/data/qcsrc/server/bot/havocbot/roles.qc 2009-09-20 21:43:29 UTC (rev 7851)
@@ -0,0 +1,309 @@
+
+.float max_armorvalue;
+.float havocbot_role_timeout;
+
+.void() havocbot_previous_role;
+.void() havocbot_role;
+
+void havocbot_goalrating_items(float ratingscale, vector org, float sradius)
+{
+ local entity head;
+ local entity player;
+ local float rating, d, discard, distance, friend_distance, enemy_distance;
+ ratingscale = ratingscale * 0.0001; // items are rated around 10000 already
+ head = findchainfloat(bot_pickup, TRUE);
+
+ while (head)
+ {
+ distance = vlen(head.origin - 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(head.origin, head.origin + '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(teams_matter)
+ {
+ discard = FALSE;
+
+ FOR_EACH_PLAYER(player)
+ {
+
+ if ( self == player || player.deadflag )
+ continue;
+
+ d = vlen(player.origin - head.origin); // distance between player and item
+
+ if ( player.team == self.team )
+ {
+ if ( clienttype(player) != CLIENTTYPE_REAL || 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( (player.weapons & head.weapons) != head.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;
+
+ 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 > cvar("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)
+{
+ local entity head;
+ head = findchain(classname, "dom_controlpoint");
+ while (head)
+ {
+ if (vlen(head.origin - 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)
+{
+ local entity head;
+ local float t, noteam, distance;
+ noteam = ((self.team == 0) || !teams_matter); // fteqcc sucks
+
+ if (cvar("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 (self != head)
+ if (head.health > 0)
+ if ((noteam && (!bot_ignore_bots || clienttype(head) == CLIENTTYPE_REAL)) || head.team != self.team)
+ {
+ distance = vlen(head.origin - org);
+ if (distance < 100 || distance > sradius)
+ continue;
+
+ if(g_minstagib)
+ if(head.items & IT_STRENGTH)
+ 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;
+ }
+
+ t = (self.health + self.armorvalue ) / (head.health + head.armorvalue );
+ navigation_routerating(head, t * ratingscale, 2000);
+ }
+ }
+};
+
+// choose a role according to the situation
+void() havocbot_role_dm;
+
+//DOM:
+//go to best items, or control points you don't own
+void havocbot_role_dom()
+{
+ if(self.deadflag != DEAD_NO)
+ return;
+
+ if (self.bot_strategytime < time)
+ {
+ self.bot_strategytime = time + cvar("bot_ai_strategyinterval");
+ navigation_goalrating_start();
+ havocbot_goalrating_controlpoints(10000, self.origin, 15000);
+ havocbot_goalrating_items(8000, self.origin, 8000);
+ //havocbot_goalrating_enemyplayers(3000, self.origin, 2000);
+ //havocbot_goalrating_waypoints(1, self.origin, 1000);
+ navigation_goalrating_end();
+ }
+};
+
+//DM:
+//go to best items
+void havocbot_role_dm()
+{
+ if(self.deadflag != DEAD_NO)
+ return;
+
+ if (self.bot_strategytime < time)
+ {
+ self.bot_strategytime = time + cvar("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();
+ }
+};
+
+//Race:
+//go to next checkpoint, and annoy enemies
+.float race_checkpoint;
+void havocbot_role_race()
+{
+ if(self.deadflag != DEAD_NO)
+ return;
+
+ entity e;
+ if (self.bot_strategytime < time)
+ {
+ self.bot_strategytime = time + cvar("bot_ai_strategyinterval");
+ navigation_goalrating_start();
+ /*
+ havocbot_goalrating_items(100, self.origin, 10000);
+ havocbot_goalrating_enemyplayers(500, self.origin, 20000);
+ */
+
+ for(e = world; (e = find(e, classname, "trigger_race_checkpoint")) != world; )
+ {
+ if(e.cnt == self.race_checkpoint)
+ {
+ navigation_routerating(e, 1000000, 5000);
+ }
+ else if(self.race_checkpoint == -1)
+ {
+ navigation_routerating(e, 1000000, 5000);
+ }
+ }
+
+ navigation_goalrating_end();
+ }
+};
+
+void havocbot_chooserole_dm()
+{
+ self.havocbot_role = havocbot_role_dm;
+};
+
+void havocbot_chooserole_race()
+{
+ self.havocbot_role = havocbot_role_race;
+};
+
+void havocbot_chooserole_dom()
+{
+ self.havocbot_role = havocbot_role_dom;
+};
+
+void havocbot_chooserole()
+{
+ dprint("choosing a role...\n");
+ navigation_clearroute();
+ self.bot_strategytime = 0;
+ if (g_ctf)
+ havocbot_chooserole_ctf();
+ else if (g_domination)
+ havocbot_chooserole_dom();
+ else if (g_keyhunt)
+ havocbot_chooserole_kh();
+ else if (g_race || g_cts)
+ havocbot_chooserole_race();
+ else if (g_onslaught)
+ havocbot_chooserole_ons();
+ else // assume anything else is deathmatch
+ havocbot_chooserole_dm();
+};
Property changes on: trunk/data/qcsrc/server/bot/havocbot/roles.qc
___________________________________________________________________
Name: svn:mergeinfo
+
Name: svn:eol-style
+ native
Copied: trunk/data/qcsrc/server/bot/navigation.qc (from rev 7849, trunk/data/qcsrc/server/bots.qc)
===================================================================
--- trunk/data/qcsrc/server/bot/navigation.qc (rev 0)
+++ trunk/data/qcsrc/server/bot/navigation.qc 2009-09-20 21:43:29 UTC (rev 7851)
@@ -0,0 +1,1069 @@
+
+// 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)
+{
+ local vector org;
+ local vector move;
+ local vector dir;
+ local float dist;
+ local float totaldist;
+ local float stepdist;
+ local float yaw;
+ local float ignorehazards;
+ local float swimming;
+
+ #ifdef DEBUG_TRACEWALK
+ debugresetnodes();
+ debugnode(start);
+ #endif
+
+ move = end - start;
+ move_z = 0;
+ org = start;
+ dist = totaldist = vlen(move);
+ dir = normalize(move);
+ stepdist = 32;
+ ignorehazards = 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
+ #ifdef DEBUG_TRACEWALK
+ debugnodestatus(start, DEBUG_NODE_FAIL);
+ #endif
+ //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
+ #ifdef DEBUG_TRACEWALK
+ debugnodestatus(org, DEBUG_NODE_SUCCESS);
+ #endif
+ //print("tracewalk: ", vtos(start), " can reach ", vtos(end), "\n");
+ return TRUE;
+ }
+ #ifdef DEBUG_TRACEWALK
+ debugnode(org);
+ #endif
+
+ 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
+ #ifdef DEBUG_TRACEWALK
+ debugnodestatus(org, DEBUG_NODE_FAIL);
+ #endif
+ //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);
+
+ #ifdef DEBUG_TRACEWALK
+ debugnode(trace_endpos);
+ #endif
+
+ 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)
+ {
+ #ifdef DEBUG_TRACEWALK
+ debugnode(org);
+ #endif
+ if(pointcontents(org) == CONTENT_EMPTY)
+ break;
+ }
+
+ if not (pointcontents(org + '0 0 1') == CONTENT_EMPTY)
+ {
+ #ifdef DEBUG_TRACEWALK
+ debugnodestatus(org, DEBUG_NODE_FAIL);
+ #endif
+ 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);
+
+ #ifdef DEBUG_TRACEWALK
+ debugnode(trace_endpos);
+ #endif
+
+ // hit something
+ if (trace_fraction < 1)
+ {
+ // check if we can walk over this obstacle
+ tracebox(org + stepheightvec, m1, m2, move + stepheightvec, movemode, e);
+ if (trace_fraction < 1 || trace_startsolid)
+ {
+ #ifdef DEBUG_TRACEWALK
+ debugnodestatus(trace_endpos, DEBUG_NODE_WARNING);
+ #endif
+
+ // check for doors
+ traceline( org, move, movemode, e);
+ if ( trace_ent.classname == "door_rotating" || trace_ent.classname == "door")
+ {
+ local 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
+ {
+ #ifdef DEBUG_TRACEWALK
+ debugnodestatus(trace_endpos, DEBUG_NODE_FAIL);
+ #endif
+ //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;
+
+ // 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)
+ {
+ local float c;
+ c = pointcontents(org + '0 0 1');
+ if not(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
+ #ifdef DEBUG_TRACEWALK
+ debugnodestatus(org, DEBUG_NODE_FAIL);
+ #endif
+
+ return FALSE;
+};
+
+/////////////////////////////////////////////////////////////////////////////
+// goal stack
+/////////////////////////////////////////////////////////////////////////////
+
+// completely empty the goal stack, used when deciding where to go
+void navigation_clearroute()
+{
+ //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)
+void navigation_pushroute(entity e)
+{
+ //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()
+{
+ //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;
+};
+
+// find the spawnfunc_waypoint near a dynamic goal such as a dropped weapon
+entity navigation_findnearestwaypoint(entity ent, float walkfromwp)
+{
+ local entity waylist, w, best;
+ local float dist, bestdist;
+ local vector v, org, pm1, pm2;
+ pm1 = ent.origin + PL_MIN;
+ pm2 = ent.origin + PL_MAX;
+ 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)
+ if (boxesoverlap(pm1, pm2, w.absmin, w.absmax))
+ return w;
+ w = w.chain;
+ }
+
+ org = ent.origin + (ent.mins_z - PL_MIN_z) * '0 0 1';
+ if(ent.tag_entity)
+ org = org + ent.tag_entity.origin;
+ if (navigation_testtracewalk)
+ te_plasmaburn(org);
+
+ best = world;
+ bestdist = 1050;
+
+ // box check failed, try walk
+ w = waylist;
+ while (w)
+ {
+ // if object can walk from spawnfunc_waypoint
+ if(w != ent)
+ {
+ if (w.wpisbox)
+ {
+ local 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;
+ dist = vlen(v - org);
+ if (bestdist > dist)
+ {
+ traceline(v, org, TRUE, ent);
+ if (trace_fraction == 1)
+ {
+ if (walkfromwp)
+ {
+ //print("^1can I reach ", vtos(org), " from ", vtos(v), "?\n");
+ if (tracewalk(ent, v, PL_MIN, PL_MAX, org, bot_navigation_movemode))
+ {
+ bestdist = dist;
+ best = w;
+ }
+ }
+ else
+ {
+ if (tracewalk(ent, org, PL_MIN, PL_MAX, v, bot_navigation_movemode))
+ {
+ bestdist = dist;
+ best = w;
+ }
+ }
+ }
+ }
+ }
+ w = w.chain;
+ }
+ return best;
+}
+
+// finds the waypoints near the bot initiating a navigation query
+float navigation_markroutes_nearestwaypoints(entity waylist, float maxdist)
+{
+ local entity head;
+ local vector v, m1, m2, diff;
+ local 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)
+{
+ local vector m1;
+ local vector m2;
+ local 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()
+{
+ local entity w, wp, waylist;
+ local float searching, cost, cost2;
+ local 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;
+ }
+
+ // 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
+ local float i, increment, maxdistance;
+ if(self.flags & FL_ONGROUND)
+ {
+ increment = 750;
+ maxdistance = 50000;
+ }
+ else
+ {
+ increment = 500;
+ maxdistance = 1500;
+ }
+
+ for(i=increment;!navigation_markroutes_nearestwaypoints(waylist, i)&&i<maxdistance;i+=increment);
+
+ searching = TRUE;
+ while (searching)
+ {
+ searching = FALSE;
+ w = waylist;
+ while (w)
+ {
+ if (w.wpfire)
+ {
+ searching = TRUE;
+ w.wpfire = 0;
+ cost = w.wpcost;
+ p = w.wpnearestpoint;
+ wp = w.wp00;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp00mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
+ wp = w.wp01;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp01mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
+ wp = w.wp02;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp02mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
+ wp = w.wp03;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp03mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
+ wp = w.wp04;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp04mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
+ wp = w.wp05;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp05mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
+ wp = w.wp06;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp06mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
+ wp = w.wp07;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp07mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
+ wp = w.wp08;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp08mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
+ wp = w.wp09;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp09mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
+ wp = w.wp10;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp10mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
+ wp = w.wp11;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp11mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
+ wp = w.wp12;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp12mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
+ wp = w.wp13;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp13mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
+ wp = w.wp14;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp14mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
+ wp = w.wp15;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp15mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
+ wp = w.wp16;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp16mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
+ wp = w.wp17;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp17mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
+ wp = w.wp18;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp18mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
+ wp = w.wp19;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp19mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
+ wp = w.wp20;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp20mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
+ wp = w.wp21;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp21mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
+ wp = w.wp22;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp22mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
+ wp = w.wp23;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp23mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
+ wp = w.wp24;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp24mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
+ wp = w.wp25;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp25mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
+ wp = w.wp26;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp26mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
+ wp = w.wp27;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp27mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
+ wp = w.wp28;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp28mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
+ wp = w.wp29;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp29mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
+ wp = w.wp30;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp30mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
+ wp = w.wp31;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp31mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
+ }}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
+ }
+ w = w.chain;
+ }
+ }
+};
+
+void navigation_bestgoals_reset()
+{
+ local float i;
+
+ bestgoalswindex = 0;
+ bestgoalsrindex = 0;
+
+ for(i=0;i>MAX_BESTGOALS-1;++i)
+ {
+ navigation_bestgoals[i] = world;
+ }
+}
+
+void navigation_add_bestgoal(entity goal)
+{
+ if(bestgoalsrindex>0)
+ {
+ ++bestgoalsrindex;
+
+ if(bestgoalsrindex==MAX_BESTGOALS)
+ bestgoalsrindex = 0;
+ }
+
+ if(bestgoalswindex==MAX_BESTGOALS)
+ {
+ bestgoalswindex=0;
+ if(bestgoalsrindex==0)
+ bestgoalsrindex=1;
+ }
+
+ navigation_bestgoals[bestgoalswindex] = goal;
+
+ ++bestgoalswindex;
+}
+
+entity navigation_get_bestgoal()
+{
+ local entity ent;
+
+ ent = navigation_bestgoals[bestgoalsrindex];
+ navigation_bestgoals[bestgoalsrindex] = world;
+
+ ++bestgoalsrindex;
+
+ if(bestgoalsrindex==MAX_BESTGOALS)
+ bestgoalsrindex = 0;
+
+ return ent;
+}
+
+// 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)
+{
+ entity nwp;
+ if (!e)
+ return;
+
+ // Evaluate path using jetpack
+ if(g_jetpack)
+ if(self.items & IT_JETPACK)
+ if(cvar("bot_ai_navigation_jetpack"))
+ if(vlen(self.origin - e.origin) > cvar("bot_ai_navigation_jetpack_mindistance"))
+ {
+ vector pointa, pointb;
+
+ // dprint("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(e.origin, e.origin + '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)
+ {
+ // dprint("jetpack ai: can bridge these two points\n");
+
+ // Lower the altitude of these points as much as possible
+ local float zdistance, xydistance, cost, t, fuel;
+ local 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 / cvar("g_jetpack_maxspeed_up");
+ t += xydistance / cvar("g_jetpack_maxspeed_side");
+ fuel = t * cvar("g_jetpack_fuel") * 0.8;
+
+ // dprint("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 / (cvar("g_jetpack_maxspeed_side")/cvar("sv_maxspeed"));
+ cost += zdistance / (cvar("g_jetpack_maxspeed_up")/cvar("sv_maxspeed"));
+ cost *= 1.5;
+
+ // Compare against other goals
+ f = f * rangebias / (rangebias + cost);
+
+ if (navigation_bestrating < f)
+ {
+ // dprint("jetpack path: added goal", e.classname, " (with rating ", ftos(f), ")\n");
+ navigation_bestrating = f;
+ navigation_add_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
+ {
+ if (time > e.nearestwaypointtimeout)
+ {
+ e.nearestwaypoint = navigation_findnearestwaypoint(e, TRUE);
+
+ // 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;
+ }
+
+ //dprint("-- checking ", e.classname, " (with cost ", ftos(nwp.wpcost), ")\n");
+ if (nwp)
+ if (nwp.wpcost < 10000000)
+ {
+ //te_wizspike(nwp.wpnearestpoint);
+ // dprint(e.classname, " ", ftos(f), "/(1+", ftos((nwp.wpcost + vlen(e.origin - nwp.wpnearestpoint))), "/", ftos(rangebias), ") = ");
+ f = f * rangebias / (rangebias + (nwp.wpcost + vlen(e.origin - nwp.wpnearestpoint)));
+ //dprint("considering ", e.classname, " (with rating ", ftos(f), ")\n");
+ //dprint(ftos(f));
+ if (navigation_bestrating < f)
+ {
+ // dprint("ground path: added goal ", e.classname, " (with rating ", ftos(f), ")\n");
+ navigation_bestrating = f;
+ navigation_add_bestgoal(e);
+ }
+ }
+ //dprint("\n");
+};
+
+// adds an item to the the goal stack with the path to a given item
+float navigation_routetogoal(entity e, vector startposition)
+{
+ 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
+ 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.origin, 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;
+};
+
+void navigation_routetogoals()
+{
+ entity g1, g2;
+
+ navigation_clearroute();
+
+ g1 = navigation_get_bestgoal();
+ for(;;)
+ {
+ if(g2==world)
+ g2 = navigation_get_bestgoal();
+
+ if(g2==world)
+ {
+ navigation_routetogoal(g1, self.origin);
+ return;
+ }
+
+ if(navigation_routetogoal(g1, g2.origin))
+ {
+ g1 = g2;
+ g2 = world;
+ continue;
+ }
+
+ navigation_clearroute();
+ g1 = g2;
+ g2 = world;
+ }
+}
+
+// removes any currently touching waypoints from the goal stack
+// (this is how bots detect if they reached a goal)
+void navigation_poptouchedgoals()
+{
+ local 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<0.15)
+ {
+ navigation_poproute();
+ return;
+ }
+ }
+
+ // Loose goal touching check when running
+ if(self.aistatus & AI_STATUS_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()
+{
+ self.navigation_jetpack_goal = world;
+ navigation_bestrating = -1;
+ self.navigation_hasgoals = FALSE;
+ navigation_bestgoals_reset();
+ navigation_markroutes();
+};
+
+// ends a goal selection session (updates goal stack to the best goal)
+void navigation_goalrating_end()
+{
+ navigation_routetogoals();
+// dprint("best goal ", self.goalcurrent.classname , "\n");
+
+ // Hack: if it can't walk to any goal just move blindly to the first visible waypoint
+ if not (self.navigation_hasgoals)
+ {
+ dprint(self.netname, " can't walk to any goal, going to a near waypoint\n");
+
+ entity head;
+
+ RandomSelection_Init();
+ head = findradius(self.origin,1000);
+ while(head)
+ {
+ if(head.classname=="waypoint")
+ if(!(head.wpflags & WAYPOINTFLAG_GENERATED))
+ if(vlen(self.origin-head.origin)>100)
+ if(checkpvs(self.view_ofs,head))
+ RandomSelection_Add(head, 0, string_null, 1 + (vlen(self.origin-head.origin)<500), 0);
+ head = head.chain;
+ }
+ if(RandomSelection_chosen_ent)
+ navigation_routetogoal(RandomSelection_chosen_ent, self.origin);
+
+ self.navigation_hasgoals = FALSE; // Reset this value
+ }
+};
+
+void botframe_updatedangerousobjects(float maxupdate)
+{
+ local entity head, bot_dodgelist;
+ local vector m1, m2, v;
+ local 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);
+ d = head.bot_dodgerating - vlen(head.origin - v);
+ if (d > 0)
+ {
+ traceline(head.origin, 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");
+ }
+};
+
+#ifdef DEBUG_TRACEWALK
+
+void debugresetnodes()
+{
+ debuglastnode = '0 0 0';
+}
+
+void debugnode(vector node)
+{
+ if not(self.classname=="player")
+ return;
+
+ if(debuglastnode=='0 0 0')
+ {
+ debuglastnode = node;
+ return;
+ }
+
+ te_lightning2(world, node, debuglastnode);
+ debuglastnode = node;
+}
+
+void debugnodestatus(vector position, float status)
+{
+ vector color;
+
+ switch (status)
+ {
+ case DEBUG_NODE_SUCCESS:
+ color = '0 15 0';
+ break;
+ case DEBUG_NODE_WARNING:
+ color = '15 15 0';
+ break;
+ case DEBUG_NODE_FAIL:
+ color = '15 0 0';
+ break;
+ default:
+ color = '15 15 15';
+ }
+
+ te_customflash(position, 40, 2, color);
+}
+
+#endif
+
+#ifdef DEBUG_BOT_GOALSTACK
+
+.float goalcounter;
+.vector lastposition;
+
+// Debug the goal stack visually
+void debuggoalstack()
+{
+ local entity target;
+ local vector org;
+
+ if(self.goalcounter==0)target=self.goalcurrent;
+ else if(self.goalcounter==1)target=self.goalstack01;
+ else if(self.goalcounter==2)target=self.goalstack02;
+ else if(self.goalcounter==3)target=self.goalstack03;
+ else if(self.goalcounter==4)target=self.goalstack04;
+ else if(self.goalcounter==5)target=self.goalstack05;
+ else if(self.goalcounter==6)target=self.goalstack06;
+ else if(self.goalcounter==7)target=self.goalstack07;
+ else if(self.goalcounter==8)target=self.goalstack08;
+ else if(self.goalcounter==9)target=self.goalstack09;
+ else if(self.goalcounter==10)target=self.goalstack10;
+ else if(self.goalcounter==11)target=self.goalstack11;
+ else if(self.goalcounter==12)target=self.goalstack12;
+ else if(self.goalcounter==13)target=self.goalstack13;
+ else if(self.goalcounter==14)target=self.goalstack14;
+ else if(self.goalcounter==15)target=self.goalstack15;
+ else if(self.goalcounter==16)target=self.goalstack16;
+ else if(self.goalcounter==17)target=self.goalstack17;
+ else if(self.goalcounter==18)target=self.goalstack18;
+ else if(self.goalcounter==19)target=self.goalstack19;
+ else if(self.goalcounter==20)target=self.goalstack20;
+ else if(self.goalcounter==21)target=self.goalstack21;
+ else if(self.goalcounter==22)target=self.goalstack22;
+ else if(self.goalcounter==23)target=self.goalstack23;
+ else if(self.goalcounter==24)target=self.goalstack24;
+ else if(self.goalcounter==25)target=self.goalstack25;
+ else if(self.goalcounter==26)target=self.goalstack26;
+ else if(self.goalcounter==27)target=self.goalstack27;
+ else if(self.goalcounter==28)target=self.goalstack28;
+ else if(self.goalcounter==29)target=self.goalstack29;
+ else if(self.goalcounter==30)target=self.goalstack30;
+ else if(self.goalcounter==31)target=self.goalstack31;
+
+ if(target==world)
+ {
+ self.goalcounter = 0;
+ self.lastposition='0 0 0';
+ return;
+ }
+
+ if(self.lastposition=='0 0 0')
+ org = self.origin;
+ else
+ org = self.lastposition;
+
+
+ te_lightning2(world, org, target.origin);
+ self.lastposition = target.origin;
+
+ self.goalcounter++;
+}
+
+#endif
Property changes on: trunk/data/qcsrc/server/bot/navigation.qc
___________________________________________________________________
Name: svn:mergeinfo
+
Name: svn:eol-style
+ native
Added: trunk/data/qcsrc/server/bot/navigation.qh
===================================================================
--- trunk/data/qcsrc/server/bot/navigation.qh (rev 0)
+++ trunk/data/qcsrc/server/bot/navigation.qh 2009-09-20 21:43:29 UTC (rev 7851)
@@ -0,0 +1,78 @@
+/*
+ * Globals and Fields
+ */
+
+float bestgoalswindex;
+float bestgoalsrindex;
+float navigation_bestrating;
+float bot_navigation_movemode;
+float navigation_testtracewalk;
+
+vector stepheightvec;
+entity botframe_dangerwaypoint;
+
+#define MAX_BESTGOALS 3
+entity navigation_bestgoals[MAX_BESTGOALS];
+
+// 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;
+
+.entity navigation_jetpack_goal;
+.vector navigation_jetpack_point;
+
+#ifdef DEBUG_TRACEWALK
+float DEBUG_NODE_SUCCESS = 1;
+float DEBUG_NODE_WARNING = 2;
+float DEBUG_NODE_FAIL = 3;
+vector debuglastnode;
+#endif
+
+/*
+ * Functions
+ */
+
+#ifdef DEBUG_TRACEWALK
+void debugresetnodes();
+void debugnode(vector node);
+void debugnodestatus(vector position, float status);
+#endif
+
+#ifdef DEBUG_BOT_GOALSTACK
+void debuggoalstack();
+#endif
+
+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();
+void navigation_bestgoals_reset();
+void navigation_add_bestgoal(entity goal);
+void navigation_routerating(entity e, float f, float rangebias);
+void navigation_routetogoals();
+void navigation_poptouchedgoals();
+void navigation_goalrating_start();
+void navigation_goalrating_end();
+
+void botframe_updatedangerousobjects(float maxupdate);
+
+entity navigation_get_bestgoal();
+entity navigation_findnearestwaypoint(entity ent, float walkfromwp);
Copied: trunk/data/qcsrc/server/bot/scripting.qc (from rev 7849, trunk/data/qcsrc/server/bots_scripting.qc)
===================================================================
--- trunk/data/qcsrc/server/bot/scripting.qc (rev 0)
+++ trunk/data/qcsrc/server/bot/scripting.qc 2009-09-20 21:43:29 UTC (rev 7851)
@@ -0,0 +1,1306 @@
+.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)
+ error("clearqueue but no queue allocated");
+ buf_del(bot.bot_cmdqueuebuf);
+ bot.bot_cmdqueuebuf_allocated = FALSE;
+ dprint("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;
+ }
+
+ bufstr_set(bot.bot_cmdqueuebuf, bot.bot_cmdqueuebuf_end, cmdstring);
+ 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;
+}
+
+#define MAX_BOT_PLACES 4
+.float bot_places_count;
+.entity bot_places[MAX_BOT_PLACES]; FTEQCC_YOU_SUCK_THIS_IS_NOT_UNREFERENCED(bot_places);
+.string bot_placenames[MAX_BOT_PLACES]; FTEQCC_YOU_SUCK_THIS_IS_NOT_UNREFERENCED(bot_placenames);
+entity bot_getplace(string placename)
+{
+ entity e;
+ if(substring(placename, 0, 1) == "@")
+ {
+ float 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)
+ print("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)
+ print("invalid place ", s, "\n");
+ return e;
+ }
+}
+
+
+// NOTE: New commands should be added here. Do not forget to update BOT_CMD_COUNTER
+#define BOT_CMD_NULL 0
+#define BOT_CMD_PAUSE 1
+#define BOT_CMD_CONTINUE 2
+#define BOT_CMD_WAIT 3
+#define BOT_CMD_TURN 4
+#define BOT_CMD_MOVETO 5
+#define BOT_CMD_RESETGOAL 6 // Not implemented yet
+#define BOT_CMD_CC 7
+#define BOT_CMD_IF 8
+#define BOT_CMD_ELSE 9
+#define BOT_CMD_FI 10
+#define BOT_CMD_RESETAIM 11
+#define BOT_CMD_AIM 12
+#define BOT_CMD_PRESSKEY 13
+#define BOT_CMD_RELEASEKEY 14
+#define BOT_CMD_SELECTWEAPON 15
+#define BOT_CMD_IMPULSE 16
+#define BOT_CMD_WAIT_UNTIL 17
+#define BOT_CMD_MOVETOTARGET 18
+#define BOT_CMD_AIMTARGET 19
+#define BOT_CMD_BARRIER 20
+#define BOT_CMD_CONSOLE 21
+#define BOT_CMD_SOUND 22
+#define BOT_CMD_WHILE 23 // TODO: Not implemented yet
+#define BOT_CMD_WEND 24 // TODO: Not implemented yet
+#define BOT_CMD_CHASE 25 // TODO: Not implemented yet
+
+#define BOT_CMD_COUNTER 23 // 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;
+
+//
+#define BOT_CMD_PARAMETER_NONE 0
+#define BOT_CMD_PARAMETER_FLOAT 1
+#define BOT_CMD_PARAMETER_STRING 2
+#define BOT_CMD_PARAMETER_VECTOR 3
+
+float bot_cmds_initialized;
+float 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
+.float 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
+
+// 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_cmds_initialized = TRUE;
+}
+
+// Returns first bot with matching name
+entity find_bot_by_name(string name)
+{
+ local entity bot;
+
+ bot = findchainflags(flags, FL_CLIENT);
+ while (bot)
+ {
+ if(clienttype(bot) == CLIENTTYPE_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)
+{
+ local entity bot;
+ local float c;
+
+ if(!number)
+ return world;
+
+ bot = findchainflags(flags, FL_CLIENT);
+ while (bot)
+ {
+ if(clienttype(bot) == CLIENTTYPE_BOT)
+ {
+ if(++c==number)
+ return bot;
+ }
+ bot = bot.chain;
+ }
+
+ return world;
+}
+
+float bot_decodecommand(string cmdstring)
+{
+ local float cmd_parm_type, i;
+ 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();
+
+ for(i=1;i<BOT_CMD_COUNTER;++i)
+ {
+ if(bot_cmd_string[i]!=cmdstring)
+ continue;
+
+ cmd_parm_type = bot_cmd_parm_type[i];
+
+ if(cmd_parm_type!=BOT_CMD_PARAMETER_NONE&&parm=="")
+ {
+ print("ERROR: A parameter is required for this command\n");
+ return 0;
+ }
+
+ // Load command into queue
+ bot_cmd.bot_cmd_type = i;
+
+ // Attach parameter
+ switch(cmd_parm_type)
+ {
+ case BOT_CMD_PARAMETER_FLOAT:
+ bot_cmd.bot_cmd_parm_float = stof(parm);
+ break;
+ case BOT_CMD_PARAMETER_STRING:
+ if(bot_cmd.bot_cmd_parm_string)
+ strunzone(bot_cmd.bot_cmd_parm_string);
+ bot_cmd.bot_cmd_parm_string = strzone(parm);
+ break;
+ case BOT_CMD_PARAMETER_VECTOR:
+ bot_cmd.bot_cmd_parm_vector = stov(parm);
+ break;
+ default:
+ break;
+ }
+ return 1;
+ }
+ print("ERROR: No such command '", cmdstring, "'\n");
+ return 0;
+}
+
+void bot_cmdhelp(string scmd)
+{
+ local float i, ntype;
+ local string stype;
+
+ if(!bot_cmds_initialized)
+ bot_commands_init();
+
+ for(i=1;i<BOT_CMD_COUNTER;++i)
+ {
+ if(bot_cmd_string[i]!=scmd)
+ continue;
+
+ ntype = bot_cmd_parm_type[i];
+
+ switch(ntype)
+ {
+ case BOT_CMD_PARAMETER_FLOAT:
+ stype = "float number";
+ break;
+ case BOT_CMD_PARAMETER_STRING:
+ stype = "string";
+ break;
+ case BOT_CMD_PARAMETER_VECTOR:
+ stype = "vector";
+ break;
+ default:
+ stype = "none";
+ break;
+ }
+
+ print(strcat("Command: ",bot_cmd_string[i],"\nParameter: <",stype,"> \n"));
+
+ print("Description: ");
+ switch(i)
+ {
+ case BOT_CMD_PAUSE:
+ print("Stops the bot completely. Any command other than 'continue' will be ignored.");
+ break;
+ case BOT_CMD_CONTINUE:
+ print("Disable paused status");
+ break;
+ case BOT_CMD_WAIT:
+ print("Pause command parsing and bot ai for N seconds. Pressed key will remain pressed");
+ break;
+ case BOT_CMD_WAIT_UNTIL:
+ print("Pause command parsing and bot ai until time is N from the last barrier. Pressed key will remain pressed");
+ break;
+ case BOT_CMD_BARRIER:
+ print("Waits till all bots that have a command queue reach this command. Pressed key will remain pressed");
+ break;
+ case BOT_CMD_TURN:
+ print("Look to the right or left N degrees. For turning to the left use positive numbers.");
+ break;
+ case BOT_CMD_MOVETO:
+ print("Walk to an specific coordinate on the map. Usage: moveto \"x y z\"");
+ break;
+ case BOT_CMD_MOVETOTARGET:
+ print("Walk to the specific target on the map");
+ break;
+ case BOT_CMD_RESETGOAL:
+ print("Resets the goal stack");
+ break;
+ case BOT_CMD_CC:
+ print("Execute client command. Examples: cc \"say something\"; cc god; cc \"name newnickname\"; cc kill;");
+ break;
+ case BOT_CMD_IF:
+ print("Perform simple conditional execution.\n");
+ print("Syntax: \n");
+ print(" sv_cmd .. if \"condition\"\n");
+ print(" sv_cmd .. <instruction if true>\n");
+ print(" sv_cmd .. <instruction if true>\n");
+ print(" sv_cmd .. else\n");
+ print(" sv_cmd .. <instruction if false>\n");
+ print(" sv_cmd .. <instruction if false>\n");
+ print(" sv_cmd .. fi\n");
+ print("Conditions: a=b, a>b, a<b, a\t\t(spaces not allowed)\n");
+ print(" Values in conditions can be numbers, cvars in the form cvar.cvar_string or special fields\n");
+ print("Fields: health, speed, flagcarrier\n");
+ print("Examples: if health>50; if health>cvar.g_balance_laser_primary_damage; if flagcarrier;");
+ break;
+ case BOT_CMD_RESETAIM:
+ print("Points the aim to the coordinates x,y 0,0");
+ break;
+ case BOT_CMD_AIM:
+ print("Move the aim x/y (horizontal/vertical) degrees relatives to the bot\n");
+ print("There is a 3rd optional parameter telling in how many seconds the aim has to reach the new position\n");
+ print("Examples: aim \"90 0\" // Turn 90 degrees inmediately (positive numbers move to the left/up)\n");
+ print(" aim \"0 90 2\" // Will gradually look to the sky in the next two seconds");
+ break;
+ case BOT_CMD_AIMTARGET:
+ print("Points the aim to given target");
+ break;
+ case BOT_CMD_PRESSKEY:
+ print("Press one of the following keys: forward, backward, left, right, jump, crouch, attack1, attack2, use\n");
+ print("Multiple keys can be pressed at time (with many presskey calls) and it will remain pressed until the command \"releasekey\" is called");
+ print("Note: The script will not return the control to the bot ai until all keys are released");
+ break;
+ case BOT_CMD_RELEASEKEY:
+ print("Release previoulsy used keys. Use the parameter \"all\" to release all keys");
+ break;
+ case BOT_CMD_SOUND:
+ print("play sound file at bot location");
+ break;
+ default:
+ print("This command has no description yet.");
+ break;
+ }
+ print("\n");
+ }
+}
+
+void bot_list_commands()
+{
+ local float i;
+ local string ptype;
+
+ if(!bot_cmds_initialized)
+ bot_commands_init();
+
+ print("List of all available commands:\n");
+ print(" Command\t\t\t\tParameter Type\n");
+
+ for(i=1;i<BOT_CMD_COUNTER;++i)
+ {
+ switch(bot_cmd_parm_type[i])
+ {
+ case BOT_CMD_PARAMETER_FLOAT:
+ ptype = "float number";
+ break;
+ case BOT_CMD_PARAMETER_STRING:
+ ptype = "string";
+ break;
+ case BOT_CMD_PARAMETER_VECTOR:
+ ptype = "vector";
+ break;
+ default:
+ ptype = "none";
+ break;
+ }
+ print(strcat(" ",bot_cmd_string[i],"\t\t\t\t<",ptype,"> \n"));
+ }
+}
+
+// Commands code
+.float bot_exec_status;
+
+#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 SV_ParseClientCommand(string s);
+float bot_cmd_cc()
+{
+ SV_ParseClientCommand(bot_cmd.bot_cmd_parm_string);
+ return CMD_STATUS_FINISHED;
+}
+
+float bot_cmd_impulse()
+{
+ self.impulse = bot_cmd.bot_cmd_parm_float;
+ return CMD_STATUS_FINISHED;
+}
+
+float bot_cmd_continue()
+{
+ self.bot_exec_status &~= BOT_EXEC_STATUS_PAUSED;
+ return CMD_STATUS_FINISHED;
+}
+
+.float bot_cmd_wait_time;
+float bot_cmd_wait()
+{
+ 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()
+{
+ 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()
+{
+ 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()
+{
+ 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()
+{
+ local 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;
+}
+
+.float bot_cmd_condition_status;
+
+#define CMD_CONDITION_NONE 0
+#define CMD_CONDITION_TRUE 1
+#define CMD_CONDITION_FALSE 2
+#define CMD_CONDITION_TRUE_BLOCK 4
+#define CMD_CONDITION_FALSE_BLOCK 8
+
+float bot_cmd_eval(string expr)
+{
+ // 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));
+ }
+
+ print(strcat("ERROR: Unable to convert the expression '",expr,"' into a numeric value\n"));
+ return 0;
+}
+
+float bot_cmd_if()
+{
+ local string expr, val_a, val_b;
+ local float cmpofs;
+
+ if(self.bot_cmd_condition_status != CMD_CONDITION_NONE)
+ {
+ // Only one "if" block is allowed at time
+ print("ERROR: Only one conditional block can be processed at time");
+ bot_clearqueue(self);
+ return CMD_STATUS_ERROR;
+ }
+
+ self.bot_cmd_condition_status |= CMD_CONDITION_TRUE_BLOCK;
+
+ // search for operators
+ expr = bot_cmd.bot_cmd_parm_string;
+
+ cmpofs = strstrofs(expr,"=",0);
+
+ if(cmpofs>0)
+ {
+ val_a = substring(expr,0,cmpofs);
+ val_b = substring(expr,cmpofs+1,strlen(expr));
+
+ if(bot_cmd_eval(val_a)==bot_cmd_eval(val_b))
+ self.bot_cmd_condition_status |= CMD_CONDITION_TRUE;
+ else
+ self.bot_cmd_condition_status |= CMD_CONDITION_FALSE;
+
+ return CMD_STATUS_FINISHED;
+ }
+
+ cmpofs = strstrofs(expr,">",0);
+
+ if(cmpofs>0)
+ {
+ val_a = substring(expr,0,cmpofs);
+ val_b = substring(expr,cmpofs+1,strlen(expr));
+
+ if(bot_cmd_eval(val_a)>bot_cmd_eval(val_b))
+ self.bot_cmd_condition_status |= CMD_CONDITION_TRUE;
+ else
+ self.bot_cmd_condition_status |= CMD_CONDITION_FALSE;
+
+ return CMD_STATUS_FINISHED;
+ }
+
+ cmpofs = strstrofs(expr,"<",0);
+
+ if(cmpofs>0)
+ {
+ val_a = substring(expr,0,cmpofs);
+ val_b = substring(expr,cmpofs+1,strlen(expr));
+
+ if(bot_cmd_eval(val_a)<bot_cmd_eval(val_b))
+ self.bot_cmd_condition_status |= CMD_CONDITION_TRUE;
+ else
+ self.bot_cmd_condition_status |= CMD_CONDITION_FALSE;
+
+ return CMD_STATUS_FINISHED;
+ }
+
+ if(bot_cmd_eval(expr))
+ self.bot_cmd_condition_status |= CMD_CONDITION_TRUE;
+ else
+ self.bot_cmd_condition_status |= CMD_CONDITION_FALSE;
+
+ return CMD_STATUS_FINISHED;
+}
+
+float bot_cmd_else()
+{
+ self.bot_cmd_condition_status &~= CMD_CONDITION_TRUE_BLOCK;
+ self.bot_cmd_condition_status |= CMD_CONDITION_FALSE_BLOCK;
+ return CMD_STATUS_FINISHED;
+}
+
+float bot_cmd_fi()
+{
+ self.bot_cmd_condition_status = CMD_CONDITION_NONE;
+ return CMD_STATUS_FINISHED;
+}
+
+float bot_cmd_resetaim()
+{
+ self.v_angle = '0 0 0';
+ return CMD_STATUS_FINISHED;
+}
+
+.float bot_cmd_aim_begintime;
+.float bot_cmd_aim_endtime;
+.vector bot_cmd_aim_begin;
+.vector bot_cmd_aim_end;
+
+float bot_cmd_aim()
+{
+ // Current direction
+ if(self.bot_cmd_aim_endtime)
+ {
+ local float progress;
+
+ progress = min(1 - (self.bot_cmd_aim_endtime - time) / (self.bot_cmd_aim_endtime - self.bot_cmd_aim_begintime),1);
+ self.v_angle = self.bot_cmd_aim_begin + ((self.bot_cmd_aim_end - self.bot_cmd_aim_begin) * progress);
+
+ if(time>=self.bot_cmd_aim_endtime)
+ {
+ self.bot_cmd_aim_endtime = 0;
+ return CMD_STATUS_FINISHED;
+ }
+ else
+ return CMD_STATUS_EXECUTING;
+ }
+
+ // New aiming direction
+ local string parms;
+ local float tokens, step;
+
+ parms = bot_cmd.bot_cmd_parm_string;
+
+ tokens = tokenizebyseparator(parms, " ");
+
+ if(tokens==2)
+ {
+ self.v_angle_x -= stof(argv(1));
+ self.v_angle_y += stof(argv(0));
+ return CMD_STATUS_FINISHED;
+ }
+
+ if(tokens<2||tokens>3)
+ return CMD_STATUS_ERROR;
+
+ step = stof(argv(2));
+
+ 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()
+{
+ if(self.bot_cmd_aim_endtime)
+ {
+ return bot_cmd_aim();
+ }
+
+ local entity e;
+ local string parms;
+ local vector v;
+ local 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;
+}
+
+.float bot_cmd_keys;
+
+#define BOT_CMD_KEY_NONE 0
+#define BOT_CMD_KEY_FORWARD 1
+#define BOT_CMD_KEY_BACKWARD 2
+#define BOT_CMD_KEY_RIGHT 4
+#define BOT_CMD_KEY_LEFT 8
+#define BOT_CMD_KEY_JUMP 16
+#define BOT_CMD_KEY_ATTACK1 32
+#define BOT_CMD_KEY_ATTACK2 64
+#define BOT_CMD_KEY_USE 128
+#define BOT_CMD_KEY_HOOK 256
+#define BOT_CMD_KEY_CROUCH 512
+#define BOT_CMD_KEY_CHAT 1024
+
+float bot_presskeys()
+{
+ self.movement = '0 0 0';
+
+ if(self.bot_cmd_keys == BOT_CMD_KEY_NONE)
+ return FALSE;
+
+ if(self.bot_cmd_keys & BOT_CMD_KEY_FORWARD)
+ self.movement_x = cvar("sv_maxspeed");
+ else if(self.bot_cmd_keys & BOT_CMD_KEY_BACKWARD)
+ self.movement_x = -cvar("sv_maxspeed");
+
+ if(self.bot_cmd_keys & BOT_CMD_KEY_RIGHT)
+ self.movement_y = cvar("sv_maxspeed");
+ else if(self.bot_cmd_keys & BOT_CMD_KEY_LEFT)
+ self.movement_y = -cvar("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)
+{
+ 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()
+{
+ local string key;
+
+ key = bot_cmd.bot_cmd_parm_string;
+
+ bot_cmd_keypress_handler(key,TRUE);
+
+ return CMD_STATUS_FINISHED;
+}
+
+float bot_cmd_releasekey()
+{
+ local string key;
+
+ key = bot_cmd.bot_cmd_parm_string;
+
+ return bot_cmd_keypress_handler(key,FALSE);
+}
+
+float bot_cmd_pause()
+{
+ 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()
+{
+ return self.cmd_moveto(bot_cmd.bot_cmd_parm_vector);
+}
+
+float bot_cmd_movetotarget()
+{
+ 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()
+{
+ return self.cmd_resetgoal();
+}
+
+
+float bot_cmd_sound()
+{
+ string f;
+ f = bot_cmd.bot_cmd_parm_string;
+
+ precache_sound(f);
+ sound(self, CHAN_WEAPON2, f, VOL_BASE, ATTN_MIN);
+
+ return CMD_STATUS_FINISHED;
+}
+
+//
+
+void bot_command_executed(float rm)
+{
+ entity cmd;
+
+ cmd = bot_cmd;
+
+ if(rm)
+ bot_dequeuecommand(self, self.bot_cmd_execution_index);
+
+ self.bot_cmd_execution_index++;
+}
+
+void bot_setcurrentcommand()
+{
+ 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;
+ float i;
+
+ FOR_EACH_CLIENT(cl) if(cl.isbot)
+ {
+ if(cl.bot_cmdqueuebuf_allocated)
+ bot_clearqueue(cl);
+ // also, cancel all barriers
+ cl.bot_barrier = 0;
+ for(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()
+{
+ local float status, ispressingkey;
+
+ if(self.deadflag!=DEAD_NO)
+ return 0;
+
+ // Find command
+ bot_setcurrentcommand();
+
+ // 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);
+ print( "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();
+
+ if(bot_cmd==world)
+ return ispressingkey;
+
+ // Handle conditions
+ if not(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;
+ default:
+ print(strcat("ERROR: Invalid command on queue with id '",ftos(bot_cmd.bot_cmd_type),"'\n"));
+ return 0;
+ }
+
+ if (status==CMD_STATUS_ERROR)
+ print(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(cvar("g_debug_bot_commands"))
+ {
+ local 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;
+}
Property changes on: trunk/data/qcsrc/server/bot/scripting.qc
___________________________________________________________________
Name: svn:executable
+ *
Name: svn:mergeinfo
+
Name: svn:eol-style
+ native
Copied: trunk/data/qcsrc/server/bot/waypoints.qc (from rev 7849, trunk/data/qcsrc/server/bots.qc)
===================================================================
--- trunk/data/qcsrc/server/bot/waypoints.qc (rev 0)
+++ trunk/data/qcsrc/server/bot/waypoints.qc 2009-09-20 21:43:29 UTC (rev 7851)
@@ -0,0 +1,905 @@
+// 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)
+{
+ local entity w;
+ local vector org;
+ w = find(world, classname, "waypoint");
+
+ if not(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;
+ 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(!(f & WAYPOINTFLAG_GENERATED))
+ if(!w.wpisbox)
+ {
+ traceline(w.origin + '0 0 1', w.origin + PL_MIN_z * '0 0 1' - '0 0 1', MOVE_NOMONSTERS, w);
+ if (trace_fraction < 1)
+ setorigin(w, trace_endpos - PL_MIN_z * '0 0 1');
+
+ // check if the start position is stuck
+ tracebox(w.origin, PL_MIN + '-1 -1 -1', PL_MAX + '1 1 1', w.origin, MOVE_NOMONSTERS, w);
+ if (trace_startsolid)
+ {
+ org = w.origin + '0 0 26';
+ tracebox(org, PL_MIN, PL_MAX, w.origin, MOVE_WORLDONLY, w);
+ if(trace_startsolid)
+ {
+ org = w.origin + '2 2 2';
+ tracebox(org, PL_MIN, PL_MAX, w.origin, MOVE_WORLDONLY, w);
+ if(trace_startsolid)
+ {
+ org = w.origin + '-2 -2 2';
+ tracebox(org, PL_MIN, PL_MAX, w.origin, MOVE_WORLDONLY, w);
+ if(trace_startsolid)
+ {
+ org = w.origin + '-2 2 2';
+ tracebox(org, PL_MIN, PL_MAX, w.origin, MOVE_WORLDONLY, w);
+ if(trace_startsolid)
+ {
+ org = w.origin + '2 -2 2';
+ tracebox(org, PL_MIN, PL_MAX, w.origin, MOVE_WORLDONLY, w);
+ if(trace_startsolid)
+ {
+ // this WP is in solid, refuse it
+ dprint("Killed a waypoint that was stuck in solid at ", vtos(org), "\n");
+ remove(w);
+ return world;
+ }
+ }
+ }
+ }
+ }
+ setorigin(w, org * 0.05 + trace_endpos * 0.95); // don't trust the trace fully
+ }
+
+ tracebox(w.origin, PL_MIN, PL_MAX, w.origin - '0 0 128', MOVE_WORLDONLY, w);
+ if(trace_startsolid)
+ {
+ dprint("Killed a waypoint that was stuck in solid ", vtos(w.origin), "\n");
+ remove(w);
+ return world;
+ }
+ if (!trace_inwater)
+ {
+ if(trace_fraction == 1)
+ {
+ dprint("Killed a waypoint that was stuck in air at ", vtos(w.origin), "\n");
+ remove(w);
+ return world;
+ }
+ trace_endpos_z += 0.1; // don't trust the trace fully
+// dprint("Moved waypoint at ", vtos(w.origin), " by ", ftos(vlen(w.origin - trace_endpos)));
+// dprint(" direction: ", vtos((trace_endpos - w.origin)), "\n");
+ setorigin(w, trace_endpos);
+ }
+ }
+
+ waypoint_clearlinks(w);
+ //waypoint_schedulerelink(w);
+
+ if (cvar("g_waypointeditor"))
+ {
+ m1 = w.mins;
+ m2 = w.maxs;
+ setmodel(w, "models/runematch/rune.mdl"); 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)
+{
+ local 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
+ local 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()
+{
+ local entity e;
+ local vector sv, sm1, sm2, ev, em1, em2, dv;
+
+ stepheightvec = cvar("sv_stepheight") * '0 0 1';
+ bot_navigation_movemode = ((cvar("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
+ local 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 (cvar("g_waypointeditor"))
+ {
+ local vector m1, m2;
+ m1 = wp.mins;
+ m2 = wp.maxs;
+ setmodel(wp, "models/runematch/rune.mdl"); 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
+void 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()
+{
+ local 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()
+{
+ local 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()
+{
+ local string filename, s;
+ local float file, tokens, c, found;
+ local entity wp_from, wp_to;
+ local vector wp_to_pos, wp_from_pos;
+ filename = strcat("maps/", mapname);
+ filename = strcat(filename, ".waypoints.cache");
+ file = fopen(filename, FILE_READ);
+
+ if (file < 0)
+ {
+ dprint("waypoint links load from ");
+ dprint(filename);
+ dprint(" failed\n");
+ return FALSE;
+ }
+
+ while (1)
+ {
+ s = fgets(file);
+ if (!s)
+ break;
+
+ 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.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)
+ {
+ // can't find that waypoint
+ fclose(file);
+ return FALSE;
+ }
+ }
+
+ // 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)
+ {
+ // can't find that waypoint
+ fclose(file);
+ return FALSE;
+ }
+
+ ++c;
+ waypoint_addlink(wp_from, wp_to);
+ }
+
+ fclose(file);
+
+ dprint("loaded ");
+ dprint(ftos(c));
+ dprint(" waypoint links from maps/");
+ dprint(mapname);
+ dprint(".waypoints.cache\n");
+
+ botframe_cachedwaypointlinks = TRUE;
+ return TRUE;
+};
+
+void waypoint_load_links_hardwired()
+{
+ local string filename, s;
+ local float file, tokens, c, found;
+ local entity wp_from, wp_to;
+ local 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)
+ {
+ dprint("waypoint links load from ");
+ dprint(filename);
+ dprint(" failed\n");
+ return;
+ }
+
+ for (;;)
+ {
+ s = fgets(file);
+ if (!s)
+ break;
+
+ 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.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)
+ {
+ print(strcat("NOTICE: Can not find waypoint at ", vtos(wp_from_pos), ". Path skipped\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)
+ {
+ print(strcat("NOTICE: Can not find waypoint at ", vtos(wp_to_pos), ". Path skipped\n"));
+ continue;
+ }
+
+ ++c;
+ waypoint_addlink(wp_from, wp_to);
+ }
+
+ fclose(file);
+
+ dprint("loaded ");
+ dprint(ftos(c));
+ dprint(" waypoint links from maps/");
+ dprint(mapname);
+ dprint(".waypoints.hardwired\n");
+};
+
+// Save all waypoint links to a file
+void waypoint_save_links()
+{
+ local string filename, s;
+ local float file, c, i;
+ local entity w, link;
+ filename = strcat("maps/", mapname);
+ filename = strcat(filename, ".waypoints.cache");
+ file = fopen(filename, FILE_WRITE);
+ if (file < 0)
+ {
+ print("waypoint links save to ");
+ print(filename);
+ print(" failed\n");
+ }
+ c = 0;
+ w = findchain(classname, "waypoint");
+ while (w)
+ {
+ for(i=0;i<32;++i)
+ {
+ // :S
+ switch(i)
+ {
+ // for i in $(seq -w 0 31); do echo "case $i:link = w.wp$i; break;"; done;
+ case 00:link = w.wp00; break;
+ case 01:link = w.wp01; break;
+ case 02:link = w.wp02; break;
+ case 03:link = w.wp03; break;
+ case 04:link = w.wp04; break;
+ case 05:link = w.wp05; break;
+ case 06:link = w.wp06; break;
+ case 07:link = w.wp07; break;
+ case 08:link = w.wp08; break;
+ case 09:link = w.wp09; break;
+ case 10:link = w.wp10; break;
+ case 11:link = w.wp11; break;
+ case 12:link = w.wp12; break;
+ case 13:link = w.wp13; break;
+ case 14:link = w.wp14; break;
+ case 15:link = w.wp15; break;
+ case 16:link = w.wp16; break;
+ case 17:link = w.wp17; break;
+ case 18:link = w.wp18; break;
+ case 19:link = w.wp19; break;
+ case 20:link = w.wp20; break;
+ case 21:link = w.wp21; break;
+ case 22:link = w.wp22; break;
+ case 23:link = w.wp23; break;
+ case 24:link = w.wp24; break;
+ case 25:link = w.wp25; break;
+ case 26:link = w.wp26; break;
+ case 27:link = w.wp27; break;
+ case 28:link = w.wp28; break;
+ case 29:link = w.wp29; break;
+ case 30:link = w.wp30; break;
+ case 31:link = w.wp31; break;
+ }
+
+ 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;
+
+ print("saved ");
+ print(ftos(c));
+ print(" waypoints links to maps/");
+ print(mapname);
+ print(".waypoints.cache\n");
+};
+
+// save waypoints to gamedir/data/maps/mapname.waypoints
+void waypoint_saveall()
+{
+ local string filename, s;
+ local float file, c;
+ local 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()
+{
+ local string filename, s;
+ local float file, cwp, cwb, fl;
+ local vector m1, m2;
+ cwp = 0;
+ cwb = 0;
+ filename = strcat("maps/", mapname);
+ filename = strcat(filename, ".waypoints");
+ file = fopen(filename, FILE_READ);
+ if (file >= 0)
+ {
+ while (1)
+ {
+ s = fgets(file);
+ if (!s)
+ break;
+ 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);
+ dprint("loaded ");
+ dprint(ftos(cwp));
+ dprint(" waypoints and ");
+ dprint(ftos(cwb));
+ dprint(" wayboxes from maps/");
+ dprint(mapname);
+ dprint(".waypoints\n");
+ }
+ else
+ {
+ dprint("waypoint load from ");
+ dprint(filename);
+ dprint(" failed\n");
+ }
+ return cwp + cwb;
+};
+
+void waypoint_spawnforitem_force(entity e, vector org)
+{
+ local entity w;
+
+ // Fix the waypoint altitude if necessary
+ traceline(org, org + '0 0 -65535', TRUE, e);
+ if(
+ org_z - trace_endpos_z > PL_MAX_z - PL_MIN_z + 10 // If middle of entiy is above player heigth
+ || org_z - trace_endpos_z < (PL_MAX_z - PL_MIN_z) * 0.5 // or below half player height
+ )
+ org_z = trace_endpos_z + PL_MAX_z - PL_MIN_z;
+
+ // 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(entity e, vector destination, float timetaken)
+{
+ local entity w;
+ local entity dw;
+ w = waypoint_spawn(e.absmin, e.absmax, WAYPOINTFLAG_GENERATED | WAYPOINTFLAG_TELEPORT | WAYPOINTFLAG_NORELINK);
+ dw = waypoint_spawn(destination, destination, 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;
+};
+
+entity waypoint_spawnpersonal(vector position)
+{
+ 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
+ traceline(position, position + '0 0 1' * (PL_MAX_z - PL_MIN_z), MOVE_NOMONSTERS, world);
+ tracebox(trace_endpos, PL_MIN, PL_MAX, trace_endpos + '0 0 -1024', MOVE_NOMONSTERS, world);
+ if(trace_fraction < 1)
+ position = trace_endpos;
+
+ 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()
+{
+ local 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");
+ }
+};
Property changes on: trunk/data/qcsrc/server/bot/waypoints.qc
___________________________________________________________________
Name: svn:mergeinfo
+
Name: svn:eol-style
+ native
Added: trunk/data/qcsrc/server/bot/waypoints.qh
===================================================================
--- trunk/data/qcsrc/server/bot/waypoints.qh (rev 0)
+++ trunk/data/qcsrc/server/bot/waypoints.qh 2009-09-20 21:43:29 UTC (rev 7851)
@@ -0,0 +1,57 @@
+/*
+ * Globals and Fields
+ */
+
+float WAYPOINTFLAG_GENERATED = 8388608;
+float WAYPOINTFLAG_ITEM = 4194304;
+float WAYPOINTFLAG_TELEPORT = 2097152;
+float WAYPOINTFLAG_NORELINK = 1048576;
+float WAYPOINTFLAG_PERSONAL = 524288;
+
+// 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, wpflags, wplinked;
+
+.vector wpnearestpoint;
+
+/*
+ * Functions
+ */
+
+void 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 botframe_showwaypointlinks();
+
+float waypoint_loadall();
+float waypoint_load_links();
+
+entity waypoint_spawn(vector m1, vector m2, float f);
+entity waypoint_spawnpersonal(vector position);
Deleted: trunk/data/qcsrc/server/bots.qc
===================================================================
--- trunk/data/qcsrc/server/bots.qc 2009-09-20 17:48:33 UTC (rev 7850)
+++ trunk/data/qcsrc/server/bots.qc 2009-09-20 21:43:29 UTC (rev 7851)
@@ -1,3119 +0,0 @@
-.float aistatus;
-float AI_STATUS_ROAMING = 1; // Bot is just crawling the map. No enemies at sight
-float AI_STATUS_ATTACKING = 2; // There are enemies at sight
-float AI_STATUS_RUNNING = 4; // Bot is bunny hopping
-float AI_STATUS_DANGER_AHEAD = 8; // There is lava/slime/trigger_hurt ahead
-float AI_STATUS_OUT_JUMPPAD = 16; // Trying to get out of a "vertical" jump pad
-float AI_STATUS_OUT_WATER = 32; // Trying to get out of water
-float AI_STATUS_WAYPOINT_PERSONAL_LINKING = 64; // Waiting for the personal waypoint to be linked
-float AI_STATUS_WAYPOINT_PERSONAL_GOING = 128; // Going to a personal waypoint
-float AI_STATUS_WAYPOINT_PERSONAL_REACHED = 256; // Personal waypoint reached
-float AI_STATUS_JETPACK_FLYING = 512;
-float AI_STATUS_JETPACK_LANDING = 1024;
-
-// utilities for path debugging
-#ifdef DEBUG_TRACEWALK
-
-float DEBUG_NODE_SUCCESS = 1;
-float DEBUG_NODE_WARNING = 2;
-float DEBUG_NODE_FAIL = 3;
-
-vector debuglastnode;
-
-void debugresetnodes()
-{
- debuglastnode = '0 0 0';
-}
-
-void debugnode(vector node)
-{
- if not(self.classname=="player")
- return;
-
- if(debuglastnode=='0 0 0')
- {
- debuglastnode = node;
- return;
- }
-
- te_lightning2(world, node, debuglastnode);
- debuglastnode = node;
-}
-
-void debugnodestatus(vector position, float status)
-{
- vector color;
-
- switch (status)
- {
- case DEBUG_NODE_SUCCESS:
- color = '0 15 0';
- break;
- case DEBUG_NODE_WARNING:
- color = '15 15 0';
- break;
- case DEBUG_NODE_FAIL:
- color = '15 0 0';
- break;
- default:
- color = '15 15 15';
- }
-
- te_customflash(position, 40, 2, color);
-}
-
-#endif
-
-// rough simulation of walking from one point to another to test if a path
-// can be traveled, used for waypoint linking and havocbot
-
-vector stepheightvec;
-float bot_navigation_movemode;
-float navigation_testtracewalk;
-float tracewalk(entity e, vector start, vector m1, vector m2, vector end, float movemode)
-{
- local vector org;
- local vector move;
- local vector dir;
- local float dist;
- local float totaldist;
- local float stepdist;
- local float yaw;
- local float ignorehazards;
- local float swimming;
-
- #ifdef DEBUG_TRACEWALK
- debugresetnodes();
- debugnode(start);
- #endif
-
- move = end - start;
- move_z = 0;
- org = start;
- dist = totaldist = vlen(move);
- dir = normalize(move);
- stepdist = 32;
- ignorehazards = 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
- #ifdef DEBUG_TRACEWALK
- debugnodestatus(start, DEBUG_NODE_FAIL);
- #endif
- //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
- #ifdef DEBUG_TRACEWALK
- debugnodestatus(org, DEBUG_NODE_SUCCESS);
- #endif
- //print("tracewalk: ", vtos(start), " can reach ", vtos(end), "\n");
- return TRUE;
- }
- #ifdef DEBUG_TRACEWALK
- debugnode(org);
- #endif
-
- 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
- #ifdef DEBUG_TRACEWALK
- debugnodestatus(org, DEBUG_NODE_FAIL);
- #endif
- //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);
-
- #ifdef DEBUG_TRACEWALK
- debugnode(trace_endpos);
- #endif
-
- 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)
- {
- #ifdef DEBUG_TRACEWALK
- debugnode(org);
- #endif
- if(pointcontents(org) == CONTENT_EMPTY)
- break;
- }
-
- if not (pointcontents(org + '0 0 1') == CONTENT_EMPTY)
- {
- #ifdef DEBUG_TRACEWALK
- debugnodestatus(org, DEBUG_NODE_FAIL);
- #endif
- 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);
-
- #ifdef DEBUG_TRACEWALK
- debugnode(trace_endpos);
- #endif
-
- // hit something
- if (trace_fraction < 1)
- {
- // check if we can walk over this obstacle
- tracebox(org + stepheightvec, m1, m2, move + stepheightvec, movemode, e);
- if (trace_fraction < 1 || trace_startsolid)
- {
- #ifdef DEBUG_TRACEWALK
- debugnodestatus(trace_endpos, DEBUG_NODE_WARNING);
- #endif
-
- // check for doors
- traceline( org, move, movemode, e);
- if ( trace_ent.classname == "door_rotating" || trace_ent.classname == "door")
- {
- local 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
- {
- #ifdef DEBUG_TRACEWALK
- debugnodestatus(trace_endpos, DEBUG_NODE_FAIL);
- #endif
- //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;
-
- // 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)
- {
- local float c;
- c = pointcontents(org + '0 0 1');
- if not(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
- #ifdef DEBUG_TRACEWALK
- debugnodestatus(org, DEBUG_NODE_FAIL);
- #endif
-
- return FALSE;
-};
-
-
-// grenade tracing to decide the best pitch to fire at
-
-entity tracetossent;
-entity tracetossfaketarget;
-
-// 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)
-vector findtrajectory_velocity;
-float findtrajectorywithleading(vector org, vector m1, vector m2, entity targ, float shotspeed, float shotspeedupward, float maxtime, float shotdelay, entity ignore)
-{
- local float c, savesolid, shottime;
- local vector dir, end, v;
- 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;
- shottime = ((vlen(targ.origin - org) / shotspeed) + shotdelay);
- v = targ.velocity * shottime + targ.origin;
- tracebox(targ.origin, 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;
-};
-
-
-
-// Lag simulation
-#define LAG_QUEUE_LENGTH 4
-
-.void(float t, float f1, float f2, entity e1, vector v1, vector v2, vector v3, vector v4) lag_func;
-
-.float lag_time[LAG_QUEUE_LENGTH];
-.float lag_float1[LAG_QUEUE_LENGTH];
-.float lag_float2[LAG_QUEUE_LENGTH];
-.vector lag_vec1[LAG_QUEUE_LENGTH];
-.vector lag_vec2[LAG_QUEUE_LENGTH];
-.vector lag_vec3[LAG_QUEUE_LENGTH];
-.vector lag_vec4[LAG_QUEUE_LENGTH];
-.entity lag_entity1[LAG_QUEUE_LENGTH];
-
- void lag_update()
- {
- float i;
- for(i=0;i<LAG_QUEUE_LENGTH;++i)
- {
- if (self.lag_time[i])
- if (time > self.lag_time[i])
- {
- self.lag_func(
- self.lag_time[i], self.lag_float1[i], self.lag_float2[i],
- self.lag_entity1[i], self.lag_vec1[i], self.lag_vec2[i], self.lag_vec3[i],
- self.lag_vec4[i]
- );
- // Clear this position on the queue
- self.(lag_time[i]) = 0;
- }
- }
- };
-
- float lag_additem(float t, float f1, float f2, entity e1, vector v1, vector v2, vector v3, vector v4)
- {
- float i;
- for(i=0;i<LAG_QUEUE_LENGTH;++i)
- {
- // Find a free position on the queue
- if (self.lag_time[i] == 0)
- {
- self.(lag_time[i]) = t;
- self.(lag_vec1[i]) = v1;
- self.(lag_vec2[i]) = v2;
- self.(lag_vec3[i]) = v3;
- self.(lag_vec4[i]) = v4;
- self.(lag_float1[i]) = f1;
- self.(lag_float2[i]) = f2;
- self.(lag_entity1[i]) = e1;
- return TRUE;
- }
- }
- return FALSE;
- };
-
-// Random skill system
-.float bot_thinkskill;
-.float bot_mouseskill;
-.float bot_predictionskill;
-.float bot_offsetskill;
-
-
-// spawnfunc_waypoint navigation system
-
-// itemscore = (howmuchmoreIwant / howmuchIcanwant) / itemdistance
-// waypointscore = 0.7 / waypointdistance
-
-.entity wp00, wp01, wp02, wp03, wp04, wp05, wp06, wp07;
-.entity 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, wp24mincost, wp25mincost, wp26mincost, wp27mincost, wp28mincost, wp29mincost, wp30mincost, wp31mincost;
-.float wpfire, wpcost, wpconsidered;
-.float wpisbox;
-.float wpflags;
-.vector wpnearestpoint;
-.float wplinked;
-
-// 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;
-
-// used during navigation_goalrating_begin/end sessions
-#define MAX_BESTGOALS 3
-float bestgoalswindex;
-float bestgoalsrindex;
-float navigation_bestrating;
-entity navigation_bestgoals[MAX_BESTGOALS];
-.float navigation_hasgoals;
-
-/////////////////////////////////////////////////////////////////////////////
-// spawnfunc_waypoint management
-/////////////////////////////////////////////////////////////////////////////
-
-// waypoints with this flag are not saved, they are automatically generated
-// waypoints like jump pads, teleporters, and items
-float WAYPOINTFLAG_GENERATED = 8388608;
-float WAYPOINTFLAG_ITEM = 4194304;
-float WAYPOINTFLAG_TELEPORT = 2097152;
-float WAYPOINTFLAG_NORELINK = 1048576;
-float WAYPOINTFLAG_PERSONAL = 524288;
-
-// add a new link to the spawnfunc_waypoint, replacing the furthest link it already has
-void waypoint_addlink(entity from, entity to)
-{
- local 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
- local 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;
-};
-
-// 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;
-
-// relink this spawnfunc_waypoint
-// (precompile a list of all reachable waypoints from this spawnfunc_waypoint)
-// (SLOW!)
-void waypoint_think()
-{
- local entity e;
- local vector sv, sm1, sm2, ev, em1, em2, dv;
-
- stepheightvec = cvar("sv_stepheight") * '0 0 1';
- bot_navigation_movemode = ((cvar("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
- local float f;
- f = 10000000;
- wp.wp00 = wp.wp01 = wp.wp02 = wp.wp03 = wp.wp04 = wp.wp05 = wp.wp06 = wp.wp07 = world;wp.wp00mincost = wp.wp01mincost = wp.wp02mincost = wp.wp03mincost = wp.wp04mincost = wp.wp05mincost = wp.wp06mincost = wp.wp07mincost = f;
- wp.wp08 = wp.wp09 = wp.wp10 = wp.wp11 = wp.wp12 = wp.wp13 = wp.wp14 = wp.wp15 = world;wp.wp08mincost = wp.wp09mincost = wp.wp10mincost = wp.wp11mincost = wp.wp12mincost = wp.wp13mincost = wp.wp14mincost = wp.wp15mincost = f;
- wp.wp16 = wp.wp17 = wp.wp18 = wp.wp19 = wp.wp20 = wp.wp21 = wp.wp22 = wp.wp23 = world;wp.wp16mincost = wp.wp17mincost = wp.wp18mincost = wp.wp19mincost = wp.wp20mincost = wp.wp21mincost = wp.wp22mincost = wp.wp23mincost = f;
- wp.wp24 = wp.wp25 = wp.wp26 = wp.wp27 = wp.wp28 = wp.wp29 = wp.wp30 = wp.wp31 = world;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 (cvar("g_waypointeditor"))
- {
- local vector m1, m2;
- m1 = wp.mins;
- m2 = wp.maxs;
- setmodel(wp, "models/runematch/rune.mdl"); 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;
-}
-
-// 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)
-{
- local entity w;
- local vector org;
- w = find(world, classname, "waypoint");
-
- if not(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;
- 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(!(f & WAYPOINTFLAG_GENERATED))
- if(!w.wpisbox)
- {
- traceline(w.origin + '0 0 1', w.origin + PL_MIN_z * '0 0 1' - '0 0 1', MOVE_NOMONSTERS, w);
- if (trace_fraction < 1)
- setorigin(w, trace_endpos - PL_MIN_z * '0 0 1');
-
- // check if the start position is stuck
- tracebox(w.origin, PL_MIN + '-1 -1 -1', PL_MAX + '1 1 1', w.origin, MOVE_NOMONSTERS, w);
- if (trace_startsolid)
- {
- org = w.origin + '0 0 26';
- tracebox(org, PL_MIN, PL_MAX, w.origin, MOVE_WORLDONLY, w);
- if(trace_startsolid)
- {
- org = w.origin + '2 2 2';
- tracebox(org, PL_MIN, PL_MAX, w.origin, MOVE_WORLDONLY, w);
- if(trace_startsolid)
- {
- org = w.origin + '-2 -2 2';
- tracebox(org, PL_MIN, PL_MAX, w.origin, MOVE_WORLDONLY, w);
- if(trace_startsolid)
- {
- org = w.origin + '-2 2 2';
- tracebox(org, PL_MIN, PL_MAX, w.origin, MOVE_WORLDONLY, w);
- if(trace_startsolid)
- {
- org = w.origin + '2 -2 2';
- tracebox(org, PL_MIN, PL_MAX, w.origin, MOVE_WORLDONLY, w);
- if(trace_startsolid)
- {
- // this WP is in solid, refuse it
- dprint("Killed a waypoint that was stuck in solid at ", vtos(org), "\n");
- remove(w);
- return world;
- }
- }
- }
- }
- }
- setorigin(w, org * 0.05 + trace_endpos * 0.95); // don't trust the trace fully
- }
-
- tracebox(w.origin, PL_MIN, PL_MAX, w.origin - '0 0 128', MOVE_WORLDONLY, w);
- if(trace_startsolid)
- {
- dprint("Killed a waypoint that was stuck in solid ", vtos(w.origin), "\n");
- remove(w);
- return world;
- }
- if (!trace_inwater)
- {
- if(trace_fraction == 1)
- {
- dprint("Killed a waypoint that was stuck in air at ", vtos(w.origin), "\n");
- remove(w);
- return world;
- }
- trace_endpos_z += 0.1; // don't trust the trace fully
-// dprint("Moved waypoint at ", vtos(w.origin), " by ", ftos(vlen(w.origin - trace_endpos)));
-// dprint(" direction: ", vtos((trace_endpos - w.origin)), "\n");
- setorigin(w, trace_endpos);
- }
- }
-
- waypoint_clearlinks(w);
- //waypoint_schedulerelink(w);
-
- if (cvar("g_waypointeditor"))
- {
- m1 = w.mins;
- m2 = w.maxs;
- setmodel(w, "models/runematch/rune.mdl"); 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;
-};
-
-// spawnfunc_waypoint map entity
-void 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()
-{
- local 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()
-{
- local 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 botframe_cachedwaypointlinks;
-float waypoint_load_links()
-{
- local string filename, s;
- local float file, tokens, c, found;
- local entity wp_from, wp_to;
- local vector wp_to_pos, wp_from_pos;
- filename = strcat("maps/", mapname);
- filename = strcat(filename, ".waypoints.cache");
- file = fopen(filename, FILE_READ);
-
- if (file < 0)
- {
- dprint("waypoint links load from ");
- dprint(filename);
- dprint(" failed\n");
- return FALSE;
- }
-
- while (1)
- {
- s = fgets(file);
- if (!s)
- break;
-
- 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.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)
- {
- // can't find that waypoint
- fclose(file);
- return FALSE;
- }
- }
-
- // 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)
- {
- // can't find that waypoint
- fclose(file);
- return FALSE;
- }
-
- ++c;
- waypoint_addlink(wp_from, wp_to);
- }
-
- fclose(file);
-
- dprint("loaded ");
- dprint(ftos(c));
- dprint(" waypoint links from maps/");
- dprint(mapname);
- dprint(".waypoints.cache\n");
-
- botframe_cachedwaypointlinks = TRUE;
- return TRUE;
-};
-
-float botframe_loadedforcedlinks;
-void waypoint_load_links_hardwired()
-{
- local string filename, s;
- local float file, tokens, c, found;
- local entity wp_from, wp_to;
- local 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)
- {
- dprint("waypoint links load from ");
- dprint(filename);
- dprint(" failed\n");
- return;
- }
-
- for (;;)
- {
- s = fgets(file);
- if (!s)
- break;
-
- 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.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)
- {
- print(strcat("NOTICE: Can not find waypoint at ", vtos(wp_from_pos), ". Path skipped\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)
- {
- print(strcat("NOTICE: Can not find waypoint at ", vtos(wp_to_pos), ". Path skipped\n"));
- continue;
- }
-
- ++c;
- waypoint_addlink(wp_from, wp_to);
- }
-
- fclose(file);
-
- dprint("loaded ");
- dprint(ftos(c));
- dprint(" waypoint links from maps/");
- dprint(mapname);
- dprint(".waypoints.hardwired\n");
-};
-
-
-// Save all waypoint links to a file
-void waypoint_save_links()
-{
- local string filename, s;
- local float file, c, i;
- local entity w, link;
- filename = strcat("maps/", mapname);
- filename = strcat(filename, ".waypoints.cache");
- file = fopen(filename, FILE_WRITE);
- if (file < 0)
- {
- print("waypoint links save to ");
- print(filename);
- print(" failed\n");
- }
- c = 0;
- w = findchain(classname, "waypoint");
- while (w)
- {
- for(i=0;i<32;++i)
- {
- // :S
- switch(i)
- {
- // for i in $(seq -w 0 31); do echo "case $i:link = w.wp$i; break;"; done;
- case 00:link = w.wp00; break;
- case 01:link = w.wp01; break;
- case 02:link = w.wp02; break;
- case 03:link = w.wp03; break;
- case 04:link = w.wp04; break;
- case 05:link = w.wp05; break;
- case 06:link = w.wp06; break;
- case 07:link = w.wp07; break;
- case 08:link = w.wp08; break;
- case 09:link = w.wp09; break;
- case 10:link = w.wp10; break;
- case 11:link = w.wp11; break;
- case 12:link = w.wp12; break;
- case 13:link = w.wp13; break;
- case 14:link = w.wp14; break;
- case 15:link = w.wp15; break;
- case 16:link = w.wp16; break;
- case 17:link = w.wp17; break;
- case 18:link = w.wp18; break;
- case 19:link = w.wp19; break;
- case 20:link = w.wp20; break;
- case 21:link = w.wp21; break;
- case 22:link = w.wp22; break;
- case 23:link = w.wp23; break;
- case 24:link = w.wp24; break;
- case 25:link = w.wp25; break;
- case 26:link = w.wp26; break;
- case 27:link = w.wp27; break;
- case 28:link = w.wp28; break;
- case 29:link = w.wp29; break;
- case 30:link = w.wp30; break;
- case 31:link = w.wp31; break;
- }
-
- 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;
-
- print("saved ");
- print(ftos(c));
- print(" waypoints links to maps/");
- print(mapname);
- print(".waypoints.cache\n");
-};
-
-// save waypoints to gamedir/data/maps/mapname.waypoints
-void waypoint_saveall()
-{
- local string filename, s;
- local float file, c;
- local 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()
-{
- local string filename, s;
- local float file, cwp, cwb, fl;
- local vector m1, m2;
- cwp = 0;
- cwb = 0;
- filename = strcat("maps/", mapname);
- filename = strcat(filename, ".waypoints");
- file = fopen(filename, FILE_READ);
- if (file >= 0)
- {
- while (1)
- {
- s = fgets(file);
- if (!s)
- break;
- 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);
- dprint("loaded ");
- dprint(ftos(cwp));
- dprint(" waypoints and ");
- dprint(ftos(cwb));
- dprint(" wayboxes from maps/");
- dprint(mapname);
- dprint(".waypoints\n");
- }
- else
- {
- dprint("waypoint load from ");
- dprint(filename);
- dprint(" failed\n");
- }
- return cwp + cwb;
-};
-
-void waypoint_spawnforitem_force(entity e, vector org)
-{
- local entity w;
-
- // Fix the waypoint altitude if necessary
- traceline(org, org + '0 0 -65535', TRUE, e);
- if(
- org_z - trace_endpos_z > PL_MAX_z - PL_MIN_z + 10 // If middle of entiy is above player heigth
- || org_z - trace_endpos_z < (PL_MAX_z - PL_MIN_z) * 0.5 // or below half player height
- )
- org_z = trace_endpos_z + PL_MAX_z - PL_MIN_z;
-
- // 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(entity e, vector destination, float timetaken)
-{
- local entity w;
- local entity dw;
- w = waypoint_spawn(e.absmin, e.absmax, WAYPOINTFLAG_GENERATED | WAYPOINTFLAG_TELEPORT | WAYPOINTFLAG_NORELINK);
- dw = waypoint_spawn(destination, destination, 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;
-};
-
-entity waypoint_spawnpersonal(vector position)
-{
- 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
- traceline(position, position + '0 0 1' * (PL_MAX_z - PL_MIN_z), MOVE_NOMONSTERS, world);
- tracebox(trace_endpos, PL_MIN, PL_MAX, trace_endpos + '0 0 -1024', MOVE_NOMONSTERS, world);
- if(trace_fraction < 1)
- position = trace_endpos;
-
- w = waypoint_spawn(position, position, WAYPOINTFLAG_GENERATED | WAYPOINTFLAG_PERSONAL);
- w.nearestwaypoint = world;
- w.nearestwaypointtimeout = 0;
- w.owner = self;
-
- waypoint_schedulerelink(w);
-
- return w;
-};
-
-/////////////////////////////////////////////////////////////////////////////
-// goal stack
-/////////////////////////////////////////////////////////////////////////////
-
-// completely empty the goal stack, used when deciding where to go
-void navigation_clearroute()
-{
- //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)
-void navigation_pushroute(entity e)
-{
- //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()
-{
- //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;
-};
-
-// find the spawnfunc_waypoint near a dynamic goal such as a dropped weapon
-entity navigation_findnearestwaypoint(entity ent, float walkfromwp)
-{
- local entity waylist, w, best;
- local float dist, bestdist;
- local vector v, org, pm1, pm2;
- pm1 = ent.origin + PL_MIN;
- pm2 = ent.origin + PL_MAX;
- 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)
- if (boxesoverlap(pm1, pm2, w.absmin, w.absmax))
- return w;
- w = w.chain;
- }
-
- org = ent.origin + (ent.mins_z - PL_MIN_z) * '0 0 1';
- if(ent.tag_entity)
- org = org + ent.tag_entity.origin;
- if (navigation_testtracewalk)
- te_plasmaburn(org);
-
- best = world;
- bestdist = 1050;
-
- // box check failed, try walk
- w = waylist;
- while (w)
- {
- // if object can walk from spawnfunc_waypoint
- if(w != ent)
- {
- if (w.wpisbox)
- {
- local 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;
- dist = vlen(v - org);
- if (bestdist > dist)
- {
- traceline(v, org, TRUE, ent);
- if (trace_fraction == 1)
- {
- if (walkfromwp)
- {
- //print("^1can I reach ", vtos(org), " from ", vtos(v), "?\n");
- if (tracewalk(ent, v, PL_MIN, PL_MAX, org, bot_navigation_movemode))
- {
- bestdist = dist;
- best = w;
- }
- }
- else
- {
- if (tracewalk(ent, org, PL_MIN, PL_MAX, v, bot_navigation_movemode))
- {
- bestdist = dist;
- best = w;
- }
- }
- }
- }
- }
- w = w.chain;
- }
- return best;
-}
-
-// finds the waypoints near the bot initiating a navigation query
-float navigation_markroutes_nearestwaypoints(entity waylist, float maxdist)
-{
- local entity head;
- local vector v, m1, m2, diff;
- local 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)
-{
- local vector m1;
- local vector m2;
- local 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()
-{
- local entity w, wp, waylist;
- local float searching, cost, cost2;
- local 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;
- }
-
- // 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
- local float i, increment, maxdistance;
- if(self.flags & FL_ONGROUND)
- {
- increment = 750;
- maxdistance = 50000;
- }
- else
- {
- increment = 500;
- maxdistance = 1500;
- }
-
- for(i=increment;!navigation_markroutes_nearestwaypoints(waylist, i)&&i<maxdistance;i+=increment);
-
- searching = TRUE;
- while (searching)
- {
- searching = FALSE;
- w = waylist;
- while (w)
- {
- if (w.wpfire)
- {
- searching = TRUE;
- w.wpfire = 0;
- cost = w.wpcost;
- p = w.wpnearestpoint;
- wp = w.wp00;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp00mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
- wp = w.wp01;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp01mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
- wp = w.wp02;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp02mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
- wp = w.wp03;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp03mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
- wp = w.wp04;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp04mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
- wp = w.wp05;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp05mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
- wp = w.wp06;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp06mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
- wp = w.wp07;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp07mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
- wp = w.wp08;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp08mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
- wp = w.wp09;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp09mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
- wp = w.wp10;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp10mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
- wp = w.wp11;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp11mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
- wp = w.wp12;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp12mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
- wp = w.wp13;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp13mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
- wp = w.wp14;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp14mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
- wp = w.wp15;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp15mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
- wp = w.wp16;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp16mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
- wp = w.wp17;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp17mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
- wp = w.wp18;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp18mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
- wp = w.wp19;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp19mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
- wp = w.wp20;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp20mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
- wp = w.wp21;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp21mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
- wp = w.wp22;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp22mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
- wp = w.wp23;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp23mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
- wp = w.wp24;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp24mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
- wp = w.wp25;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp25mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
- wp = w.wp26;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp26mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
- wp = w.wp27;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp27mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
- wp = w.wp28;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp28mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
- wp = w.wp29;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp29mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
- wp = w.wp30;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp30mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
- wp = w.wp31;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + w.wp31mincost) navigation_markroutes_checkwaypoint(w, wp, cost2, p);
- }}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
- }
- w = w.chain;
- }
- }
-};
-
-void navigation_bestgoals_reset()
-{
- local float i;
-
- bestgoalswindex = 0;
- bestgoalsrindex = 0;
-
- for(i=0;i>MAX_BESTGOALS-1;++i)
- {
- navigation_bestgoals[i] = world;
- }
-}
-
-void navigation_add_bestgoal(entity goal)
-{
- if(bestgoalsrindex>0)
- {
- ++bestgoalsrindex;
-
- if(bestgoalsrindex==MAX_BESTGOALS)
- bestgoalsrindex = 0;
- }
-
- if(bestgoalswindex==MAX_BESTGOALS)
- {
- bestgoalswindex=0;
- if(bestgoalsrindex==0)
- bestgoalsrindex=1;
- }
-
- navigation_bestgoals[bestgoalswindex] = goal;
-
- ++bestgoalswindex;
-}
-
-entity navigation_get_bestgoal()
-{
- local entity ent;
-
- ent = navigation_bestgoals[bestgoalsrindex];
- navigation_bestgoals[bestgoalsrindex] = world;
-
- ++bestgoalsrindex;
-
- if(bestgoalsrindex==MAX_BESTGOALS)
- bestgoalsrindex = 0;
-
- return ent;
-}
-
-// fields required for jetpack navigation
-.entity navigation_jetpack_goal;
-.vector navigation_jetpack_point;
-
-// updates the best goal according to a weighted calculation of travel cost and item value of a new proposed item
-.void() havocbot_role;
-void() havocbot_role_ctf_offense;
-void navigation_routerating(entity e, float f, float rangebias)
-{
- entity nwp;
- if (!e)
- return;
-
- // Evaluate path using jetpack
- if(g_jetpack)
- if(self.items & IT_JETPACK)
- if(cvar("bot_ai_navigation_jetpack"))
- if(vlen(self.origin - e.origin) > cvar("bot_ai_navigation_jetpack_mindistance"))
- {
- vector pointa, pointb;
-
- // dprint("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(e.origin, e.origin + '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)
- {
- // dprint("jetpack ai: can bridge these two points\n");
-
- // Lower the altitude of these points as much as possible
- local float zdistance, xydistance, cost, t, fuel;
- local 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 / cvar("g_jetpack_maxspeed_up");
- t += xydistance / cvar("g_jetpack_maxspeed_side");
- fuel = t * cvar("g_jetpack_fuel") * 0.8;
-
- // dprint("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 / (cvar("g_jetpack_maxspeed_side")/cvar("sv_maxspeed"));
- cost += zdistance / (cvar("g_jetpack_maxspeed_up")/cvar("sv_maxspeed"));
- cost *= 1.5;
-
- // Compare against other goals
- f = f * rangebias / (rangebias + cost);
-
- if (navigation_bestrating < f)
- {
- // dprint("jetpack path: added goal", e.classname, " (with rating ", ftos(f), ")\n");
- navigation_bestrating = f;
- navigation_add_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
- {
- if (time > e.nearestwaypointtimeout)
- {
- e.nearestwaypoint = navigation_findnearestwaypoint(e, TRUE);
-
- // 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;
- }
-
- //dprint("-- checking ", e.classname, " (with cost ", ftos(nwp.wpcost), ")\n");
- if (nwp)
- if (nwp.wpcost < 10000000)
- {
- //te_wizspike(nwp.wpnearestpoint);
- // dprint(e.classname, " ", ftos(f), "/(1+", ftos((nwp.wpcost + vlen(e.origin - nwp.wpnearestpoint))), "/", ftos(rangebias), ") = ");
- f = f * rangebias / (rangebias + (nwp.wpcost + vlen(e.origin - nwp.wpnearestpoint)));
- //if (self.havocbot_role == havocbot_role_ctf_offense)
- //dprint("considering ", e.classname, " (with rating ", ftos(f), ")\n");
- //dprint(ftos(f));
- if (navigation_bestrating < f)
- {
- // dprint("ground path: added goal ", e.classname, " (with rating ", ftos(f), ")\n");
- navigation_bestrating = f;
- navigation_add_bestgoal(e);
- }
- }
- //dprint("\n");
-};
-
-// adds an item to the the goal stack with the path to a given item
-float navigation_routetogoal(entity e, vector startposition)
-{
- 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
- 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.origin, 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;
-};
-
-void navigation_routetogoals()
-{
- entity g1, g2;
-
- navigation_clearroute();
-
- g1 = navigation_get_bestgoal();
- for(;;)
- {
- if(g2==world)
- g2 = navigation_get_bestgoal();
-
- if(g2==world)
- {
- navigation_routetogoal(g1, self.origin);
- return;
- }
-
- if(navigation_routetogoal(g1, g2.origin))
- {
- g1 = g2;
- g2 = world;
- continue;
- }
-
- navigation_clearroute();
- g1 = g2;
- g2 = world;
- }
-}
-
-// removes any currently touching waypoints from the goal stack
-// (this is how bots detect if they have reached a goal)
-.float lastteleporttime;
-
-void navigation_poptouchedgoals()
-{
- local 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<0.15)
- {
- navigation_poproute();
- return;
- }
- }
-
- // Loose goal touching check when running
- if(self.aistatus & AI_STATUS_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()
-{
- self.navigation_jetpack_goal = world;
- navigation_bestrating = -1;
- self.navigation_hasgoals = FALSE;
- navigation_bestgoals_reset();
- navigation_markroutes();
-};
-
-// ends a goal selection session (updates goal stack to the best goal)
-void navigation_goalrating_end()
-{
- navigation_routetogoals();
-// dprint("best goal ", self.goalcurrent.classname , "\n");
-
- // Hack: if it can't walk to any goal just move blindly to the first visible waypoint
- if not (self.navigation_hasgoals)
- {
- dprint(self.netname, " can't walk to any goal, going to a near waypoint\n");
-
- entity head;
-
- RandomSelection_Init();
- head = findradius(self.origin,1000);
- while(head)
- {
- if(head.classname=="waypoint")
- if(!(head.wpflags & WAYPOINTFLAG_GENERATED))
- if(vlen(self.origin-head.origin)>100)
- if(checkpvs(self.view_ofs,head))
- RandomSelection_Add(head, 0, string_null, 1 + (vlen(self.origin-head.origin)<500), 0);
- head = head.chain;
- }
- if(RandomSelection_chosen_ent)
- navigation_routetogoal(RandomSelection_chosen_ent, self.origin);
-
- self.navigation_hasgoals = FALSE; // Reset this value
- }
-};
-
-
-//////////////////////////////////////////////////////////////////////////////
-// general bot functions
-//////////////////////////////////////////////////////////////////////////////
-
-.float isbot; // true if this client is actually a bot
-
-float skill;
-entity bot_list;
-.entity nextbot;
-.string netname_freeme;
-.string playermodel_freeme;
-.string playerskin_freeme;
-
-float sv_maxspeed;
-.float createdtime;
-
-.float bot_preferredcolors;
-
-.float bot_attack;
-.float bot_dodge;
-.float bot_dodgerating;
-
-//.float bot_painintensity;
-.float bot_firetimer;
-//.float bot_oldhealth;
-.void() bot_ai;
-
-.entity bot_aimtarg;
-.float bot_aimlatency;
-.vector bot_aimselforigin;
-.vector bot_aimselfvelocity;
-.vector bot_aimtargorigin;
-.vector bot_aimtargvelocity;
-
-.float bot_pickup;
-.float(entity player, entity item) bot_pickupevalfunc;
-.float bot_pickupbasevalue;
-.float bot_canfire;
-.float bot_strategytime;
-
-// used for aiming currently
-// FIXME: make the weapon code use these and then replace the calculations here with a call to the weapon code
-vector shotorg;
-vector shotdir;
-
-.float bot_forced_team;
-.float bot_config_loaded;
-
-void bot_setnameandstuff()
-{
- 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(cvar("g_campaign"))
- {
- prefix = "";
- suffix = "";
- }
- else
- {
- prefix = cvar_string("bot_prefix");
- suffix = cvar_string("bot_suffix");
- }
-
- file = fopen(cvar_string("bot_config_file"), FILE_READ);
-
- if(file < 0)
- print(strcat("Error: Can not open the bot configuration file '",cvar_string("bot_config_file"),"'\n"));
- else
- {
- RandomSelection_Init();
- for(;;)
- {
- readfile = fgets(file);
- if(!readfile)
- break;
- if(substring(readfile, 0, 2) == "//")
- continue;
- if(substring(readfile, 0, 1) == "#")
- continue;
- tokens = tokenizebyseparator(readfile, "\t");
- s = argv(0);
- prio = 1;
- FOR_EACH_CLIENT(p)
- {
- if(strcat(prefix, s, suffix) == p.netname)
- {
- 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 = "marine";
-
- 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));
- 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 (cvar("bot_usemodelnames"))
- name = bot_model;
- else
- name = bot_name;
-
- // pick the model and skin
- if(substring(bot_model, -4, 1) != ".")
- bot_model = strcat(bot_model, ".zym");
- self.playermodel = self.playermodel_freeme = strzone(strcat("models/player/", bot_model));
- self.playerskin = self.playerskin_freeme = strzone(bot_skin);
-
- self.netname = self.netname_freeme = strzone(strcat(prefix, name, suffix));
-};
-
-float bot_custom_weapon;
-float bot_distance_far;
-float bot_distance_close;
-
-float bot_weapons_far[WEP_LAST];
-float bot_weapons_mid[WEP_LAST];
-float bot_weapons_close[WEP_LAST];
-
-void bot_custom_weapon_priority_setup()
-{
- local float tokens, i, c, w;
-
- bot_custom_weapon = FALSE;
-
- if( cvar_string("bot_ai_custom_weapon_priority_far") == "" ||
- cvar_string("bot_ai_custom_weapon_priority_mid") == "" ||
- cvar_string("bot_ai_custom_weapon_priority_close") == "" ||
- cvar_string("bot_ai_custom_weapon_priority_distances") == ""
- )
- return;
-
- // Parse distances
- tokens = tokenizebyseparator(cvar_string("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(cvar_string("bot_ai_custom_weapon_priority_far")," ");
-
- c = 0;
- for(i=0; i < tokens && c < WEP_COUNT; ++i){
- w = stof(argv(i));
- if ( w >= WEP_FIRST && w <= WEP_LAST) {
- bot_weapons_far[c] = w;
- ++c;
- }
- }
- if(c < WEP_COUNT)
- bot_weapons_far[c] = -1;
-
- // Parse mid distance weapon priorities
- tokens = tokenizebyseparator(cvar_string("bot_ai_custom_weapon_priority_mid")," ");
-
- c = 0;
- for(i=0; i < tokens && c < WEP_COUNT; ++i){
- w = stof(argv(i));
- if ( w >= WEP_FIRST && w <= WEP_LAST) {
- bot_weapons_mid[c] = w;
- ++c;
- }
- }
- if(c < WEP_COUNT)
- bot_weapons_mid[c] = -1;
-
- // Parse close distance weapon priorities
- tokens = tokenizebyseparator(cvar_string("bot_ai_custom_weapon_priority_close")," ");
-
- c = 0;
- for(i=0; i < tokens && i < WEP_COUNT; ++i){
- w = stof(argv(i));
- if ( w >= WEP_FIRST && w <= WEP_LAST) {
- bot_weapons_close[c] = w;
- ++c;
- }
- }
- if(c < WEP_COUNT)
- bot_weapons_close[c] = -1;
-
- bot_custom_weapon = TRUE;
-};
-
-
-void bot_endgame()
-{
- local 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
-};
-
-float bot_ignore_bots; // let bots not attack other bots (only works in non-teamplay)
-float bot_shouldattack(entity e)
-{
- if (e.team == self.team)
- {
- if (e == self)
- return FALSE;
- if (teams_matter)
- if (e.team != 0)
- return FALSE;
- }
-
- if(teams_matter)
- {
- if(e.team==0)
- return FALSE;
- }
- else if(bot_ignore_bots)
- if(clienttype(e) == CLIENTTYPE_BOT)
- return FALSE;
-
- if (!e.takedamage)
- return FALSE;
- if (e.deadflag)
- return FALSE;
- if (e.BUTTON_CHAT)
- return FALSE;
- if(g_minstagib)
- if(e.items & IT_STRENGTH)
- return FALSE;
- if(e.flags & FL_NOTARGET)
- return FALSE;
- return TRUE;
-};
-
-void bot_lagfunc(float t, float f1, float f2, entity e1, vector v1, vector v2, vector v3, vector v4)
-{
- 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_nextthink;
-.float bot_badaimtime;
-.float bot_aimthinktime;
-.float bot_prevaimtime;
-.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;
-float bot_aimdir(vector v, float maxfiredeviation)
-{
- local float dist, delta_t, blend;
- local 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) * cvar("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, cvar("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, cvar("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, cvar("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, cvar("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, cvar("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,10)*0.1;
- desiredang = desiredang + blend *
- (
- self.bot_1st_order_aimfilter * cvar("bot_ai_aimskill_order_mix_1st")
- + self.bot_2nd_order_aimfilter * cvar("bot_ai_aimskill_order_mix_2nd")
- + self.bot_3th_order_aimfilter * cvar("bot_ai_aimskill_order_mix_3th")
- + self.bot_4th_order_aimfilter * cvar("bot_ai_aimskill_order_mix_4th")
- + self.bot_5th_order_aimfilter * cvar("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,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,cvar("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
- local float r, fixedrate, blendrate;
- fixedrate = cvar("bot_ai_aimskill_fixedrate") / bound(1,dist,1000);
- blendrate = cvar("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,cvar("bot_ai_aimskill_mouse"),1) + desiredang * bound(0,(1-cvar("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, 10) || random()*random()>bound(0,skill*0.05,1))
- self.bot_firetimer = time + bound(0.1, 0.5-skill*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)
-{
- local float f, r;
- local vector v;
- /*
- eprint(self);
- dprint("bot_aim(", ftos(shotspeed));
- dprint(", ", ftos(shotspeedupward));
- dprint(", ", ftos(maxshottime));
- dprint(", ", ftos(applygravity));
- dprint(");\n");
- */
- shotspeed *= g_weaponspeedfactor;
- shotspeedupward *= g_weaponspeedfactor;
- if (!shotspeed)
- {
- dprint("bot_aim: WARNING: weapon ", W_Name(self.weapon), " shotspeed is zero!\n");
- shotspeed = 1000000;
- }
- if (!maxshottime)
- {
- dprint("bot_aim: WARNING: weapon ", W_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);
- local float distanceratio;
- distanceratio =sqrt(bound(0,skill,10000))*0.3*(vlen(v-shotorg)-100)/cvar("bot_ai_aimskill_firetolerance_distdegrees");
- distanceratio = bound(0,distanceratio,1);
- r = (cvar("bot_ai_aimskill_firetolerance_maxdegrees")-cvar("bot_ai_aimskill_firetolerance_mindegrees"))
- * (1-distanceratio) + cvar("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))
- 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))
- return FALSE;
- }
- if (r > maxshottime * shotspeed)
- return FALSE;
- return f;
-};
-
-// TODO: move this painintensity code to the player damage code
-void bot_think()
-{
- if (self.bot_nextthink > time)
- return;
-
- self.flags &~= FL_GODMODE;
- if(cvar("bot_god"))
- self.flags |= FL_GODMODE;
-
- self.bot_nextthink = self.bot_nextthink + cvar("bot_ai_thinkinterval");
- //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(time < game_starttime || ((cvar("g_campaign") && !campaign_bots_may_start)))
- {
- self.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 * 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 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;
- }
- }
-
- // now call the current bot AI (havocbot for example)
- self.bot_ai();
-};
-
-entity bot_strategytoken;
-float bot_strategytoken_taken;
-entity player_list;
-.entity nextplayer;
-void bot_relinkplayerlist()
-{
- local entity e;
- local 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 (clienttype(e) == CLIENTTYPE_BOT)
- {
- if (prevbot)
- prevbot.nextbot = e;
- else
- {
- bot_list = e;
- bot_list.nextbot = world;
- }
- prevbot = e;
- currentbots = currentbots + 1;
- }
- e = e.chain;
- }
- dprint(strcat("relink: ", ftos(currentbots), " bots seen.\n"));
- bot_strategytoken = bot_list;
- bot_strategytoken_taken = TRUE;
-};
-
-void() havocbot_setupbot;
-float JoinBestTeam(entity pl, float only_return_best, float forcebestteam);
-
-void bot_clientdisconnect()
-{
- if (clienttype(self) != CLIENTTYPE_BOT)
- return;
- 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.netname_freeme = string_null;
- self.playermodel_freeme = string_null;
- self.playerskin_freeme = string_null;
-}
-
-void bot_clientconnect()
-{
- if (clienttype(self) != CLIENTTYPE_BOT)
- return;
- self.bot_preferredcolors = self.clientcolors;
- self.bot_nextthink = time - random();
- self.lag_func = bot_lagfunc;
- self.isbot = TRUE;
- self.createdtime = self.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 = COLOR_TEAM1;
- else if(self.bot_forced_team==2)
- self.team = COLOR_TEAM2;
- else if(self.bot_forced_team==3)
- self.team = COLOR_TEAM3;
- else if(self.bot_forced_team==4)
- self.team = COLOR_TEAM4;
- else
- JoinBestTeam(self, FALSE, TRUE);
-
- havocbot_setupbot();
- self.bot_mouseskill=random()-0.5;
- self.bot_thinkskill=random()-0.5;
- self.bot_predictionskill=random()-0.5;
- self.bot_offsetskill=random()-0.5;
-};
-
-entity bot_spawn()
-{
- local entity oldself, bot;
- bot = spawnclient();
- if (bot)
- {
- currentbots = currentbots + 1;
- oldself = self;
- self = bot;
- bot_setnameandstuff();
- ClientConnect();
- PutClientInServer();
- self = oldself;
- }
- return bot;
-};
-
-void CheckAllowedTeams(entity for_whom); void GetTeamCounts(entity other); float c1, c2, c3, c4;
-void bot_removefromlargestteam()
-{
- local float besttime, bestcount, thiscount;
- local 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 == COLOR_TEAM1)
- thiscount = c1;
- else if(head.team == COLOR_TEAM2)
- thiscount = c2;
- else if(head.team == COLOR_TEAM3)
- thiscount = c3;
- else if(head.team == COLOR_TEAM4)
- 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()
-{
- local float besttime;
- local entity best, head;
-
- if(teams_matter)
- {
- 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);
-};
-
-float botframe_waypointeditorlightningtime;
-void botframe_showwaypointlinks()
-{
- local 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");
- }
-};
-
-entity botframe_dangerwaypoint;
-void botframe_updatedangerousobjects(float maxupdate)
-{
- local entity head, bot_dodgelist;
- local vector m1, m2, v;
- local 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);
- d = head.bot_dodgerating - vlen(head.origin - v);
- if (d > 0)
- {
- traceline(head.origin, 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");
- }
-};
-
-
-float botframe_spawnedwaypoints;
-float botframe_nextthink;
-float botframe_nextdangertime;
-
-float autoskill_nextthink;
-.float totalfrags_lastcheck;
-void autoskill(float factor)
-{
- float bestbot;
- float bestplayer;
- entity head;
-
- bestbot = -1;
- bestplayer = -1;
- FOR_EACH_PLAYER(head)
- {
- if(clienttype(head) == CLIENTTYPE_REAL)
- bestplayer = max(bestplayer, head.totalfrags - head.totalfrags_lastcheck);
- else
- bestbot = max(bestbot, head.totalfrags - head.totalfrags_lastcheck);
- }
-
- dprint("autoskill: best player got ", ftos(bestplayer), ", ");
- dprint("best bot got ", ftos(bestbot), "; ");
- if(bestbot < 0 || bestplayer < 0)
- {
- dprint("not doing anything\n");
- // don't return, let it reset all counters below
- }
- else if(bestbot <= bestplayer * factor - 2)
- {
- if(cvar("skill") < 17)
- {
- dprint("2 frags difference, increasing skill\n");
- cvar_set("skill", ftos(cvar("skill") + 1));
- bprint("^2SKILL UP!^7 Now at level ", ftos(cvar("skill")), "\n");
- }
- }
- else if(bestbot >= bestplayer * factor + 2)
- {
- if(cvar("skill") > 0)
- {
- dprint("2 frags difference, decreasing skill\n");
- cvar_set("skill", ftos(cvar("skill") - 1));
- bprint("^1SKILL DOWN!^7 Now at level ", ftos(cvar("skill")), "\n");
- }
- }
- else
- {
- dprint("not doing anything\n");
- return;
- // don't reset counters, wait for them to accumulate
- }
-
- FOR_EACH_PLAYER(head)
- head.totalfrags_lastcheck = head.totalfrags;
-}
-
-float bot_cvar_nextthink;
-void bot_serverframe()
-{
- float realplayers, bots, activerealplayers;
- entity head;
-
- if (intermission_running)
- return;
-
- if (time < 2)
- return;
-
- stepheightvec = cvar("sv_stepheight") * '0 0 1';
- bot_navigation_movemode = ((cvar("bot_navigation_ignoreplayers")) ? MOVE_NOMONSTERS : MOVE_NORMAL);
-
- if(time > autoskill_nextthink)
- {
- float a;
- a = cvar("skill_auto");
- if(a)
- autoskill(a);
- autoskill_nextthink = time + 5;
- }
-
- activerealplayers = 0;
- realplayers = 0;
-
- FOR_EACH_REALCLIENT(head)
- {
- if(head.classname == "player" || g_lms || g_arena)
- ++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 ((realplayers || cvar("bot_join_empty") || (currentbots > 0 && time < 5)))
- {
- float realminplayers, minplayers;
- realminplayers = cvar("minplayers");
- minplayers = max(0, floor(realminplayers));
-
- float realminbots, minbots;
- if(teamplay && cvar("bot_vs_human"))
- realminbots = ceil(fabs(cvar("bot_vs_human")) * activerealplayers);
- else
- realminbots = cvar("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;
- }
-
- bot_ignore_bots = cvar("bot_ignore_bots");
-
- // 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");
- botframe_nextthink = time + 10;
- break;
- }
- }
- while (currentbots > bots)
- bot_removenewest();
- }
-
- if(botframe_spawnedwaypoints)
- {
- if(cvar("waypoint_benchmark"))
- localcmd("quit\n");
- }
-
- if (currentbots > 0 || cvar("g_waypointeditor"))
- if (botframe_spawnedwaypoints)
- {
- if(botframe_cachedwaypointlinks)
- {
- if(!botframe_loadedforcedlinks)
- waypoint_load_links_hardwired();
- }
- else
- {
- // TODO: Make this check cleaner
- local 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)
- {
- local float interval;
- interval = cvar("bot_ai_dangerdetectioninterval");
- if (botframe_nextdangertime < time - interval * 1.5)
- botframe_nextdangertime = time;
- botframe_nextdangertime = botframe_nextdangertime + interval;
- botframe_updatedangerousobjects(cvar("bot_ai_dangerdetectionupdates"));
- }
- }
-
- if (cvar("g_waypointeditor"))
- botframe_showwaypointlinks();
-
- if(time > bot_cvar_nextthink)
- {
- if(currentbots>0)
- bot_custom_weapon_priority_setup();
- bot_cvar_nextthink = time + 5;
- }
-};
-
-FTEQCC_YOU_SUCK_THIS_IS_NOT_UNREFERENCED(lag_time);
-FTEQCC_YOU_SUCK_THIS_IS_NOT_UNREFERENCED(lag_float1);
-FTEQCC_YOU_SUCK_THIS_IS_NOT_UNREFERENCED(lag_float2);
-FTEQCC_YOU_SUCK_THIS_IS_NOT_UNREFERENCED(lag_vec1);
-FTEQCC_YOU_SUCK_THIS_IS_NOT_UNREFERENCED(lag_vec2);
-FTEQCC_YOU_SUCK_THIS_IS_NOT_UNREFERENCED(lag_vec3);
-FTEQCC_YOU_SUCK_THIS_IS_NOT_UNREFERENCED(lag_vec4);
-FTEQCC_YOU_SUCK_THIS_IS_NOT_UNREFERENCED(lag_entity1);
Deleted: trunk/data/qcsrc/server/bots_scripting.qc
===================================================================
--- trunk/data/qcsrc/server/bots_scripting.qc 2009-09-20 17:48:33 UTC (rev 7850)
+++ trunk/data/qcsrc/server/bots_scripting.qc 2009-09-20 21:43:29 UTC (rev 7851)
@@ -1,1306 +0,0 @@
-.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)
- error("clearqueue but no queue allocated");
- buf_del(bot.bot_cmdqueuebuf);
- bot.bot_cmdqueuebuf_allocated = FALSE;
- dprint("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;
- }
-
- bufstr_set(bot.bot_cmdqueuebuf, bot.bot_cmdqueuebuf_end, cmdstring);
- 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;
-}
-
-#define MAX_BOT_PLACES 4
-.float bot_places_count;
-.entity bot_places[MAX_BOT_PLACES]; FTEQCC_YOU_SUCK_THIS_IS_NOT_UNREFERENCED(bot_places);
-.string bot_placenames[MAX_BOT_PLACES]; FTEQCC_YOU_SUCK_THIS_IS_NOT_UNREFERENCED(bot_placenames);
-entity bot_getplace(string placename)
-{
- entity e;
- if(substring(placename, 0, 1) == "@")
- {
- float 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)
- print("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)
- print("invalid place ", s, "\n");
- return e;
- }
-}
-
-
-// NOTE: New commands should be added here. Do not forget to update BOT_CMD_COUNTER
-#define BOT_CMD_NULL 0
-#define BOT_CMD_PAUSE 1
-#define BOT_CMD_CONTINUE 2
-#define BOT_CMD_WAIT 3
-#define BOT_CMD_TURN 4
-#define BOT_CMD_MOVETO 5
-#define BOT_CMD_RESETGOAL 6 // Not implemented yet
-#define BOT_CMD_CC 7
-#define BOT_CMD_IF 8
-#define BOT_CMD_ELSE 9
-#define BOT_CMD_FI 10
-#define BOT_CMD_RESETAIM 11
-#define BOT_CMD_AIM 12
-#define BOT_CMD_PRESSKEY 13
-#define BOT_CMD_RELEASEKEY 14
-#define BOT_CMD_SELECTWEAPON 15
-#define BOT_CMD_IMPULSE 16
-#define BOT_CMD_WAIT_UNTIL 17
-#define BOT_CMD_MOVETOTARGET 18
-#define BOT_CMD_AIMTARGET 19
-#define BOT_CMD_BARRIER 20
-#define BOT_CMD_CONSOLE 21
-#define BOT_CMD_SOUND 22
-#define BOT_CMD_WHILE 23 // TODO: Not implemented yet
-#define BOT_CMD_WEND 24 // TODO: Not implemented yet
-#define BOT_CMD_CHASE 25 // TODO: Not implemented yet
-
-#define BOT_CMD_COUNTER 23 // 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;
-
-//
-#define BOT_CMD_PARAMETER_NONE 0
-#define BOT_CMD_PARAMETER_FLOAT 1
-#define BOT_CMD_PARAMETER_STRING 2
-#define BOT_CMD_PARAMETER_VECTOR 3
-
-float bot_cmds_initialized;
-float 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
-.float 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
-
-// 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_cmds_initialized = TRUE;
-}
-
-// Returns first bot with matching name
-entity find_bot_by_name(string name)
-{
- local entity bot;
-
- bot = findchainflags(flags, FL_CLIENT);
- while (bot)
- {
- if(clienttype(bot) == CLIENTTYPE_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)
-{
- local entity bot;
- local float c;
-
- if(!number)
- return world;
-
- bot = findchainflags(flags, FL_CLIENT);
- while (bot)
- {
- if(clienttype(bot) == CLIENTTYPE_BOT)
- {
- if(++c==number)
- return bot;
- }
- bot = bot.chain;
- }
-
- return world;
-}
-
-float bot_decodecommand(string cmdstring)
-{
- local float cmd_parm_type, i;
- 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();
-
- for(i=1;i<BOT_CMD_COUNTER;++i)
- {
- if(bot_cmd_string[i]!=cmdstring)
- continue;
-
- cmd_parm_type = bot_cmd_parm_type[i];
-
- if(cmd_parm_type!=BOT_CMD_PARAMETER_NONE&&parm=="")
- {
- print("ERROR: A parameter is required for this command\n");
- return 0;
- }
-
- // Load command into queue
- bot_cmd.bot_cmd_type = i;
-
- // Attach parameter
- switch(cmd_parm_type)
- {
- case BOT_CMD_PARAMETER_FLOAT:
- bot_cmd.bot_cmd_parm_float = stof(parm);
- break;
- case BOT_CMD_PARAMETER_STRING:
- if(bot_cmd.bot_cmd_parm_string)
- strunzone(bot_cmd.bot_cmd_parm_string);
- bot_cmd.bot_cmd_parm_string = strzone(parm);
- break;
- case BOT_CMD_PARAMETER_VECTOR:
- bot_cmd.bot_cmd_parm_vector = stov(parm);
- break;
- default:
- break;
- }
- return 1;
- }
- print("ERROR: No such command '", cmdstring, "'\n");
- return 0;
-}
-
-void bot_cmdhelp(string scmd)
-{
- local float i, ntype;
- local string stype;
-
- if(!bot_cmds_initialized)
- bot_commands_init();
-
- for(i=1;i<BOT_CMD_COUNTER;++i)
- {
- if(bot_cmd_string[i]!=scmd)
- continue;
-
- ntype = bot_cmd_parm_type[i];
-
- switch(ntype)
- {
- case BOT_CMD_PARAMETER_FLOAT:
- stype = "float number";
- break;
- case BOT_CMD_PARAMETER_STRING:
- stype = "string";
- break;
- case BOT_CMD_PARAMETER_VECTOR:
- stype = "vector";
- break;
- default:
- stype = "none";
- break;
- }
-
- print(strcat("Command: ",bot_cmd_string[i],"\nParameter: <",stype,"> \n"));
-
- print("Description: ");
- switch(i)
- {
- case BOT_CMD_PAUSE:
- print("Stops the bot completely. Any command other than 'continue' will be ignored.");
- break;
- case BOT_CMD_CONTINUE:
- print("Disable paused status");
- break;
- case BOT_CMD_WAIT:
- print("Pause command parsing and bot ai for N seconds. Pressed key will remain pressed");
- break;
- case BOT_CMD_WAIT_UNTIL:
- print("Pause command parsing and bot ai until time is N from the last barrier. Pressed key will remain pressed");
- break;
- case BOT_CMD_BARRIER:
- print("Waits till all bots that have a command queue reach this command. Pressed key will remain pressed");
- break;
- case BOT_CMD_TURN:
- print("Look to the right or left N degrees. For turning to the left use positive numbers.");
- break;
- case BOT_CMD_MOVETO:
- print("Walk to an specific coordinate on the map. Usage: moveto \"x y z\"");
- break;
- case BOT_CMD_MOVETOTARGET:
- print("Walk to the specific target on the map");
- break;
- case BOT_CMD_RESETGOAL:
- print("Resets the goal stack");
- break;
- case BOT_CMD_CC:
- print("Execute client command. Examples: cc \"say something\"; cc god; cc \"name newnickname\"; cc kill;");
- break;
- case BOT_CMD_IF:
- print("Perform simple conditional execution.\n");
- print("Syntax: \n");
- print(" sv_cmd .. if \"condition\"\n");
- print(" sv_cmd .. <instruction if true>\n");
- print(" sv_cmd .. <instruction if true>\n");
- print(" sv_cmd .. else\n");
- print(" sv_cmd .. <instruction if false>\n");
- print(" sv_cmd .. <instruction if false>\n");
- print(" sv_cmd .. fi\n");
- print("Conditions: a=b, a>b, a<b, a\t\t(spaces not allowed)\n");
- print(" Values in conditions can be numbers, cvars in the form cvar.cvar_string or special fields\n");
- print("Fields: health, speed, flagcarrier\n");
- print("Examples: if health>50; if health>cvar.g_balance_laser_primary_damage; if flagcarrier;");
- break;
- case BOT_CMD_RESETAIM:
- print("Points the aim to the coordinates x,y 0,0");
- break;
- case BOT_CMD_AIM:
- print("Move the aim x/y (horizontal/vertical) degrees relatives to the bot\n");
- print("There is a 3rd optional parameter telling in how many seconds the aim has to reach the new position\n");
- print("Examples: aim \"90 0\" // Turn 90 degrees inmediately (positive numbers move to the left/up)\n");
- print(" aim \"0 90 2\" // Will gradually look to the sky in the next two seconds");
- break;
- case BOT_CMD_AIMTARGET:
- print("Points the aim to given target");
- break;
- case BOT_CMD_PRESSKEY:
- print("Press one of the following keys: forward, backward, left, right, jump, crouch, attack1, attack2, use\n");
- print("Multiple keys can be pressed at time (with many presskey calls) and it will remain pressed until the command \"releasekey\" is called");
- print("Note: The script will not return the control to the bot ai until all keys are released");
- break;
- case BOT_CMD_RELEASEKEY:
- print("Release previoulsy used keys. Use the parameter \"all\" to release all keys");
- break;
- case BOT_CMD_SOUND:
- print("play sound file at bot location");
- break;
- default:
- print("This command has no description yet.");
- break;
- }
- print("\n");
- }
-}
-
-void bot_list_commands()
-{
- local float i;
- local string ptype;
-
- if(!bot_cmds_initialized)
- bot_commands_init();
-
- print("List of all available commands:\n");
- print(" Command\t\t\t\tParameter Type\n");
-
- for(i=1;i<BOT_CMD_COUNTER;++i)
- {
- switch(bot_cmd_parm_type[i])
- {
- case BOT_CMD_PARAMETER_FLOAT:
- ptype = "float number";
- break;
- case BOT_CMD_PARAMETER_STRING:
- ptype = "string";
- break;
- case BOT_CMD_PARAMETER_VECTOR:
- ptype = "vector";
- break;
- default:
- ptype = "none";
- break;
- }
- print(strcat(" ",bot_cmd_string[i],"\t\t\t\t<",ptype,"> \n"));
- }
-}
-
-// Commands code
-.float bot_exec_status;
-
-#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 SV_ParseClientCommand(string s);
-float bot_cmd_cc()
-{
- SV_ParseClientCommand(bot_cmd.bot_cmd_parm_string);
- return CMD_STATUS_FINISHED;
-}
-
-float bot_cmd_impulse()
-{
- self.impulse = bot_cmd.bot_cmd_parm_float;
- return CMD_STATUS_FINISHED;
-}
-
-float bot_cmd_continue()
-{
- self.bot_exec_status &~= BOT_EXEC_STATUS_PAUSED;
- return CMD_STATUS_FINISHED;
-}
-
-.float bot_cmd_wait_time;
-float bot_cmd_wait()
-{
- 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()
-{
- 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()
-{
- 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()
-{
- 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()
-{
- local 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;
-}
-
-.float bot_cmd_condition_status;
-
-#define CMD_CONDITION_NONE 0
-#define CMD_CONDITION_TRUE 1
-#define CMD_CONDITION_FALSE 2
-#define CMD_CONDITION_TRUE_BLOCK 4
-#define CMD_CONDITION_FALSE_BLOCK 8
-
-float bot_cmd_eval(string expr)
-{
- // 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));
- }
-
- print(strcat("ERROR: Unable to convert the expression '",expr,"' into a numeric value\n"));
- return 0;
-}
-
-float bot_cmd_if()
-{
- local string expr, val_a, val_b;
- local float cmpofs;
-
- if(self.bot_cmd_condition_status != CMD_CONDITION_NONE)
- {
- // Only one "if" block is allowed at time
- print("ERROR: Only one conditional block can be processed at time");
- bot_clearqueue(self);
- return CMD_STATUS_ERROR;
- }
-
- self.bot_cmd_condition_status |= CMD_CONDITION_TRUE_BLOCK;
-
- // search for operators
- expr = bot_cmd.bot_cmd_parm_string;
-
- cmpofs = strstrofs(expr,"=",0);
-
- if(cmpofs>0)
- {
- val_a = substring(expr,0,cmpofs);
- val_b = substring(expr,cmpofs+1,strlen(expr));
-
- if(bot_cmd_eval(val_a)==bot_cmd_eval(val_b))
- self.bot_cmd_condition_status |= CMD_CONDITION_TRUE;
- else
- self.bot_cmd_condition_status |= CMD_CONDITION_FALSE;
-
- return CMD_STATUS_FINISHED;
- }
-
- cmpofs = strstrofs(expr,">",0);
-
- if(cmpofs>0)
- {
- val_a = substring(expr,0,cmpofs);
- val_b = substring(expr,cmpofs+1,strlen(expr));
-
- if(bot_cmd_eval(val_a)>bot_cmd_eval(val_b))
- self.bot_cmd_condition_status |= CMD_CONDITION_TRUE;
- else
- self.bot_cmd_condition_status |= CMD_CONDITION_FALSE;
-
- return CMD_STATUS_FINISHED;
- }
-
- cmpofs = strstrofs(expr,"<",0);
-
- if(cmpofs>0)
- {
- val_a = substring(expr,0,cmpofs);
- val_b = substring(expr,cmpofs+1,strlen(expr));
-
- if(bot_cmd_eval(val_a)<bot_cmd_eval(val_b))
- self.bot_cmd_condition_status |= CMD_CONDITION_TRUE;
- else
- self.bot_cmd_condition_status |= CMD_CONDITION_FALSE;
-
- return CMD_STATUS_FINISHED;
- }
-
- if(bot_cmd_eval(expr))
- self.bot_cmd_condition_status |= CMD_CONDITION_TRUE;
- else
- self.bot_cmd_condition_status |= CMD_CONDITION_FALSE;
-
- return CMD_STATUS_FINISHED;
-}
-
-float bot_cmd_else()
-{
- self.bot_cmd_condition_status &~= CMD_CONDITION_TRUE_BLOCK;
- self.bot_cmd_condition_status |= CMD_CONDITION_FALSE_BLOCK;
- return CMD_STATUS_FINISHED;
-}
-
-float bot_cmd_fi()
-{
- self.bot_cmd_condition_status = CMD_CONDITION_NONE;
- return CMD_STATUS_FINISHED;
-}
-
-float bot_cmd_resetaim()
-{
- self.v_angle = '0 0 0';
- return CMD_STATUS_FINISHED;
-}
-
-.float bot_cmd_aim_begintime;
-.float bot_cmd_aim_endtime;
-.vector bot_cmd_aim_begin;
-.vector bot_cmd_aim_end;
-
-float bot_cmd_aim()
-{
- // Current direction
- if(self.bot_cmd_aim_endtime)
- {
- local float progress;
-
- progress = min(1 - (self.bot_cmd_aim_endtime - time) / (self.bot_cmd_aim_endtime - self.bot_cmd_aim_begintime),1);
- self.v_angle = self.bot_cmd_aim_begin + ((self.bot_cmd_aim_end - self.bot_cmd_aim_begin) * progress);
-
- if(time>=self.bot_cmd_aim_endtime)
- {
- self.bot_cmd_aim_endtime = 0;
- return CMD_STATUS_FINISHED;
- }
- else
- return CMD_STATUS_EXECUTING;
- }
-
- // New aiming direction
- local string parms;
- local float tokens, step;
-
- parms = bot_cmd.bot_cmd_parm_string;
-
- tokens = tokenizebyseparator(parms, " ");
-
- if(tokens==2)
- {
- self.v_angle_x -= stof(argv(1));
- self.v_angle_y += stof(argv(0));
- return CMD_STATUS_FINISHED;
- }
-
- if(tokens<2||tokens>3)
- return CMD_STATUS_ERROR;
-
- step = stof(argv(2));
-
- 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()
-{
- if(self.bot_cmd_aim_endtime)
- {
- return bot_cmd_aim();
- }
-
- local entity e;
- local string parms;
- local vector v;
- local 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;
-}
-
-.float bot_cmd_keys;
-
-#define BOT_CMD_KEY_NONE 0
-#define BOT_CMD_KEY_FORWARD 1
-#define BOT_CMD_KEY_BACKWARD 2
-#define BOT_CMD_KEY_RIGHT 4
-#define BOT_CMD_KEY_LEFT 8
-#define BOT_CMD_KEY_JUMP 16
-#define BOT_CMD_KEY_ATTACK1 32
-#define BOT_CMD_KEY_ATTACK2 64
-#define BOT_CMD_KEY_USE 128
-#define BOT_CMD_KEY_HOOK 256
-#define BOT_CMD_KEY_CROUCH 512
-#define BOT_CMD_KEY_CHAT 1024
-
-float bot_presskeys()
-{
- self.movement = '0 0 0';
-
- if(self.bot_cmd_keys == BOT_CMD_KEY_NONE)
- return FALSE;
-
- if(self.bot_cmd_keys & BOT_CMD_KEY_FORWARD)
- self.movement_x = cvar("sv_maxspeed");
- else if(self.bot_cmd_keys & BOT_CMD_KEY_BACKWARD)
- self.movement_x = -cvar("sv_maxspeed");
-
- if(self.bot_cmd_keys & BOT_CMD_KEY_RIGHT)
- self.movement_y = cvar("sv_maxspeed");
- else if(self.bot_cmd_keys & BOT_CMD_KEY_LEFT)
- self.movement_y = -cvar("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)
-{
- 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()
-{
- local string key;
-
- key = bot_cmd.bot_cmd_parm_string;
-
- bot_cmd_keypress_handler(key,TRUE);
-
- return CMD_STATUS_FINISHED;
-}
-
-float bot_cmd_releasekey()
-{
- local string key;
-
- key = bot_cmd.bot_cmd_parm_string;
-
- return bot_cmd_keypress_handler(key,FALSE);
-}
-
-float bot_cmd_pause()
-{
- 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()
-{
- return self.cmd_moveto(bot_cmd.bot_cmd_parm_vector);
-}
-
-float bot_cmd_movetotarget()
-{
- 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()
-{
- return self.cmd_resetgoal();
-}
-
-
-float bot_cmd_sound()
-{
- string f;
- f = bot_cmd.bot_cmd_parm_string;
-
- precache_sound(f);
- sound(self, CHAN_WEAPON2, f, VOL_BASE, ATTN_MIN);
-
- return CMD_STATUS_FINISHED;
-}
-
-//
-
-void bot_command_executed(float rm)
-{
- entity cmd;
-
- cmd = bot_cmd;
-
- if(rm)
- bot_dequeuecommand(self, self.bot_cmd_execution_index);
-
- self.bot_cmd_execution_index++;
-}
-
-void bot_setcurrentcommand()
-{
- 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;
- float i;
-
- FOR_EACH_CLIENT(cl) if(cl.isbot)
- {
- if(cl.bot_cmdqueuebuf_allocated)
- bot_clearqueue(cl);
- // also, cancel all barriers
- cl.bot_barrier = 0;
- for(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()
-{
- local float status, ispressingkey;
-
- if(self.deadflag!=DEAD_NO)
- return 0;
-
- // Find command
- bot_setcurrentcommand();
-
- // 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);
- print( "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();
-
- if(bot_cmd==world)
- return ispressingkey;
-
- // Handle conditions
- if not(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;
- default:
- print(strcat("ERROR: Invalid command on queue with id '",ftos(bot_cmd.bot_cmd_type),"'\n"));
- return 0;
- }
-
- if (status==CMD_STATUS_ERROR)
- print(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(cvar("g_debug_bot_commands"))
- {
- local 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;
-}
Deleted: trunk/data/qcsrc/server/havocbot.qc
===================================================================
--- trunk/data/qcsrc/server/havocbot.qc 2009-09-20 17:48:33 UTC (rev 7850)
+++ trunk/data/qcsrc/server/havocbot.qc 2009-09-20 21:43:29 UTC (rev 7851)
@@ -1,1443 +0,0 @@
-.void() havocbot_role;
-void() havocbot_chooserole;
-.float havocbot_keyboardskill;
-.float facingwalltime, ignoregoaltime;
-.entity ignoregoal;
-.float lastfiredweapon;
-.float lastcombotime;
-.float havocbot_blockhead;
-
-#ifdef DEBUG_BOT_GOALSTACK
-void debuggoalstack();
-#endif
-
-vector havocbot_dodge()
-{
- // LordHavoc: disabled because this is too expensive
- return '0 0 0';
- /*
- local entity head;
- local vector dodge, v, n;
- local 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 > 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;
- */
-};
-
-.float havocbot_keyboardtime;
-.float havocbot_ducktime;
-.vector havocbot_keyboard;
-void havocbot_keyboard_movement(vector destorg)
-{
- local vector keyboard;
- local float blend, maxspeed;
-
- maxspeed = cvar("sv_maxspeed");
-
- if (time < self.havocbot_keyboardtime)
- return;
-
- self.havocbot_keyboardtime =
- max(
- self.havocbot_keyboardtime
- + bound(0,0.05/(skill+self.havocbot_keyboardskill),0.05)
- +random()*bound(0,0.025/(skill+self.havocbot_keyboardskill),100)
- , time);
- keyboard = self.movement * (1.0 / maxspeed);
-
- local float trigger, trigger1;
- blend = bound(0,skill*0.1,1);
- trigger = cvar("bot_ai_keyboard_treshold");
- 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 (skill < 2.5)
- keyboard_y = 0;
- }
- else if (keyboard_x < trigger1 && skill > 1.5)
- {
- keyboard_x = -1;
- if (skill < 4.5)
- keyboard_y = 0;
- }
- else
- {
- keyboard_x = 0;
- if (skill < 1.5)
- keyboard_y = 0;
- }
- if (skill < 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)/cvar("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;
-};
-
-.entity bot_lastseengoal;
-.float bot_timelastseengoal;
-.float bot_canruntogoal;
-void havocbot_bunnyhop(vector dir)
-{
- local float bunnyhopdistance;
- local vector deviation;
- local float maxspeed;
-
- if(cvar("g_midair"))
- return;
-
- // Don't jump when using some weapons
- if(self.aistatus & AI_STATUS_ATTACKING)
- if(self.weapon & WEP_CAMPINGRIFLE)
- return;
-
- if(self.goalcurrent.classname == "player")
- return;
-
- maxspeed = cvar("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;
- }
-
- bunnyhopdistance = vlen(self.origin - self.goalcurrent.origin);
-
- // 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 > cvar("bot_ai_bunnyhop_firstjumpdelay"))
- {
- local float checkdistance;
- checkdistance = TRUE;
-
- // don't run if it is too close
- if(self.bot_canruntogoal==0)
- {
- if(bunnyhopdistance > cvar("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 not(self.goalcurrent.wpflags & WAYPOINTFLAG_PERSONAL)
- if(fabs(self.goalcurrent.origin_z - self.origin_z) < self.maxs_z - self.mins_z)
- if(self.goalstack01!=world)
- {
- deviation = vectoangles(self.goalstack01.origin - self.origin) - vectoangles(self.goalcurrent.origin - 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 - self.goalstack01.origin))
- if(fabs(self.goalstack01.origin_z - self.goalcurrent.origin_z) < self.maxs_z - self.mins_z)
- {
- if(vlen(self.goalcurrent.origin - self.goalstack01.origin) > cvar("bot_ai_bunnyhop_startdistance"))
- if(checkpvs(self.origin + self.view_ofs, self.goalstack01))
- {
- checkdistance = FALSE;
- }
- }
- }
-
- if(checkdistance)
- {
- self.aistatus &~= AI_STATUS_RUNNING;
- if(bunnyhopdistance > cvar("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;
- }
-
- // Release jump button
- if(self.flags & FL_ONGROUND == 0)
- {
- if(self.velocity_z < 0 || vlen(self.velocity)<maxspeed)
- self.BUTTON_JUMP = FALSE;
-
- // Strafe
- if(self.aistatus & AI_STATUS_RUNNING)
- if(vlen(self.velocity)>maxspeed)
- {
- deviation = vectoangles(dir) - vectoangles(self.velocity);
- while (deviation_y < -180) deviation_y = deviation_y + 360;
- while (deviation_y > 180) deviation_y = deviation_y - 360;
-
- if(fabs(deviation_y)>10)
- self.movement_x = 0;
-
- if(deviation_y>10)
- self.movement_y = maxspeed * -1;
- else if(deviation_y<10)
- self.movement_y = maxspeed;
-
- }
- }
-};
-
-//.float havocbotignoretime;
-//.vector bot_dodgevector;
-//.float bot_dodgevector_time;
-//.float bot_dodgevector_jumpbutton;
-.float ladder_time;
-.entity ladder_entity;
-.float rocketjumptime;
-void havocbot_movetogoal()
-{
- local vector destorg;
- local vector diff;
- local vector dir;
- local vector flatdir;
- local vector m1;
- local vector m2;
- local vector evadeobstacle;
- local vector evadelava;
- local float s;
- local float maxspeed;
- //local float dist;
- local vector dodge;
- //if (self.goalentity)
- // te_lightning2(self, self.origin, (self.goalentity.absmin + self.goalentity.absmax) * 0.5);
- self.movement = '0 0 0';
- maxspeed = cvar("sv_maxspeed");
-
- // Jetpack navigation
- if(self.navigation_jetpack_goal)
- if(self.goalcurrent==self.navigation_jetpack_goal)
- if(self.ammo_fuel)
- {
- #ifdef DEBUG_BOT_GOALSTACK
- debuggoalstack();
- te_wizspike(self.navigation_jetpack_point);
- #endif
-
- // Take off
- if not(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.origin; dxy_z = 0;
- d = vlen(dxy);
- v = vlen(self.velocity - self.velocity_z * '0 0 1');
- db = (pow(v,2) / (cvar("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(self.flags & FL_ONGROUND)
- {
- self.jumppadcount = FALSE;
- if(self.aistatus & AI_STATUS_OUT_JUMPPAD)
- self.aistatus &~= AI_STATUS_OUT_JUMPPAD;
- }
-
- // If got stuck on the jump pad try to reach the farther visible item
- if(self.aistatus & AI_STATUS_OUT_JUMPPAD)
- {
- if(fabs(self.velocity_z)<50)
- {
- local entity head, newgoal;
- local float distance, bestdistance;
-
- for (head = findchainfloat(bot_pickup, TRUE); head; head = head.chain)
- {
- if(head.classname=="worldspawn")
- continue;
-
- distance = vlen(head.origin - self.origin);
- if(distance>1000)
- continue;
-
- traceline(self.origin + self.view_ofs , head.origin, TRUE, world);
-
- if(trace_fraction<1)
- continue;
-
- if(distance>bestdistance)
- {
- newgoal = head;
- bestdistance = distance;
- }
- }
-
- if(newgoal)
- {
- self.ignoregoal = self.goalcurrent;
- self.ignoregoaltime = time + cvar("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)
- {
- local float threshold;
- threshold = maxspeed * 0.2;
- if(fabs(self.velocity_x) < threshold && fabs(self.velocity_y) < threshold)
- self.aistatus |= AI_STATUS_OUT_JUMPPAD;
- return;
- }
- }
- }
-
- // If there is a trigger_hurt right below try to use the jetpack or make a rocketjump
- if(skill>6)
- if not(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.origin - self.origin);
-
- local vector xyvelocity = self.velocity; xyvelocity_z = 0;
- local 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>cvar("g_balance_rocketlauncher_damage")*0.5)
- {
- if(self.velocity_z < 0)
- if(client_hasweapon(self, WEP_ROCKET_LAUNCHER, TRUE, FALSE))
- {
- self.movement_x = maxspeed;
-
- if(self.rocketjumptime)
- {
- if(time > self.rocketjumptime)
- {
- self.BUTTON_ATCK2 = TRUE;
- self.rocketjumptime = 0;
- }
- return;
- }
-
- self.switchweapon = WEP_ROCKET_LAUNCHER;
- self.v_angle_x = 90;
- self.BUTTON_ATCK = TRUE;
- self.rocketjumptime = time + cvar("g_balance_rocketlauncher_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;
- }
-
-#ifdef DEBUG_BOT_GOALSTACK
- debuggoalstack();
-#endif
-
- 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);
-
- //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 && self.goalcurrent.origin_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 + '0 0 16', self.mins, self.maxs, self.origin + self.velocity * 0.2 + '0 0 16', FALSE, self);
- if (trace_fraction < s + 0.01)
- if (trace_plane_normal_z < 0.7)
- {
- s = trace_fraction;
- tracebox(self.origin + '0 0 48', self.mins, self.maxs, self.origin + self.velocity * 0.2 + '0 0 48', FALSE, self);
- if (trace_fraction > s)
- self.BUTTON_JUMP = 1;
- }
- }
-
- // avoiding dangers and obstacles
- local 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 + cvar("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)
- 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
- if (self.goalcurrent.classname == "player" || self.goalcurrent.classname == "droppedweapon")
- navigation_poproute();
- // try to stop
- flatdir = '0 0 0';
- evadeobstacle = normalize(self.velocity) * -1;
- }
- }
- }
- }
-
- 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,3+skill*0.1,1);
- evadelava = evadelava * bound(1,3-skill,3); //Noobs fear lava a lot and take more distance from it
- traceline(self.origin, self.enemy.origin, TRUE, world);
- if(trace_ent.classname == "player")
- dir = dir * bound(0,skill/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(skill >= cvar("bot_ai_bunnyhop_skilloffset"))
- havocbot_bunnyhop(dir);
-
- if ((dir * v_up) >= cvar("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)*0.1,1)) self.BUTTON_JUMP=TRUE;
- if (((dodge * v_up) < 0) && random()*frametime >= 0.5*bound(0,(10-skill)*0.1,1)) self.havocbot_ducktime=time+0.3/bound(0.1,skill,10);
-};
-
-.float havocbot_chooseenemy_finished;
-.float havocbot_stickenemy;
-void havocbot_chooseenemy()
-{
- local entity head, best, head2;
- local float rating, bestrating, i, f;
- local vector eye, v;
- if (cvar("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.origin+self.enemy.view_ofs*0.5,FALSE,world);
- if (trace_ent == self.enemy || trace_fraction == 1)
- if (vlen(self.enemy.origin - 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 + cvar("bot_ai_enemydetectioninterval");
- 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 + cvar("bot_ai_enemydetectioninterval");
- eye = self.origin + self.view_ofs;
- best = world;
- bestrating = 100000000;
- head = head2 = findchainfloat(bot_attack, TRUE);
-
- // Search for enemies, if no enemy can be seen directly try to look through transparent objects
- for(;;)
- {
- while (head)
- {
- v = (head.absmin + head.absmax) * 0.5;
- rating = vlen(v - eye);
- if (rating<cvar("bot_ai_enemydetectionradius"))
- if (bestrating > rating)
- if (bot_shouldattack(head))
- {
- traceline(eye, v, TRUE, self);
- if (trace_ent == head || trace_fraction >= 1)
- {
- best = head;
- bestrating = rating;
- }
- }
- head = head.chain;
- }
-
- // 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_CAMPINGRIFLE
- break;
- if(i)
- break;
-
- // Set flags to see through transparent objects
- f = self.dphitcontentsmask;
- self.dphitcontentsmask = DPCONTENTS_OPAQUE;
-
- head = head2;
- ++i;
- }
-
- // Restore hit flags if needed
- if(i)
- self.dphitcontentsmask = f;
-
- self.enemy = best;
- self.havocbot_stickenemy = TRUE;
-};
-
-.float bot_chooseweapontime;
-float(entity e) w_getbestweapon;
-void havocbot_chooseweapon()
-{
- local float i;
-
- // ;)
- if(g_weaponarena == WEPBIT_TUBA)
- {
- self.switchweapon = WEP_TUBA;
- 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_LASER + 1; i < WEP_COUNT ; ++i)
- {
- if(client_hasweapon(self, i, TRUE, FALSE))
- {
- self.switchweapon = i;
- return;
- }
- }
- return;
- }
-
- // Do not change weapon during the next second after a combo
- i = time - self.lastcombotime;
- if(i < 1)
- return;
-
- // Workaround for rifle reloading (..)
- if(self.weapon == WEP_CAMPINGRIFLE)
- if(i < cvar("g_balance_campingrifle_reloadtime") + 1)
- return;
-
- local float w;
- local float rocket ; rocket =-1000;
- local float nex ; nex =-1000;
- local float hagar ; hagar =-1000;
- local float grenade ; grenade =-1000;
- local float electro ; electro =-1000;
- local float crylink ; crylink =-1000;
- local float uzi ; uzi =-1000;
- local float shotgun ; shotgun =-1000;
- local float campingrifle ; campingrifle =-1000;
- local float laser ; laser =-1000;
- local float minstanex ; minstanex =-1000;
- local float bestscore; bestscore = 0;
- local float bestweapon; bestweapon=self.switchweapon;
- local float distance; distance=bound(10,vlen(self.origin-self.enemy.origin)-200,10000);
- local float maxdelaytime=0.5;
- local float spreadpenalty=10;
-
- // Should it do a weapon combo?
- local float af, ct, combo_time, combo;
-
- af = ATTACK_FINISHED(self);
- ct = cvar("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)+3));
-
- combo = FALSE;
-
- if(cvar("bot_ai_weapon_combo"))
- if(self.weapon == self.lastfiredweapon)
- if(af > combo_time)
- {
- combo = TRUE;
- self.lastcombotime = time;
- }
-
- // 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 < WEP_COUNT && bot_weapons_far[i] != -1 ; ++i){
- w = bot_weapons_far[i];
- if ( client_hasweapon(self, w, TRUE, FALSE) ){
- if ( self.weapon == w && combo)
- continue;
- self.switchweapon = w;
- return;
- }
- }
- }
-
- // Choose weapons for mid distance
- if ( distance > bot_distance_close ) {
- for(i=0; i < WEP_COUNT && bot_weapons_mid[i] != -1 ; ++i){
- w = bot_weapons_mid[i];
- if ( client_hasweapon(self, w, TRUE, FALSE) ){
- if ( self.weapon == w && combo)
- continue;
- self.switchweapon = w;
- return;
- }
- }
- }
-
- // Choose weapons for close distance
- for(i=0; i < WEP_COUNT && bot_weapons_close[i] != -1 ; ++i){
- w = bot_weapons_close[i];
- if ( client_hasweapon(self, w, TRUE, FALSE) ){
- if ( self.weapon == w && combo)
- continue;
- self.switchweapon = w;
- return;
- }
- }
- }
-
-#ifdef 0
- // TODO: This disabled code is not working well and got replaced by custom weapon priorities.
- // However, this logic should be refactored and moved to weapons code so each new weapon can be
- // evaluated dynamically by bots without updating the "ai" or config files. --mand1nga
- float s, distancefromfloor, currentscore;
-
-
- // Formula:
- // (Damage/Sec * Weapon spefic change to get that damage)
- // *(Time to get to target * weapon specfic hitchange bonus) / (in a time of maxdelaytime)
- // *(Spread change of hit) // if it applies
- // *(Penality for target beeing in air)
- // %weaponaddpoint
-
- traceline(self.enemy.origin,self.enemy.origin-'0 0 1000',TRUE,world);
- distancefromfloor = self.enemy.origin_z - trace_endpos_z;
-
- if (client_hasweapon(self, WEP_MINSTANEX, TRUE, FALSE))
- minstanex = (1000/cvar("g_balance_minstanex_refire")*1.0)
- * (0.5);
-
- if (client_hasweapon(self, WEP_ROCKET_LAUNCHER, TRUE, FALSE) &&
- !( cvar("bot_ai_weapon_combo") && self.weapon == WEP_ROCKET_LAUNCHER &&
- af > combo_time
- )
- )
- rocket = (cvar("g_balance_rocketlauncher_damage")/cvar("g_balance_rocketlauncher_refire")*0.75)
- * bound(0,(cvar("g_balance_rocketlauncher_speed")/distance*maxdelaytime),1)*1.5;
-
- if (client_hasweapon(self, WEP_NEX, TRUE, FALSE) &&
- !( cvar("bot_ai_weapon_combo") && self.weapon == WEP_NEX &&
- af > combo_time
- )
- )
- nex = (cvar("g_balance_nex_damage")/cvar("g_balance_nex_refire")*1.0)
- * (0.5);
-
- if (client_hasweapon(self, WEP_HAGAR, TRUE, FALSE) ) // &&
- // !( cvar("bot_ai_weapon_combo") && self.weapon == WEP_HAGAR && time < self.bot_lastshot + cvar("g_balance_hagar_primary_refire") ))
- hagar = (cvar("g_balance_hagar_primary_damage")/cvar("g_balance_hagar_primary_refire")*1.0)
- * bound(0,(cvar("g_balance_hagar_primary_speed")/distance*maxdelaytime),1)*0.2;
-
- if (client_hasweapon(self, WEP_GRENADE_LAUNCHER, TRUE, FALSE) &&
- !(
- cvar("bot_ai_weapon_combo") && self.weapon == WEP_GRENADE_LAUNCHER &&
- af > combo_time
- )
- )
- grenade = (cvar("g_balance_grenadelauncher_primary_damage")/cvar("g_balance_grenadelauncher_primary_refire")*1.0)
- * bound(0,(cvar("g_balance_grenadelauncher_primary_speed")/distance*maxdelaytime),1)*1.1;
-
- if (client_hasweapon(self, WEP_ELECTRO, TRUE, FALSE) &&
- !( cvar("bot_ai_weapon_combo") && self.weapon == WEP_ELECTRO &&
- af > combo_time
- )
- )
- electro = (cvar("g_balance_electro_primary_damage")/cvar("g_balance_electro_primary_refire")*0.75)
- * bound(0,(cvar("g_balance_electro_primary_speed")/distance*maxdelaytime),1)*1.0;
-
- if (client_hasweapon(self, WEP_CRYLINK, TRUE, FALSE) ) // &&
- // !( self.weapon == WEP_CRYLINK && time < self.bot_lastshot + cvar("g_balance_crylink_primary_refire") ))
- crylink = (cvar("g_balance_crylink_primary_damage")/cvar("g_balance_crylink_primary_refire")*1.0)
- * bound(0,(cvar("g_balance_crylink_primary_speed")/distance*maxdelaytime),1)*(64/(32+cvar("g_balance_crylink_primary_spread")*distance))*1.0;
-
- if (client_hasweapon(self, WEP_UZI, TRUE, FALSE) ) // &&
- // !( self.weapon == WEP_UZI && time < self.bot_lastshot + cvar("g_balance_uzi_sustained_refire") ))
- uzi = (cvar("g_balance_uzi_sustained_damage")/cvar("g_balance_uzi_sustained_refire")*1.0)
- * bound(0,32/(32+cvar("g_balance_uzi_sustained_spread")*distance),1);
-
- if (client_hasweapon(self, WEP_SHOTGUN, TRUE, FALSE) &&
- !( cvar("bot_ai_weapon_combo") && self.weapon == WEP_SHOTGUN &&
- af > combo_time
- )
- )
- shotgun = (cvar("g_balance_shotgun_primary_damage")*cvar("g_balance_shotgun_primary_bullets")/cvar("g_balance_shotgun_primary_refire")*1.0)
- * bound(0,32/(32+cvar("g_balance_shotgun_primary_spread")*distance),1);
-
- if (client_hasweapon(self, WEP_LASER, FALSE, FALSE) &&
- !( cvar("bot_ai_weapon_combo") && self.weapon == WEP_LASER &&
- af > combo_time
- )
- )
- laser = (cvar("g_balance_laser_primary_damage")/cvar("g_balance_laser_primary_refire")*1.0)
- * bound(0,cvar("g_balance_laser_primary_speed")/distance*maxdelaytime,1);
-
- if((self.enemy.flags & FL_ONGROUND)==FALSE){
- rocket = rocket * (1.5-bound(0, distancefromfloor/cvar("g_balance_rocketlauncher_radius" ),0.9)); //slight bigger change
- grenade = grenade * (1.5-bound(0,distancefromfloor/cvar("g_balance_grenadelauncher_primary_radius"),0.95));
- electro = electro * (1.5-bound(0,distancefromfloor/cvar("g_balance_electro_primary_radius" ),0.95));
- laser = laser * (1.5-bound(0,distancefromfloor/cvar("g_balance_laser_primary_radius" ),0.95));
- }
- /*
- dprint("Floor distance: ",ftos(distancefromfloor),"\n");
- dprint("Rocket: " , ftos(rocket ), "\n");
- dprint("Nex: " , ftos(nex ), "\n");
- dprint("Hagar: " , ftos(hagar ), "\n");
- dprint("Grenade: ", ftos(grenade ), "\n");
- dprint("Electro: ", ftos(electro ), "\n");
- dprint("Crylink: ", ftos(crylink ), "\n");
- dprint("Uzi: " , ftos(uzi ), "\n");
- dprint("Shotgun :", ftos(shotgun ), "\n");
- dprint("Laser :", ftos(laser ), "\n\n");
- */
- currentscore = -1;
- w = WEP_MINSTANEX ;s = minstanex;if (s > bestscore){bestscore = s;bestweapon = w;} if (self.switchweapon == w) currentscore = s;
- w = WEP_ROCKET_LAUNCHER ;s = rocket ;if (s > bestscore){bestscore = s;bestweapon = w;} if (self.switchweapon == w) currentscore = s;
- w = WEP_NEX ;s = nex ;if (s > bestscore){bestscore = s;bestweapon = w;} if (self.switchweapon == w) currentscore = s;
- w = WEP_HAGAR ;s = hagar ;if (s > bestscore){bestscore = s;bestweapon = w;} if (self.switchweapon == w) currentscore = s;
- w = WEP_GRENADE_LAUNCHER ;s = grenade ;if (s > bestscore){bestscore = s;bestweapon = w;} if (self.switchweapon == w) currentscore = s;
- w = WEP_ELECTRO ;s = electro ;if (s > bestscore){bestscore = s;bestweapon = w;} if (self.switchweapon == w) currentscore = s;
- w = WEP_CRYLINK ;s = crylink ;if (s > bestscore){bestscore = s;bestweapon = w;} if (self.switchweapon == w) currentscore = s;
- w = WEP_UZI ;s = uzi ;if (s > bestscore){bestscore = s;bestweapon = w;} if (self.switchweapon == w) currentscore = s;
- w = WEP_SHOTGUN ;s = shotgun ;if (s > bestscore){bestscore = s;bestweapon = w;} if (self.switchweapon == w) currentscore = s;
- w = WEP_LASER ;s = laser ;if (s > bestscore){bestscore = s;bestweapon = w;} if (self.switchweapon == w) currentscore = s;
-
- // switch if the best weapon would provide a significant damage increase
- if (bestscore > currentscore*1.5){
- self.switchweapon = bestweapon;
-
- // buys time for detonating the rocket. not tested yet
- if ( cvar("bot_ai_weapon_combo") && bestweapon == WEP_ROCKET_LAUNCHER )
- self.bot_chooseweapontime += (distance / cvar("g_balance_rocketlauncher_speed"));
- }
-#endif
-};
-
-.float nextaim;
-void havocbot_aim()
-{
- local 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.origin, enemyvel);
- }
- else
- lag_additem(time + self.ping, 0, 0, world, self.origin, selfvel, self.goalcurrent.origin, '0 0 0');
-};
-
-.entity draggedby;
-void havocbot_ai()
-{
- 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
- {
- 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
- local entity newgoal, head;
- local float bestdistance, distance;
-
- newgoal = world;
- bestdistance = 10000;
- for (head = findchain(classname, "waypoint"); head; head = head.chain)
- {
- distance = vlen(head.origin - self.origin);
- if(distance>10000)
- continue;
-
- if(head.origin_z < self.origin_z)
- continue;
-
- if(head.origin_z - self.origin_z - self.view_ofs_z > 100)
- continue;
-
- if (pointcontents(head.origin + head.maxs + '0 0 1') != CONTENT_EMPTY)
- continue;
-
- traceline(self.origin + self.view_ofs , head.origin, TRUE, head);
-
- if(trace_fraction<1)
- continue;
-
- if(distance<bestdistance)
- {
- newgoal = head;
- bestdistance = distance;
- }
- }
-
- if(newgoal)
- {
- // te_wizspike(newgoal.origin);
- navigation_pushroute(newgoal);
- }
- }
-
- // token has been used this frame
- bot_strategytoken_taken = TRUE;
- }
-
- if(self.deadflag != DEAD_NO)
- return;
-
- havocbot_chooseenemy();
- if (self.bot_chooseweapontime < time )
- {
- self.bot_chooseweapontime = time + cvar("bot_ai_chooseweaponinterval");
- havocbot_chooseweapon();
- }
- havocbot_aim();
- lag_update();
- if (self.bot_aimtarg)
- {
- self.aistatus |= AI_STATUS_ATTACKING;
- self.aistatus &~= AI_STATUS_ROAMING;
-
- if(self.weapons)
- {
- weapon_action(self.weapon, WR_AIM);
- if (cvar("bot_nofire") || IS_INDEPENDENT_PLAYER(self))
- {
- self.BUTTON_ATCK = FALSE;
- self.BUTTON_ATCK2 = FALSE;
- }
- else
- {
- if(self.BUTTON_ATCK||self.BUTTON_ATCK2)
- self.lastfiredweapon = self.weapon;
- }
- }
- else
- {
- if(self.bot_aimtarg.classname=="player")
- bot_aimdir(self.bot_aimtarg.origin + self.bot_aimtarg.view_ofs - self.origin - self.view_ofs , -1);
- }
- }
- else if (self.goalcurrent)
- {
- self.aistatus |= AI_STATUS_ROAMING;
- self.aistatus &~= AI_STATUS_ATTACKING;
-
- local vector now,v,next;//,heading;
- local float aimdistance,skillblend,distanceblend,blend;
- next = now = self.goalcurrent.origin - (self.origin + self.view_ofs);
- aimdistance = vlen(now);
- //heading = self.velocity;
- //dprint(self.goalstack01.classname,etos(self.goalstack01),"\n");
- if(
- self.goalstack01 != self && self.goalstack01 != world && self.aistatus & AI_STATUS_RUNNING == 0 &&
- !(self.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT)
- )
- next = self.goalstack01.origin - (self.origin + self.view_ofs);
-
- skillblend=bound(0,(skill-2.5)*0.5,1); //lower skill player can't preturn
- distanceblend=bound(0,aimdistance/cvar("bot_ai_keyboard_distance"),1);
- blend = skillblend * (1-distanceblend);
- //v = (now * (distanceblend) + next * (1-distanceblend)) * (skillblend) + now * (1-skillblend);
- //v = now * (distanceblend) * (skillblend) + next * (1-distanceblend) * (skillblend) + now * (1-skillblend);
- //v = now * ((1-skillblend) + (distanceblend) * (skillblend)) + next * (1-distanceblend) * (skillblend);
- v = now + blend * (next - now);
- //dprint(etos(self), " ");
- //dprint(vtos(now), ":", vtos(next), "=", vtos(v), " (blend ", ftos(blend), ")\n");
- //v = now * (distanceblend) + next * (1-distanceblend);
- if (self.waterlevel < WATERLEVEL_SWIMMING)
- v_z = 0;
- //dprint("walk at:", vtos(v), "\n");
- //te_lightning2(world, self.origin, self.goalcurrent.origin);
- bot_aimdir(v, -1);
- }
- havocbot_movetogoal();
-};
-
-.entity havocbot_personal_waypoint;
-.float havocbot_personal_waypoint_searchtime;
-float havocbot_moveto_refresh_route()
-{
- // 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_personal_waypoint_failcounter;
-float havocbot_moveto(vector pos)
-{
- local entity wp;
-
- if(self.aistatus & AI_STATUS_WAYPOINT_PERSONAL_GOING)
- {
- // Step 4: Move to waypoint
- if(self.havocbot_personal_waypoint==world)
- {
- dprint("Error: ", self.netname, " trying to walk to a non existent personal waypoint\n");
- self.aistatus &~= AI_STATUS_WAYPOINT_PERSONAL_GOING;
- return CMD_STATUS_ERROR;
- }
-
- if (!bot_strategytoken_taken)
- if(self.havocbot_personal_waypoint_searchtime<time)
- {
- bot_strategytoken_taken = TRUE;
- if(havocbot_moveto_refresh_route())
- {
- dprint(self.netname, " walking to its personal waypoint (after ", ftos(self.havocbot_personal_waypoint_failcounter), " failed attempts)\n");
- self.havocbot_personal_waypoint_searchtime = time + 10;
- self.havocbot_personal_waypoint_failcounter = 0;
- }
- else
- {
- self.havocbot_personal_waypoint_failcounter += 1;
- self.havocbot_personal_waypoint_searchtime = time + 2;
- if(self.havocbot_personal_waypoint_failcounter >= 30)
- {
- dprint("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
- dprint(self.netname, " can't walk to its personal waypoint (after ", ftos(self.havocbot_personal_waypoint_failcounter), " failed attempts), trying later\n");
- }
- }
-
- #ifdef DEBUG_BOT_GOALSTACK
- debuggoalstack();
- #endif
-
- // Heading
- local vector dir = self.goalcurrent.origin - (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
- dprint(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)
- {
- dprint(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
- dprint(self.netname, " walking to its personal waypoint\n");
-
- return CMD_STATUS_EXECUTING;
- }
-
- // Step 1: Spawning waypoint
- wp = waypoint_spawnpersonal(pos);
- if(wp==world)
- {
- dprint("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;
-
- return CMD_STATUS_EXECUTING;
-}
-
-float havocbot_resetgoal()
-{
- navigation_clearroute();
- return CMD_STATUS_FINISHED;
-}
-
-void havocbot_setupbot()
-{
- self.bot_ai = havocbot_ai;
- self.cmd_moveto = havocbot_moveto;
- self.cmd_resetgoal = havocbot_resetgoal;
-
- // will be updated by think code
- //Generate some random skill levels
- self.havocbot_keyboardskill=random()-0.5;
- havocbot_chooserole();
-}
-
-
-#ifdef DEBUG_BOT_GOALSTACK
-
-.float goalcounter;
-.vector lastposition;
-
-// Debug the goal stack visually
-void debuggoalstack()
-{
- local entity target;
- local vector org;
-
- if(self.goalcounter==0)target=self.goalcurrent;
- else if(self.goalcounter==1)target=self.goalstack01;
- else if(self.goalcounter==2)target=self.goalstack02;
- else if(self.goalcounter==3)target=self.goalstack03;
- else if(self.goalcounter==4)target=self.goalstack04;
- else if(self.goalcounter==5)target=self.goalstack05;
- else if(self.goalcounter==6)target=self.goalstack06;
- else if(self.goalcounter==7)target=self.goalstack07;
- else if(self.goalcounter==8)target=self.goalstack08;
- else if(self.goalcounter==9)target=self.goalstack09;
- else if(self.goalcounter==10)target=self.goalstack10;
- else if(self.goalcounter==11)target=self.goalstack11;
- else if(self.goalcounter==12)target=self.goalstack12;
- else if(self.goalcounter==13)target=self.goalstack13;
- else if(self.goalcounter==14)target=self.goalstack14;
- else if(self.goalcounter==15)target=self.goalstack15;
- else if(self.goalcounter==16)target=self.goalstack16;
- else if(self.goalcounter==17)target=self.goalstack17;
- else if(self.goalcounter==18)target=self.goalstack18;
- else if(self.goalcounter==19)target=self.goalstack19;
- else if(self.goalcounter==20)target=self.goalstack20;
- else if(self.goalcounter==21)target=self.goalstack21;
- else if(self.goalcounter==22)target=self.goalstack22;
- else if(self.goalcounter==23)target=self.goalstack23;
- else if(self.goalcounter==24)target=self.goalstack24;
- else if(self.goalcounter==25)target=self.goalstack25;
- else if(self.goalcounter==26)target=self.goalstack26;
- else if(self.goalcounter==27)target=self.goalstack27;
- else if(self.goalcounter==28)target=self.goalstack28;
- else if(self.goalcounter==29)target=self.goalstack29;
- else if(self.goalcounter==30)target=self.goalstack30;
- else if(self.goalcounter==31)target=self.goalstack31;
-
- if(target==world)
- {
- self.goalcounter = 0;
- self.lastposition='0 0 0';
- return;
- }
-
- if(self.lastposition=='0 0 0')
- org = self.origin;
- else
- org = self.lastposition;
-
-
- te_lightning2(world, org, target.origin);
- self.lastposition = target.origin;
-
- self.goalcounter++;
-}
-
-#endif
Deleted: trunk/data/qcsrc/server/havocbot_ctf.qc
===================================================================
--- trunk/data/qcsrc/server/havocbot_ctf.qc 2009-09-20 17:48:33 UTC (rev 7850)
+++ trunk/data/qcsrc/server/havocbot_ctf.qc 2009-09-20 21:43:29 UTC (rev 7851)
@@ -1,680 +0,0 @@
-#define HAVOCBOT_CTF_ROLE_NONE 0
-#define HAVOCBOT_CTF_ROLE_DEFENSE 2
-#define HAVOCBOT_CTF_ROLE_MIDDLE 4
-#define HAVOCBOT_CTF_ROLE_OFFENSE 8
-#define HAVOCBOT_CTF_ROLE_CARRIER 16
-#define HAVOCBOT_CTF_ROLE_RETRIEVER 32
-#define HAVOCBOT_CTF_ROLE_ESCORT 64
-
-.void() havocbot_role;
-.void() havocbot_previous_role;
-
-void() havocbot_role_ctf_middle;
-void() havocbot_role_ctf_defense;
-void() havocbot_role_ctf_offense;
-void() havocbot_role_ctf_carrier;
-void() havocbot_role_ctf_retriever;
-void() havocbot_role_ctf_escort;
-
-void(entity bot) havocbot_ctf_reset_role;
-void(float ratingscale, vector org, float sradius) havocbot_goalrating_items;
-void(float ratingscale, vector org, float sradius) havocbot_goalrating_enemyplayers;
-
-.float havocbot_cantfindflag;
-.float havocbot_role_timeout;
-.entity ctf_worldflagnext;
-.entity basewaypoint;
-
-vector havocbot_ctf_middlepoint;
-float havocbot_ctf_middlepoint_radius;
-entity ctf_worldflaglist;
-
-entity havocbot_ctf_find_flag(entity bot)
-{
- entity f;
- f = ctf_worldflaglist;
- while (f)
- {
- if (bot.team == f.team)
- return f;
- f = f.ctf_worldflagnext;
- }
- return world;
-};
-
-entity havocbot_ctf_find_enemy_flag(entity bot)
-{
- entity f;
- f = ctf_worldflaglist;
- while (f)
- {
- if (bot.team != f.team)
- return f;
- f = f.ctf_worldflagnext;
- }
- return world;
-};
-
-float havocbot_ctf_teamcount(entity bot, vector org, float radius)
-{
- if not(teams_matter)
- return 0;
-
- float c;
- entity head;
-
- FOR_EACH_PLAYER(head)
- {
- if(head.team!=bot.team || head.deadflag != DEAD_NO || head == bot)
- continue;
-
- if(vlen(head.origin - org) < radius)
- ++c;
- }
-
- return c;
-};
-
-void havocbot_goalrating_ctf_ourflag(float ratingscale)
-{
- local entity head;
- head = ctf_worldflaglist;
- while (head)
- {
- if (self.team == head.team)
- break;
- head = head.ctf_worldflagnext;
- }
- if (head)
- navigation_routerating(head, ratingscale, 10000);
-};
-
-void havocbot_goalrating_ctf_ourbase(float ratingscale)
-{
- local entity head;
- head = ctf_worldflaglist;
- while (head)
- {
- if (self.team == head.team)
- break;
- head = head.ctf_worldflagnext;
- }
- if not(head)
- return;
-
- navigation_routerating(head.basewaypoint, ratingscale, 10000);
-};
-
-void havocbot_goalrating_ctf_enemyflag(float ratingscale)
-{
- local entity head;
- head = ctf_worldflaglist;
- while (head)
- {
- if (self.team != head.team)
- break;
- head = head.ctf_worldflagnext;
- }
- if (head)
- navigation_routerating(head, ratingscale, 10000);
-};
-
-void havocbot_goalrating_ctf_enemybase(float ratingscale)
-{
- if not(bot_waypoints_for_items)
- {
- havocbot_goalrating_ctf_enemyflag(ratingscale);
- return;
- }
-
- local entity head;
-
- head = havocbot_ctf_find_enemy_flag(self);
-
- if not(head)
- return;
-
- navigation_routerating(head.basewaypoint, ratingscale, 10000);
-};
-
-void havocbot_goalrating_ctf_ourstolenflag(float ratingscale)
-{
- local entity mf;
-
- mf = havocbot_ctf_find_flag(self);
-
- if(mf.cnt == FLAG_BASE)
- return;
-
- navigation_routerating(mf, ratingscale, 10000);
-};
-
-void havocbot_goalrating_ctf_droppedflags(float ratingscale, vector org, float radius)
-{
- local entity head;
- head = ctf_worldflaglist;
- while (head)
- {
- // flag is out in the field
- if(head.cnt != FLAG_BASE)
- if(head.tag_entity==world) // dropped
- {
- if(radius)
- {
- if(vlen(org-head.origin)<radius)
- navigation_routerating(head, ratingscale, 10000);
- }
- else
- navigation_routerating(head, ratingscale, 10000);
- }
-
- head = head.ctf_worldflagnext;
- }
-};
-
-void havocbot_goalrating_ctf_carrieritems(float ratingscale, vector org, float sradius)
-{
- local entity head;
- local float t;
- head = findchainfloat(bot_pickup, TRUE);
- while (head)
- {
- // gather health and armor only
- if (head.solid)
- if (head.health || head.armorvalue)
- if (vlen(head.origin - org) < sradius)
- {
- // get the value of the item
- t = head.bot_pickupevalfunc(self, head) * 0.0001;
- if (t > 0)
- navigation_routerating(head, t * ratingscale, 500);
- }
- head = head.chain;
- }
-};
-
-void havocbot_role_ctf_setrole(entity bot, float role)
-{
- dprint(strcat(bot.netname," switched to "));
- switch(role)
- {
- case HAVOCBOT_CTF_ROLE_CARRIER:
- dprint("carrier");
- bot.havocbot_role = havocbot_role_ctf_carrier;
- bot.havocbot_role_timeout = 0;
- bot.havocbot_cantfindflag = time + 10;
- break;
- case HAVOCBOT_CTF_ROLE_DEFENSE:
- dprint("defense");
- bot.havocbot_role = havocbot_role_ctf_defense;
- bot.havocbot_role_timeout = 0;
- break;
- case HAVOCBOT_CTF_ROLE_MIDDLE:
- dprint("middle");
- bot.havocbot_role = havocbot_role_ctf_middle;
- bot.havocbot_role_timeout = 0;
- break;
- case HAVOCBOT_CTF_ROLE_OFFENSE:
- dprint("offense");
- bot.havocbot_role = havocbot_role_ctf_offense;
- bot.havocbot_role_timeout = 0;
- break;
- case HAVOCBOT_CTF_ROLE_RETRIEVER:
- dprint("retriever");
- bot.havocbot_previous_role = bot.havocbot_role;
- bot.havocbot_role = havocbot_role_ctf_retriever;
- bot.havocbot_role_timeout = time + 10;
- break;
- case HAVOCBOT_CTF_ROLE_ESCORT:
- dprint("escort");
- bot.havocbot_previous_role = bot.havocbot_role;
- bot.havocbot_role = havocbot_role_ctf_escort;
- bot.havocbot_role_timeout = time + 30;
- break;
- }
- dprint("\n");
-};
-
-void havocbot_role_ctf_carrier()
-{
- if(self.deadflag != DEAD_NO)
- {
- havocbot_ctf_reset_role(self);
- return;
- }
-
- if (self.flagcarried == world)
- {
- havocbot_ctf_reset_role(self);
- return;
- }
-
- if (self.bot_strategytime < time)
- {
- self.bot_strategytime = time + cvar("bot_ai_strategyinterval");
-
- navigation_goalrating_start();
- havocbot_goalrating_ctf_ourbase(50000);
-
- if(self.health<100)
- havocbot_goalrating_ctf_carrieritems(1000, self.origin, 1000);
-
- navigation_goalrating_end();
-
- if (self.navigation_hasgoals)
- self.havocbot_cantfindflag = time + 10;
- else if (time > self.havocbot_cantfindflag)
- {
- // Can't navigate to my own base, suicide!
- // TODO: drop it and wander around
- Damage(self, self, self, 100000, DEATH_KILL, self.origin, '0 0 0');
- return;
- }
- }
-};
-
-void havocbot_role_ctf_escort()
-{
- local entity mf, ef;
-
- if(self.deadflag != DEAD_NO)
- {
- havocbot_ctf_reset_role(self);
- return;
- }
-
- if (self.flagcarried)
- {
- havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
- return;
- }
-
- // If enemy flag is back on the base switch to previous role
- ef = havocbot_ctf_find_enemy_flag(self);
- if(ef.cnt==FLAG_BASE)
- {
- self.havocbot_role = self.havocbot_previous_role;
- self.havocbot_role_timeout = 0;
- return;
- }
-
- // If the flag carrier reached the base switch to defense
- mf = havocbot_ctf_find_flag(self);
- if(mf.cnt!=FLAG_BASE)
- if(vlen(ef.origin - mf.dropped_origin) < 300)
- {
- havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_DEFENSE);
- return;
- }
-
- // Set the role timeout if necessary
- if (!self.havocbot_role_timeout)
- {
- self.havocbot_role_timeout = time + random() * 30 + 60;
- }
-
- // If nothing happened just switch to previous role
- if (time > self.havocbot_role_timeout)
- {
- self.havocbot_role = self.havocbot_previous_role;
- self.havocbot_role_timeout = 0;
- return;
- }
-
- // Chase the flag carrier
- if (self.bot_strategytime < time)
- {
- self.bot_strategytime = time + cvar("bot_ai_strategyinterval");
- navigation_goalrating_start();
- havocbot_goalrating_ctf_enemyflag(30000);
- havocbot_goalrating_ctf_ourstolenflag(40000);
- havocbot_goalrating_items(10000, self.origin, 10000);
- navigation_goalrating_end();
- }
-};
-
-void havocbot_role_ctf_offense()
-{
- local entity mf, ef;
- local vector pos;
-
- if(self.deadflag != DEAD_NO)
- {
- havocbot_ctf_reset_role(self);
- return;
- }
-
- if (self.flagcarried)
- {
- havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
- return;
- }
-
- // Check flags
- mf = havocbot_ctf_find_flag(self);
- ef = havocbot_ctf_find_enemy_flag(self);
-
- // Own flag stolen
- if(mf.cnt!=FLAG_BASE)
- {
- if(mf.tag_entity)
- pos = mf.tag_entity.origin;
- else
- pos = mf.origin;
-
- // Try to get it if closer than the enemy base
- if(vlen(self.origin-ef.dropped_origin)>vlen(self.origin-pos))
- {
- havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
- return;
- }
- }
-
- // Escort flag carrier
- if(ef.cnt!=FLAG_BASE)
- {
- if(ef.tag_entity)
- pos = ef.tag_entity.origin;
- else
- pos = ef.origin;
-
- if(vlen(pos-mf.dropped_origin)>700)
- {
- havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_ESCORT);
- return;
- }
- }
-
- // About to fail, switch to middlefield
- if(self.health<50)
- {
- havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_MIDDLE);
- return;
- }
-
- // Set the role timeout if necessary
- if (!self.havocbot_role_timeout)
- self.havocbot_role_timeout = time + 120;
-
- if (time > self.havocbot_role_timeout)
- {
- havocbot_ctf_reset_role(self);
- return;
- }
-
- if (self.bot_strategytime < time)
- {
- self.bot_strategytime = time + cvar("bot_ai_strategyinterval");
- navigation_goalrating_start();
- havocbot_goalrating_ctf_ourstolenflag(50000);
- havocbot_goalrating_ctf_enemybase(20000);
- havocbot_goalrating_items(5000, self.origin, 1000);
- havocbot_goalrating_items(1000, self.origin, 10000);
- navigation_goalrating_end();
- }
-};
-
-// Retriever (temporary role):
-void havocbot_role_ctf_retriever()
-{
- local entity mf;
-
- if(self.deadflag != DEAD_NO)
- {
- havocbot_ctf_reset_role(self);
- return;
- }
-
- if (self.flagcarried)
- {
- havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
- return;
- }
-
- // If flag is back on the base switch to previous role
- mf = havocbot_ctf_find_flag(self);
- if(mf.cnt==FLAG_BASE)
- {
- havocbot_ctf_reset_role(self);
- return;
- }
-
- if (!self.havocbot_role_timeout)
- self.havocbot_role_timeout = time + 20;
-
- if (time > self.havocbot_role_timeout)
- {
- havocbot_ctf_reset_role(self);
- return;
- }
-
- if (self.bot_strategytime < time)
- {
- local float radius;
- radius = 10000;
-
- self.bot_strategytime = time + cvar("bot_ai_strategyinterval");
- navigation_goalrating_start();
- havocbot_goalrating_ctf_ourstolenflag(50000);
- havocbot_goalrating_ctf_droppedflags(40000, self.origin, radius);
- havocbot_goalrating_ctf_enemybase(30000);
- havocbot_goalrating_items(500, self.origin, radius);
- navigation_goalrating_end();
- }
-};
-
-void havocbot_role_ctf_middle()
-{
- local entity mf;
-
- if(self.deadflag != DEAD_NO)
- {
- havocbot_ctf_reset_role(self);
- return;
- }
-
- if (self.flagcarried)
- {
- havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
- return;
- }
-
- mf = havocbot_ctf_find_flag(self);
- if(mf.cnt!=FLAG_BASE)
- {
- havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
- return;
- }
-
- if (!self.havocbot_role_timeout)
- self.havocbot_role_timeout = time + 10;
-
- if (time > self.havocbot_role_timeout)
- {
- havocbot_ctf_reset_role(self);
- return;
- }
-
- if (self.bot_strategytime < time)
- {
- local vector org;
-
- org = havocbot_ctf_middlepoint;
- org_z = self.origin_z;
-
- self.bot_strategytime = time + cvar("bot_ai_strategyinterval");
- navigation_goalrating_start();
- havocbot_goalrating_ctf_ourstolenflag(50000);
- havocbot_goalrating_ctf_droppedflags(30000, self.origin, 10000);
- havocbot_goalrating_enemyplayers(10000, org, havocbot_ctf_middlepoint_radius * 0.5);
- havocbot_goalrating_items(5000, org, havocbot_ctf_middlepoint_radius * 0.5);
- havocbot_goalrating_items(2500, self.origin, 10000);
- havocbot_goalrating_ctf_enemybase(2500);
- navigation_goalrating_end();
- }
-};
-
-void havocbot_role_ctf_defense()
-{
- local entity mf;
-
- if(self.deadflag != DEAD_NO)
- {
- havocbot_ctf_reset_role(self);
- return;
- }
-
- if (self.flagcarried)
- {
- havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
- return;
- }
-
- // If own flag was captured
- mf = havocbot_ctf_find_flag(self);
- if(mf.cnt!=FLAG_BASE)
- {
- havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
- return;
- }
-
- if (!self.havocbot_role_timeout)
- self.havocbot_role_timeout = time + 30;
-
- if (time > self.havocbot_role_timeout)
- {
- havocbot_ctf_reset_role(self);
- return;
- }
- if (self.bot_strategytime < time)
- {
- local float radius;
- local vector org;
-
- org = mf.dropped_origin;
- radius = havocbot_ctf_middlepoint_radius;
-
- self.bot_strategytime = time + cvar("bot_ai_strategyinterval");
- navigation_goalrating_start();
-
- // if enemies are closer to our base, go there
- local entity head, closestplayer;
- local float distance, bestdistance;
- distance = 10000;
- FOR_EACH_PLAYER(head)
- {
- if(head.deadflag!=DEAD_NO)
- continue;
-
- distance = vlen(org - head.origin);
- if(distance<bestdistance)
- {
- closestplayer = head;
- bestdistance = distance;
- }
- }
-
- if(closestplayer)
- if(closestplayer.team!=self.team)
- if(vlen(org - self.origin)>1000)
- if(checkpvs(self.origin,closestplayer)||random()<0.5)
- havocbot_goalrating_ctf_ourbase(30000);
-
- havocbot_goalrating_ctf_ourstolenflag(20000);
- havocbot_goalrating_ctf_droppedflags(20000, org, radius);
- havocbot_goalrating_enemyplayers(15000, org, radius);
- havocbot_goalrating_items(10000, org, radius);
- havocbot_goalrating_items(5000, self.origin, 10000);
- navigation_goalrating_end();
- }
-};
-
-void havocbot_calculate_middlepoint()
-{
- entity f;
- vector p1, p2;
-
- f = ctf_worldflaglist;
- while (f)
- {
- if(p1)
- p2 = f.origin;
- else
- p1 = f.origin;
-
- f = f.ctf_worldflagnext;
- }
- havocbot_ctf_middlepoint = p1 + ((p2-p1) * 0.5);
- havocbot_ctf_middlepoint_radius = vlen(p2-p1) * 0.5;
-};
-
-void havocbot_ctf_reset_role(entity bot)
-{
- local float cdefense, cmiddle, coffense;
- local entity mf, ef, head;
- local float c;
-
- if(bot.deadflag != DEAD_NO)
- return;
-
- if(vlen(havocbot_ctf_middlepoint)==0)
- havocbot_calculate_middlepoint();
-
- // Check ctf flags
- if (bot.flagcarried)
- {
- havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_CARRIER);
- return;
- }
-
- mf = havocbot_ctf_find_flag(bot);
- ef = havocbot_ctf_find_enemy_flag(bot);
-
- // Retrieve stolen flag
- if(mf.cnt!=FLAG_BASE)
- {
- havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_RETRIEVER);
- return;
- }
-
- // If enemy flag is taken go to the middle to intercept pursuers
- if(ef.cnt!=FLAG_BASE)
- {
- havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
- return;
- }
-
- // if there is only me on the team switch to offense
- c = 0;
- FOR_EACH_PLAYER(head)
- if(head.team==bot.team)
- ++c;
-
- if(c==1)
- {
- havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
- return;
- }
-
- // Evaluate best position to take
- // Count mates on middle position
- cmiddle = havocbot_ctf_teamcount(bot, havocbot_ctf_middlepoint, havocbot_ctf_middlepoint_radius * 0.5);
-
- // Count mates on defense position
- cdefense = havocbot_ctf_teamcount(bot, mf.dropped_origin, havocbot_ctf_middlepoint_radius * 0.5);
-
- // Count mates on offense position
- coffense = havocbot_ctf_teamcount(bot, ef.dropped_origin, havocbot_ctf_middlepoint_radius);
-
- if(cdefense<=coffense)
- havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_DEFENSE);
- else if(coffense<=cmiddle)
- havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
- else
- havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
-};
-
-void havocbot_chooserole_ctf()
-{
- havocbot_ctf_reset_role(self);
-};
Deleted: trunk/data/qcsrc/server/havocbot_ons.qc
===================================================================
--- trunk/data/qcsrc/server/havocbot_ons.qc 2009-09-20 17:48:33 UTC (rev 7850)
+++ trunk/data/qcsrc/server/havocbot_ons.qc 2009-09-20 21:43:29 UTC (rev 7851)
@@ -1,366 +0,0 @@
-#define HAVOCBOT_ONS_ROLE_NONE 0
-#define HAVOCBOT_ONS_ROLE_DEFENSE 2
-#define HAVOCBOT_ONS_ROLE_ASSISTANT 4
-#define HAVOCBOT_ONS_ROLE_OFFENSE 8
-
-.float havocbot_role_flags;
-.float havocbot_attack_time;
-
-.void() havocbot_role;
-.void() havocbot_previous_role;
-
-void() havocbot_role_ons_defense;
-void() havocbot_role_ons_offense;
-void() havocbot_role_ons_assistant;
-
-void(entity bot) havocbot_ons_reset_role;
-void(float ratingscale, vector org, float sradius) havocbot_goalrating_items;
-void(float ratingscale, vector org, float sradius) havocbot_goalrating_enemyplayers;
-
-.float isshielded;
-.float iscaptured;
-.float islinked;
-.float isgenneighbor_blue, iscpneighbor_blue;
-.float isgenneighbor_red, iscpneighbor_red;
-
-.entity havocbot_ons_target;
-
-void havocbot_goalrating_ons_offenseitems(float ratingscale, vector org, float sradius)
-{
- local entity head;
- local float t, i, c, needarmor, needweapons;
-
- // Needs armor/health?
- if(self.health<100)
- needarmor = TRUE;
-
- // Needs weapons?
- for(i = WEP_FIRST; i < WEP_LAST ; ++i)
- {
- // Find weapon
- if((get_weaponinfo(i)).weapons & self.weapons)
- if(++c>=4)
- break;
- }
-
- if(c<4)
- needweapons = TRUE;
-
- if(!needweapons && !needarmor)
- return;
-
-// dprint(self.netname, " needs weapons ", ftos(needweapons) , "\n");
-// dprint(self.netname, " needs armor ", ftos(needarmor) , "\n");
-
- // See what is around
- head = findchainfloat(bot_pickup, TRUE);
- while (head)
- {
- // gather health and armor only
- if (head.solid)
- if ( ((head.health || head.armorvalue) && needarmor) || (head.weapons && needweapons ) )
- if (vlen(head.origin - org) < sradius)
- {
- t = head.bot_pickupevalfunc(self, head);
- if (t > 0)
- navigation_routerating(head, t * ratingscale, 500);
- }
- head = head.chain;
- }
-};
-
-void havocbot_role_ons_setrole(entity bot, float role)
-{
- dprint(strcat(bot.netname," switched to "));
- switch(role)
- {
- case HAVOCBOT_ONS_ROLE_DEFENSE:
- dprint("defense");
- bot.havocbot_role = havocbot_role_ons_defense;
- bot.havocbot_role_flags = HAVOCBOT_ONS_ROLE_DEFENSE;
- bot.havocbot_role_timeout = 0;
- break;
- case HAVOCBOT_ONS_ROLE_ASSISTANT:
- dprint("assistant");
- bot.havocbot_role = havocbot_role_ons_assistant;
- bot.havocbot_role_flags = HAVOCBOT_ONS_ROLE_ASSISTANT;
- bot.havocbot_role_timeout = 0;
- break;
- case HAVOCBOT_ONS_ROLE_OFFENSE:
- dprint("offense");
- bot.havocbot_role = havocbot_role_ons_offense;
- bot.havocbot_role_flags = HAVOCBOT_ONS_ROLE_OFFENSE;
- bot.havocbot_role_timeout = 0;
- break;
- }
- dprint("\n");
-};
-
-float havocbot_ons_teamcount(entity bot, float role)
-{
- local float c;
- local entity head;
-
- FOR_EACH_PLAYER(head)
- if(head.team==self.team)
- if(head.havocbot_role_flags & role)
- ++c;
-
- return c;
-};
-
-void havocbot_goalrating_ons_controlpoints_attack(float ratingscale)
-{
- entity cp, cp1, cp2, best, pl, wp;
- float radius, found, bestvalue, c;
-
- cp1 = cp2 = findchain(classname, "onslaught_controlpoint");
-
- // Filter control points
- for (; cp2; cp2 = cp2.chain)
- {
- cp2.wpcost = c = 0;
- cp2.wpconsidered = FALSE;
-
- if(cp2.isshielded)
- continue;
-
- // Ignore owned controlpoints
- if(self.team == COLOR_TEAM1)
- {
- if( (cp2.isgenneighbor_blue || cp2.iscpneighbor_blue) && !(cp2.isgenneighbor_red || cp2.iscpneighbor_red) )
- continue;
- }
- else if(self.team == COLOR_TEAM2)
- {
- if( (cp2.isgenneighbor_red || cp2.iscpneighbor_red) && !(cp2.isgenneighbor_blue || cp2.iscpneighbor_blue) )
- continue;
- }
-
- // Count team mates interested in this control point
- // (easier and cleaner than keeping counters per cp and teams)
- FOR_EACH_PLAYER(pl)
- if(pl.team==self.team)
- if(pl.havocbot_role_flags & HAVOCBOT_ONS_ROLE_OFFENSE)
- if(pl.havocbot_ons_target==cp2)
- ++c;
-
- // NOTE: probably decrease the cost of attackable control points
- cp2.wpcost = c;
- cp2.wpconsidered = TRUE;
- }
-
- // We'll consider only the best case
- bestvalue = 99999999999;
- for (; cp1; cp1 = cp1.chain)
- {
- if not(cp1.wpconsidered)
- continue;
-
- if(cp1.wpcost<bestvalue)
- {
- bestvalue = cp1.wpcost;
- cp = cp1;
- self.havocbot_ons_target = cp1;
- }
- }
-
- if not(cp)
- return;
-
-// dprint(self.netname, " chose cp ranked ", ftos(bestvalue), "\n");
-
- if(cp.goalentity)
- {
- // Should be attacked
- // Rate waypoints near it
- found = FALSE;
- best = world;
- bestvalue = 99999999999;
- for(radius=0; radius<1000 && !found; radius+=500)
- {
- for(wp=findradius(cp.origin,radius); wp; wp=wp.chain)
- {
- if(!(wp.wpflags & WAYPOINTFLAG_GENERATED))
- if(wp.classname=="waypoint")
- if(checkpvs(wp.origin,cp))
- {
- found = TRUE;
- if(wp.cnt<bestvalue)
- {
- best = wp;
- bestvalue = wp.cnt;
- }
- }
- }
- }
-
- if(best)
- {
- navigation_routerating(best, ratingscale, 10000);
- best.cnt += 1;
-
- self.havocbot_attack_time = 0;
- if(checkpvs(self.view_ofs,cp))
- if(checkpvs(self.view_ofs,best))
- self.havocbot_attack_time = time + 2;
- }
- else
- {
- navigation_routerating(cp, ratingscale, 10000);
- }
- // dprint(self.netname, " found an attackable controlpoint at ", vtos(cp.origin) ,"\n");
- }
- else
- {
- // Should be touched
- // dprint(self.netname, " found a touchable controlpoint at ", vtos(cp.origin) ,"\n");
-
- // Look for auto generated waypoint
- if not(bot_waypoints_for_items)
- for (wp = findradius(cp.origin,100); wp; wp = wp.chain)
- {
- if(wp.classname=="waypoint")
- {
- navigation_routerating(wp, ratingscale, 10000);
- found = TRUE;
- }
- }
-
- // Nothing found, rate the controlpoint itself
- if not(found)
- navigation_routerating(cp, ratingscale, 10000);
- }
-};
-
-float havocbot_goalrating_ons_generator_attack(float ratingscale)
-{
- local entity g, wp, bestwp;
- local float found, best;
-
- for (g = findchain(classname, "onslaught_generator"); g; g = g.chain)
- {
- if(g.team == self.team || g.isshielded)
- continue;
-
- // Should be attacked
- // Rate waypoints near it
- found = FALSE;
- bestwp = world;
- best = 99999999999;
-
- for(wp=findradius(g.origin,400); wp; wp=wp.chain)
- {
- if(wp.classname=="waypoint")
- if(checkpvs(wp.origin,g))
- {
- found = TRUE;
- if(wp.cnt<best)
- {
- bestwp = wp;
- best = wp.cnt;
- }
- }
- }
-
- if(bestwp)
- {
- // dprint("waypoints found around generator\n");
- navigation_routerating(bestwp, ratingscale, 10000);
- bestwp.cnt += 1;
-
- self.havocbot_attack_time = 0;
- if(checkpvs(self.view_ofs,g))
- if(checkpvs(self.view_ofs,bestwp))
- self.havocbot_attack_time = time + 5;
-
- return TRUE;
- }
- else
- {
- // dprint("generator found without waypoints around\n");
- // if there aren't waypoints near the generator go straight to it
- navigation_routerating(g, ratingscale, 10000);
- self.havocbot_attack_time = 0;
- return TRUE;
- }
- }
- return FALSE;
-};
-
-void havocbot_role_ons_offense()
-{
- if(self.deadflag != DEAD_NO)
- {
- self.havocbot_attack_time = 0;
- havocbot_ons_reset_role(self);
- return;
- }
-
- // Set the role timeout if necessary
- if (!self.havocbot_role_timeout)
- self.havocbot_role_timeout = time + 120;
-
- if (time > self.havocbot_role_timeout)
- {
- havocbot_ons_reset_role(self);
- return;
- }
-
- if(self.havocbot_attack_time>time)
- return;
-
- if (self.bot_strategytime < time)
- {
- navigation_goalrating_start();
- havocbot_goalrating_enemyplayers(20000, self.origin, 650);
- if(!havocbot_goalrating_ons_generator_attack(20000))
- havocbot_goalrating_ons_controlpoints_attack(20000);
- havocbot_goalrating_ons_offenseitems(10000, self.origin, 10000);
- navigation_goalrating_end();
-
- self.bot_strategytime = time + cvar("bot_ai_strategyinterval");
- }
-};
-
-void havocbot_role_ons_assistant()
-{
- havocbot_ons_reset_role(self);
-};
-
-void havocbot_role_ons_defense()
-{
- havocbot_ons_reset_role(self);
-};
-
-void havocbot_ons_reset_role(entity bot)
-{
- local entity head;
- local float c;
-
- if(self.deadflag != DEAD_NO)
- return;
-
- bot.havocbot_ons_target = world;
-
- // TODO: Defend control points or generator if necessary
-
- // if there is only me on the team switch to offense
- c = 0;
- FOR_EACH_PLAYER(head)
- if(head.team==self.team)
- ++c;
-
- if(c==1)
- {
- havocbot_role_ons_setrole(bot, HAVOCBOT_ONS_ROLE_OFFENSE);
- return;
- }
-
- havocbot_role_ons_setrole(bot, HAVOCBOT_ONS_ROLE_OFFENSE);
-};
-
-void havocbot_chooserole_ons()
-{
- havocbot_ons_reset_role(self);
-};
Deleted: trunk/data/qcsrc/server/havocbot_roles.qc
===================================================================
--- trunk/data/qcsrc/server/havocbot_roles.qc 2009-09-20 17:48:33 UTC (rev 7850)
+++ trunk/data/qcsrc/server/havocbot_roles.qc 2009-09-20 21:43:29 UTC (rev 7851)
@@ -1,544 +0,0 @@
-
-.float havocbot_role_timeout;
-.void() havocbot_previous_role;
-.void() havocbot_role;
-float bot_ignore_bots;
-
-.float max_armorvalue;
-
-void havocbot_goalrating_items(float ratingscale, vector org, float sradius)
-{
- local entity head;
- local entity player;
- local float rating, d, discard, distance, friend_distance, enemy_distance;
- ratingscale = ratingscale * 0.0001; // items are rated around 10000 already
- head = findchainfloat(bot_pickup, TRUE);
-
- while (head)
- {
- distance = vlen(head.origin - 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(head.origin, head.origin + '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(teams_matter)
- {
- discard = FALSE;
-
- FOR_EACH_PLAYER(player)
- {
-
- if ( self == player || player.deadflag )
- continue;
-
- d = vlen(player.origin - head.origin); // distance between player and item
-
- if ( player.team == self.team )
- {
- if ( clienttype(player) != CLIENTTYPE_REAL || 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( (player.weapons & head.weapons) != head.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;
-
- 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 > cvar("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)
-{
- local entity head;
- head = findchain(classname, "dom_controlpoint");
- while (head)
- {
- if (vlen(head.origin - 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;
- }
-};
-
-/*
-// LordHavoc: this function was already unused, but for waypoints to be a
-// useful goal the bots would have to seek out the least-recently-visited
-// ones, not the closest
-void havocbot_goalrating_waypoints(float ratingscale, vector org, float sradius)
-{
- local entity head;
- head = findchain(classname, "waypoint");
- while (head)
- {
- if (vlen(head.origin - org) < sradius && vlen(head.origin - org) > 100)
- navigation_routerating(head, ratingscale, 2000);
- head = head.chain;
- }
-};
-*/
-
-void havocbot_goalrating_enemyplayers(float ratingscale, vector org, float sradius)
-{
- local entity head;
- local float t, noteam, distance;
- noteam = ((self.team == 0) || !teams_matter); // fteqcc sucks
-
- if (cvar("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 (self != head)
- if (head.health > 0)
- if ((noteam && (!bot_ignore_bots || clienttype(head) == CLIENTTYPE_REAL)) || head.team != self.team)
- {
- distance = vlen(head.origin - org);
- if (distance < 100 || distance > sradius)
- continue;
-
- if(g_minstagib)
- if(head.items & IT_STRENGTH)
- 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;
- }
-
- t = (self.health + self.armorvalue ) / (head.health + head.armorvalue );
- navigation_routerating(head, t * ratingscale, 2000);
- }
- }
-};
-
-// choose a role according to the situation
-void() havocbot_role_dm;
-
-//DOM:
-//go to best items, or control points you don't own
-void havocbot_role_dom()
-{
- if(self.deadflag != DEAD_NO)
- return;
-
- if (self.bot_strategytime < time)
- {
- self.bot_strategytime = time + cvar("bot_ai_strategyinterval");
- navigation_goalrating_start();
- havocbot_goalrating_controlpoints(10000, self.origin, 15000);
- havocbot_goalrating_items(8000, self.origin, 8000);
- //havocbot_goalrating_enemyplayers(3000, self.origin, 2000);
- //havocbot_goalrating_waypoints(1, self.origin, 1000);
- navigation_goalrating_end();
- }
-};
-
-//DM:
-//go to best items
-void havocbot_role_dm()
-{
- if(self.deadflag != DEAD_NO)
- return;
-
- if (self.bot_strategytime < time)
- {
- self.bot_strategytime = time + cvar("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();
- }
-};
-
-//Race:
-//go to next checkpoint, and annoy enemies
-.float race_checkpoint;
-void havocbot_role_race()
-{
- if(self.deadflag != DEAD_NO)
- return;
-
- entity e;
- if (self.bot_strategytime < time)
- {
- self.bot_strategytime = time + cvar("bot_ai_strategyinterval");
- navigation_goalrating_start();
- /*
- havocbot_goalrating_items(100, self.origin, 10000);
- havocbot_goalrating_enemyplayers(500, self.origin, 20000);
- */
-
- for(e = world; (e = find(e, classname, "trigger_race_checkpoint")) != world; )
- {
- if(e.cnt == self.race_checkpoint)
- {
- navigation_routerating(e, 1000000, 5000);
- }
- else if(self.race_checkpoint == -1)
- {
- navigation_routerating(e, 1000000, 5000);
- }
- }
-
- navigation_goalrating_end();
- }
-};
-
-void havocbot_chooserole_dm()
-{
- self.havocbot_role = havocbot_role_dm;
-};
-
-void havocbot_chooserole_race()
-{
- self.havocbot_role = havocbot_role_race;
-};
-
-void havocbot_chooserole_dom()
-{
- self.havocbot_role = havocbot_role_dom;
-};
-
-
-
-
-
-
-entity kh_worldkeylist;
-.entity kh_worldkeynext;
-void havocbot_goalrating_kh(float ratingscale_team, float ratingscale_dropped, float ratingscale_enemy)
-{
- local 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;
-void() havocbot_role_kh_defense;
-void() havocbot_role_kh_offense;
-void() havocbot_role_kh_freelancer;
-void havocbot_role_kh_carrier()
-{
- if(self.deadflag != DEAD_NO)
- return;
-
- if (!(self.kh_next))
- {
- dprint("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 + cvar("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()
-{
- if(self.deadflag != DEAD_NO)
- return;
-
- if (self.kh_next)
- {
- dprint("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)
- {
- dprint("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 + cvar("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()
-{
- if(self.deadflag != DEAD_NO)
- return;
-
- if (self.kh_next)
- {
- dprint("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)
- {
- dprint("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 + cvar("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()
-{
- if(self.deadflag != DEAD_NO)
- return;
-
- if (self.kh_next)
- {
- dprint("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)
- {
- dprint("changing role to offense\n");
- self.havocbot_role = havocbot_role_kh_offense;
- }
- else
- {
- dprint("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 + cvar("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()
-{
- local 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;
-};
-
-void havocbot_chooserole()
-{
- dprint("choosing a role...\n");
- navigation_clearroute();
- self.bot_strategytime = 0;
- if (g_ctf)
- havocbot_chooserole_ctf();
- else if (g_domination)
- havocbot_chooserole_dom();
- else if (g_keyhunt)
- havocbot_chooserole_kh();
- else if (g_race || g_cts)
- havocbot_chooserole_race();
- else if (g_onslaught)
- havocbot_chooserole_ons();
- else // assume anything else is deathmatch
- havocbot_chooserole_dm();
-};
-
Modified: trunk/data/qcsrc/server/progs.src
===================================================================
--- trunk/data/qcsrc/server/progs.src 2009-09-20 17:48:33 UTC (rev 7850)
+++ trunk/data/qcsrc/server/progs.src 2009-09-20 21:43:29 UTC (rev 7851)
@@ -46,17 +46,8 @@
waypointsprites.qc
+bot/bot.qc
-// general bot utility functions and management
-bots.qc
-bots_scripting.qc
-
-// LordHavoc's bots
-havocbot.qc
-havocbot_ctf.qc
-havocbot_ons.qc
-havocbot_roles.qc
-
g_subs.qc
g_tetris.qc
More information about the nexuiz-commits
mailing list