[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