r3426 - in trunk: data data/qcsrc/server pro

DONOTREPLY at icculus.org DONOTREPLY at icculus.org
Mon Feb 25 14:37:46 EST 2008


Author: lordhavoc
Date: 2008-02-25 14:37:46 -0500 (Mon, 25 Feb 2008)
New Revision: 3426

Modified:
   trunk/data/default.cfg
   trunk/data/qcsrc/server/bots.qc
   trunk/data/qcsrc/server/ctf.qc
   trunk/data/qcsrc/server/havocbot.qc
   trunk/data/qcsrc/server/havocbot_roles.qc
   trunk/data/qcsrc/server/keyhunt.qc
   trunk/pro/default.cfg
Log:
fixed several bugs in bots to do with movement and aiming
optimized ctf and keyhunt by keeping flaglist and kh_keylist variables
(greatly accelerated some code that used find before)
added bot_ai_thinkinterval 0.05 which means that fast servers don't
croak from bot ai
added bot_ai_dodgeupdateinterval
added bot_ai_chooseweaponinterval
added bot_ai_dangerdetectioninterval
added bot_ai_dangerdetectionupdates (how many waypoints to update danger
ratings on per interval)


Modified: trunk/data/default.cfg
===================================================================
--- trunk/data/default.cfg	2008-02-25 19:37:08 UTC (rev 3425)
+++ trunk/data/default.cfg	2008-02-25 19:37:46 UTC (rev 3426)
@@ -160,8 +160,13 @@
 seta bot_suffix ""
 seta skill_auto 0 // when 1, "skill" gets adjusted to match the best player on the map
 // general bot AI cvars
+set bot_ai_thinkinterval 0.05
 set bot_ai_strategyinterval 2
 set bot_ai_enemydetectioninterval 0.5
+set bot_ai_dodgeupdateinterval 0.1
+set bot_ai_chooseweaponinterval 0.3
+set bot_ai_dangerdetectioninterval 0.1
+set bot_ai_dangerdetectionupdates 64
 set bot_ai_aimskill_blendrate 2
 set bot_ai_aimskill_fixedrate 15
 set bot_ai_aimskill_firetolerance_distdegrees 180
@@ -169,7 +174,7 @@
 set bot_ai_aimskill_firetolerance_maxdegrees 45
 set bot_ai_aimskill_mouse 1
 set bot_ai_keyboard_distance 250
-set bot_ai_keyboard_treshold 0.94
+set bot_ai_keyboard_treshold 0.57
 set bot_ai_aimskill_offset 1
 set bot_ai_aimskill_think 1
 // Better don't touch these, there are hard to tweak!

Modified: trunk/data/qcsrc/server/bots.qc
===================================================================
--- trunk/data/qcsrc/server/bots.qc	2008-02-25 19:37:08 UTC (rev 3425)
+++ trunk/data/qcsrc/server/bots.qc	2008-02-25 19:37:46 UTC (rev 3426)
@@ -4,7 +4,7 @@
 
 vector stepheightvec;
 float navigation_testtracewalk;
-float(entity e, vector start, vector m1, vector m2, vector end) tracewalk =
+float(entity e, vector start, vector m1, vector m2, vector end, float movemode) tracewalk =
 {
 	local vector org;
 	local vector move;
@@ -30,10 +30,10 @@
 	stepdist = 32;
 	ignorehazards = FALSE;
 	//self.angles = vectoangles(dir);
-	traceline(start, start, FALSE, e);
+	traceline(start, start, MOVE_NORMAL, e);
 	if (trace_dpstartcontents & (DPCONTENTS_SLIME | DPCONTENTS_LAVA))
 		ignorehazards = TRUE;
-	tracebox(start, m1, m2, start, TRUE, e);
+	tracebox(start, m1, m2, start, MOVE_NOMONSTERS, e);
 	if (trace_startsolid)
 	{
 		// failed - bad start
@@ -71,7 +71,7 @@
 		if (stepdist > dist)
 			stepdist = dist;
 		dist = dist - stepdist;
-		traceline(org, org, FALSE, e);
+		traceline(org, org, MOVE_NORMAL, e);
 		if (!ignorehazards)
 		{
 			if (trace_dpstartcontents & (DPCONTENTS_SLIME | DPCONTENTS_LAVA))
@@ -89,7 +89,7 @@
 		if (trace_dpstartcontents & DPCONTENTS_LIQUIDSMASK)
 		{
 			move = normalize(end - org);
-			tracebox(org, m1, m2, org + move * stepdist, MOVE_WORLDONLY, e);
+			tracebox(org, m1, m2, org + move * stepdist, movemode, e);
 			if (trace_fraction < 1)
 			{
 				if (navigation_testtracewalk)
@@ -107,10 +107,10 @@
 		else
 		{
 			move = dir * stepdist + org;
-			tracebox(org, m1, m2, move, MOVE_WORLDONLY, e);
+			tracebox(org, m1, m2, move, movemode, e);
 			if (trace_fraction < 1)
 			{
-				tracebox(org + stepheightvec, m1, m2, move + stepheightvec, MOVE_WORLDONLY, e);
+				tracebox(org + stepheightvec, m1, m2, move + stepheightvec, movemode, e);
 				if (trace_fraction < 1 || trace_startsolid)
 				{
 					if (navigation_testtracewalk)
@@ -126,13 +126,12 @@
 				}
 			}
 			move = trace_endpos;
-			tracebox(move, m1, m2, move + '0 0 -65536', FALSE, e);
-			/*
 			// 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 + '0 0 18', m1, m2, move + '0 0 -65536', FALSE, e);
+			tracebox(move, m1, m2, move + '0 0 -65536', movemode, e);
+			/*
 			if (trace_startsolid)
 			{
 				tracebox(move, m1, m2, move + '0 0 -65536', FALSE, e);
@@ -344,7 +343,7 @@
 .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;
+.float wpfire, wpcost, wpconsidered;
 .float wpisbox;
 .float wpflags;
 .vector wpnearestpoint;
@@ -516,10 +515,10 @@
 				//traceline(self.origin, e.origin, FALSE, world);
 				//if (trace_fraction == 1)
 				if (!self.wpisbox)
-				if (tracewalk(self, sv, PL_MIN, PL_MAX, ev))
+				if (tracewalk(self, sv, PL_MIN, PL_MAX, ev, MOVE_NOMONSTERS))
 					waypoint_addlink(self, e);
 				if (!e.wpisbox)
-				if (tracewalk(e, ev, PL_MIN, PL_MAX, sv))
+				if (tracewalk(e, ev, PL_MIN, PL_MAX, sv, MOVE_NOMONSTERS))
 					waypoint_addlink(e, self);
 			}
 		}
@@ -1039,7 +1038,7 @@
 			{
 				traceline(v, org, TRUE, player);
 				if (trace_fraction == 1)
-				if (tracewalk(player, v, PL_MIN, PL_MAX, org))
+				if (tracewalk(player, v, PL_MIN, PL_MAX, org, MOVE_NORMAL))
 				{
 					bestdist = dist;
 					best = w;
@@ -1049,7 +1048,7 @@
 			{
 				traceline(v, org, TRUE, player);
 				if (trace_fraction == 1)
-				if (tracewalk(player, org, PL_MIN, PL_MAX, v))
+				if (tracewalk(player, org, PL_MIN, PL_MAX, v, MOVE_NORMAL))
 				{
 					bestdist = dist;
 					best = w;
@@ -1062,50 +1061,48 @@
 }
 
 // finds the waypoints near the bot initiating a navigation query
-void() navigation_markroutes_nearestwaypoints =
+float(entity waylist, float maxdist) navigation_markroutes_nearestwaypoints =
 {
 	local entity head;
-	local float dist;
-	local vector v, m1, m2;
+	local vector v, m1, m2, diff;
+	local float c;
 	//navigation_testtracewalk = TRUE;
-	head = findchain(classname, "waypoint");
+	c = 0;
+	head = waylist;
 	while (head)
 	{
-		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);
-		dist = vlen(v - self.origin);
-		if (dist < 1000)
+		if (!head.wpconsidered)
 		{
-			if (dist < 4)
+			if (head.wpisbox)
 			{
-				// if bot is very near a waypoint don't bother doing a trace
-				head.wpnearestpoint = v;
-				head.wpcost = dist + head.dmg;
-				head.wpfire = 1;
-				head.enemy = world;
+				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)
 			{
-				tracebox(self.origin, '-16 -16 0', '16 16 0', v, TRUE, self);
-				if (trace_fraction == 1)
+				head.wpconsidered = TRUE;
+				if (tracewalk(self, self.origin, self.mins, self.maxs, v, MOVE_NORMAL))
 				{
-					if (tracewalk(self, self.origin, self.mins, self.maxs, v))
-					{
-						head.wpnearestpoint = v;
-						head.wpcost = dist + head.dmg;
-						head.wpfire = 1;
-						head.enemy = world;
-					}
+					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 waypoint link is better than the current one
@@ -1143,13 +1140,18 @@
 	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;
 	}
-	navigation_markroutes_nearestwaypoints();
+	// try a short range search for the nearest waypoints, and expand the
+	// search repeatedly if none are found
+	if (!navigation_markroutes_nearestwaypoints(waylist, 250))
+		if (!navigation_markroutes_nearestwaypoints(waylist, 1000))
+			navigation_markroutes_nearestwaypoints(waylist, 1000000);
 	searching = TRUE;
 	while (searching)
 	{
@@ -1252,9 +1254,9 @@
 	//bprint(etos(e));
 	//bprint(") : ");
 	//bprint(etos(e));
-	tracebox(self.origin, '-16 -16 0', '16 16 0', e.origin, TRUE, self);
-	if (trace_fraction == 1)
-	if (tracewalk(self, self.origin, PL_MIN, PL_MAX, e.origin))
+	//tracebox(self.origin, '-16 -16 0', '16 16 0', e.origin, TRUE, self);
+	//if (trace_fraction == 1)
+	if (tracewalk(self, self.origin, PL_MIN, PL_MAX, e.origin, MOVE_NORMAL))
 		return;
 	// see if there are waypoints describing a path to the item
 	e = e.nearestwaypoint;
@@ -1265,7 +1267,7 @@
 		//te_smallflash((e.absmin + e.absmax) * 0.5);
 		//tracebox(self.origin, '-16 -16 0', '16 16 0', e.origin, TRUE, self);
 		//if (trace_fraction == 1)
-		//if (tracewalk(self, self.origin, PL_MIN, PL_MAX, e.origin))
+		//if (tracewalk(self, self.origin, PL_MIN, PL_MAX, e.origin, MOVE_NORMAL))
 		//	break;
 		// add the waypoint to the path
 		navigation_pushroute(e);
@@ -1527,6 +1529,7 @@
 		self.bot_canfire = 1;
 };
 
+.float bot_nextthink;
 .float bot_badaimtime;
 .float bot_aimthinktime;
 .vector bot_mouseaim;
@@ -1575,13 +1578,7 @@
 
 	//dprint("aim ", self.netname, ": old:", vtos(self.v_angle));
 	// make sure v_angle is sane first
-	//self.v_angle_x = bound(-180, self.v_angle_x, 180);
-	//while (self.v_angle_x < -180) self.v_angle_x = self.v_angle_x + 360;
-	//while (self.v_angle_x >= 180) self.v_angle_x = self.v_angle_x - 360;
-	while (self.v_angle_y < -180) self.v_angle_y = self.v_angle_y + 360;
-	while (self.v_angle_y >= 180) self.v_angle_y = self.v_angle_y - 360;
-	//while (self.v_angle_z < -180) self.v_angle_z = self.v_angle_z + 360;
-	//while (self.v_angle_z >= 180) self.v_angle_z = self.v_angle_z - 360;
+	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
@@ -1591,15 +1588,14 @@
 	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);
+		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*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 = 0 - desiredang_x;
+	desiredang_x = bound(-90, 0 - desiredang_x, 90);
 	desiredang_z = self.v_angle_z;
-	desiredang_x = bound(-90, desiredang_x, 90);
 	//dprint(" / ", vtos(desiredang));
 
 	//// pain throws off aim
@@ -1610,19 +1606,17 @@
 	//}
 
 	// calculate turn angles
-	diffang = (desiredang - self.bot_olddesiredang)*(1/frametime);
+	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));
 
-	// wrap yaw turn
-	while (diffang_y < -180)
-		diffang_y = diffang_y + 360;
-	while (diffang_y >  180)
-		diffang_y = diffang_y - 360;
-
 	// Here we will try to anticipate the comming aiming direction
 	self.bot_1st_order_aimfilter= self.bot_1st_order_aimfilter
-		+ (diffang                      - self.bot_1st_order_aimfilter) * bound(0, cvar("bot_ai_aimskill_order_filter_1st"),1);
+		+ (diffang * (1 / frametime)    - 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
@@ -1643,19 +1637,14 @@
 		+ self.bot_4th_order_aimfilter * cvar("bot_ai_aimskill_order_mix_4th")
 		+ self.bot_5th_order_aimfilter * cvar("bot_ai_aimskill_order_mix_5th")
 	);
-	while (desiredang_z < -180)
-		desiredang_z = desiredang_z + 360;
-	while (desiredang_z >  180)
-		desiredang_z = desiredang_z - 360;
+
 	// calculate turn angles
 	diffang = desiredang - self.bot_mouseaim;
-	//dprint(" diff:", vtos(diffang));
-
 	// wrap yaw turn
-	while (diffang_y < -180)
-		diffang_y = diffang_y + 360;
-	while (diffang_y >  180)
+	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)
 	{
@@ -1665,27 +1654,21 @@
 
 	//self.v_angle = self.v_angle + diffang * bound(0, r * frametime * (skill * 0.5 + 2), 1);
 
-	desiredang = self.bot_mouseaim*bound(0,cvar("bot_ai_aimskill_think"),1) + desiredang * bound(0,(1-cvar("bot_ai_aimskill_think")),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;
-	//dprint(" diff:", vtos(diffang));
-
 	// wrap yaw turn
-	while (diffang_y < -180)
-		diffang_y = diffang_y + 360;
-	while (diffang_y >  180)
+	diffang_y = diffang_y - floor(diffang_y / 360) * 360;
+	if (diffang_y >= 180)
 		diffang_y = diffang_y - 360;
-	// calculate turn angles
-	diffang = desiredang - self.v_angle;
 	//dprint(" diff:", vtos(diffang));
 
-	// wrap yaw turn
-	while (diffang_y < -180)
-		diffang_y = diffang_y + 360;
-	while (diffang_y >  180)
-		diffang_y = diffang_y - 360;
-
 	// jitter tracking
 	dist = vlen(diffang);
 	//diffang = diffang + randomvec() * (dist * 0.05 * (3.5 - bound(0, skill, 3)));
@@ -1701,10 +1684,7 @@
 	//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;
-	while (self.v_angle_y < -180)
-		self.v_angle_y = self.v_angle_y + 360;
-	while (self.v_angle_y >= 180)
-		self.v_angle_y = self.v_angle_y - 360;
+	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);
@@ -1715,13 +1695,12 @@
 	//te_lightning2(world, shotorg, shotorg + shotdir * 100);
 
 	// calculate turn angles again
-	diffang = desiredang - self.v_angle;
-	while (diffang_y < -180)
-		diffang_y = diffang_y + 360;
-	while (diffang_y >  180)
-		diffang_y = diffang_y - 360;
+	//diffang = desiredang - self.v_angle;
+	//diffang_y = diffang_y - floor(diffang_y / 360) * 360;
+	//if (diffang_y >= 180)
+	//	diffang_y = diffang_y - 360;
 
-	//bprint("e ");bprintvector(diffang);bprint(" < ");bprintfloat(maxfiredeviation);bprint("\n");
+	//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
@@ -1729,7 +1708,7 @@
 	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);
+	//traceline(shotorg,shotorg+shotdir*1000,FALSE,world);
 	//dprint(ftos(maxfiredeviation),"\n");
 	//dprint(" diff:", vtos(diffang), "\n");
 
@@ -1802,6 +1781,9 @@
 // TODO: move this painintensity code to the player damage code
 void() bot_think =
 {
+	if (self.bot_nextthink > time)
+		return;
+	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;
 
@@ -1859,6 +1841,7 @@
 };
 
 entity bot_strategytoken;
+float bot_strategytoken_taken;
 entity player_list;
 .entity nextplayer;
 void() bot_relinkplayerlist =
@@ -1887,6 +1870,7 @@
 	}
 	dprint(strcat("relink: ", ftos(currentbots), " bots seen.\n"));
 	bot_strategytoken = bot_list;
+	bot_strategytoken_taken = TRUE;
 };
 
 void() havocbot_setupbot;
@@ -1896,6 +1880,7 @@
 {
 	if (clienttype(self) != CLIENTTYPE_BOT)
 		return;
+	self.bot_nextthink = time - random();
 	self.lag_func = bot_lagfunc;
 	self.isbot = TRUE;
 	self.createdtime = self.nextthink;
@@ -2053,7 +2038,7 @@
 };
 
 entity botframe_dangerwaypoint;
-void() botframe_updatedangerousobjects =
+void(float maxupdate) botframe_updatedangerousobjects =
 {
 	local entity head, bot_dodgelist;
 	local vector m1, m2, v;
@@ -2084,7 +2069,7 @@
 		}
 		botframe_dangerwaypoint.dmg = danger;
 		c = c + 1;
-		if (c >= 256)
+		if (c >= maxupdate)
 			break;
 		botframe_dangerwaypoint = find(botframe_dangerwaypoint, classname, "waypoint");
 	}
@@ -2093,6 +2078,7 @@
 
 float botframe_spawnedwaypoints;
 float botframe_nextthink;
+float botframe_nextdangertime;
 
 float autoskill_nextthink;
 .float totalfrags_lastcheck;
@@ -2245,12 +2231,24 @@
 		// cycle the goal token from one bot to the next each frame
 		// (this prevents them from all doing waypoint searches on the same
 		//  frame, which causes choppy framerates)
-		if (bot_strategytoken)
-			bot_strategytoken = bot_strategytoken.nextbot;
-		if (!bot_strategytoken)
-			bot_strategytoken = bot_list;
+		if (bot_strategytoken_taken)
+		{
+			bot_strategytoken_taken = FALSE;
+			if (bot_strategytoken)
+				bot_strategytoken = bot_strategytoken.nextbot;
+			if (!bot_strategytoken)
+				bot_strategytoken = bot_list;
+		}
 
-		botframe_updatedangerousobjects();
+		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"))

Modified: trunk/data/qcsrc/server/ctf.qc
===================================================================
--- trunk/data/qcsrc/server/ctf.qc	2008-02-25 19:37:08 UTC (rev 3425)
+++ trunk/data/qcsrc/server/ctf.qc	2008-02-25 19:37:46 UTC (rev 3426)
@@ -1,4 +1,6 @@
 
+entity flaglist; // CTF flags in the map
+
 .float next_take_time;			// the next time a player can pick up a flag (time + blah)
 								/// I used this, in part, to fix the looping score bug. - avirox
 
@@ -458,10 +460,15 @@
 {
 	if (!g_ctf)
 		return;
+
 	//if(!cvar("teamplay"))
 	//	cvar_set("teamplay", "3");
 
-	self.classname = "item_flag_team1";
+	// link flag into flaglist
+	self.enemy = flaglist;
+	flaglist = self;
+
+	self.classname = "item_flag_team";
 	self.team = COLOR_TEAM1; // color 4 team (red)
 	self.items = IT_KEY2; // gold key (redish enough)
 	self.netname = "^1RED^7 flag";
@@ -520,7 +527,11 @@
 	//if(!cvar("teamplay"))
 	//	cvar_set("teamplay", "3");
 
-	self.classname = "item_flag_team2";
+	// link flag into flaglist
+	self.enemy = flaglist;
+	flaglist = self;
+
+	self.classname = "item_flag_team";
 	self.team = COLOR_TEAM2; // color 13 team (blue)
 	self.items = IT_KEY1; // silver key (bluish enough)
 	self.netname = "^4BLUE^7 flag";
@@ -655,9 +666,11 @@
 
 	if (g_ctf) {
 		local entity flag;
-		flag = find(world, classname, "item_flag_team1");
-		ctf_setstatus2(flag);
-		flag = find(world, classname, "item_flag_team2");
-		ctf_setstatus2(flag);
+		flag = flaglist;
+		while (flag)
+		{
+			ctf_setstatus2(flag);
+			flag = flag.enemy;
+		}
 	}
 };

Modified: trunk/data/qcsrc/server/havocbot.qc
===================================================================
--- trunk/data/qcsrc/server/havocbot.qc	2008-02-25 19:37:08 UTC (rev 3425)
+++ trunk/data/qcsrc/server/havocbot.qc	2008-02-25 19:37:46 UTC (rev 3426)
@@ -5,6 +5,9 @@
 
 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;
@@ -49,12 +52,16 @@
 		head = head.chain;
 	}
 	return dodge;
+	*/
 };
 
 //.float havocbotignoretime;
 .float havocbot_keyboardtime;
 .float havocbot_ducktime;
 .vector havocbot_keyboard;
+//.vector bot_dodgevector;
+//.float bot_dodgevector_time;
+//.float bot_dodgevector_jumpbutton;
 void() havocbot_movetogoal =
 {
 	local vector destorg;
@@ -66,11 +73,13 @@
 	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");
 
 	if(self.jumppadcount)
 	{
@@ -89,8 +98,6 @@
 		self.bot_strategytime = 0;
 		return;
 	}
-	evadeobstacle = '0 0 0';
-	evadelava = '0 0 0';
 	m1 = self.goalcurrent.origin + self.goalcurrent.mins;
 	m2 = self.goalcurrent.origin + self.goalcurrent.maxs;
 	destorg = self.origin;
@@ -102,113 +109,146 @@
 	dir = normalize(diff);
 	flatdir = diff;flatdir_z = 0;
 	flatdir = normalize(flatdir);
-	if (!self.waterlevel)
+	//if (self.bot_dodgevector_time < time)
 	{
-		// Since new update in air contol, we can move in air
-		//if (!(self.flags & FL_ONGROUND))
-		//{
-		//	// prevent goal checks when we can't walk
-		//	if (self.bot_strategytime < time + 0.1)
-		//		self.bot_strategytime = time + 0.1;
-		//	return;
-		//}
+	//	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)
+		{
+			// Since new update in air contol, we can move in air
+			//if (!(self.flags & FL_ONGROUND))
+			//{
+			//	// prevent goal checks when we can't walk
+			//	if (self.bot_strategytime < time + 0.1)
+			//		self.bot_strategytime = time + 0.1;
+			//	return;
+			//}
 
-		// 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)
+			// 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 48', self.mins, self.maxs, self.origin + self.velocity * 0.2 + '0 0 48', FALSE, self);
-				if (trace_fraction > s)
-					self.button2 = 1;
+				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.button2 = 1;
+				}
 			}
+
+			traceline(self.origin + self.velocity * 0.3, self.origin + self.velocity * 0.3 + '0 0 -1000', TRUE, world);
+			s = pointcontents(trace_endpos + '0 0 1');
+			if (s == CONTENT_LAVA || s == CONTENT_SLIME)
+				evadelava = normalize(self.velocity) * -1;
+
+			dir = flatdir;
+			evadeobstacle_z = 0;
+			evadelava_z = 0;
+			makevectors(self.v_angle_y * '0 1 0');
 		}
+		else
+			makevectors(self.v_angle);
+		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);
 
-		traceline(self.origin + self.velocity * 0.3, self.origin + self.velocity * 0.3 + '0 0 -1000', TRUE, world);
-		s = pointcontents(trace_endpos + '0 0 1');
-		if (s == CONTENT_LAVA || s == CONTENT_SLIME)
-			evadelava = normalize(self.velocity) * -1;
-
-		dir = flatdir;
+		dir = normalize(dir + dodge + evadeobstacle + evadelava);
+	//	self.bot_dodgevector = dir;
+	//	self.bot_dodgevector_jumpbutton = self.button2;
 	}
-	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);
+	//dir = self.bot_dodgevector;
+	//if (self.bot_dodgevector_jumpbutton)
+	//	self.button2 = 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;
-	local vector keyboard,flatangle;
-	local float blend;
-	keyboard = self.havocbot_keyboard;
-	if (time >= self.havocbot_keyboardtime)
+	if (skill < 10)
 	{
-		flatdir=dir; flatdir_z = 0;
-		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 = '0 0 0';
-		
-		flatangle = self.v_angle; flatangle_z=0;
-		makevectors(flatangle);
-		
-		local float trigger;
-		local vector v_forward_right;
-		local vector v_forward_left;
-		blend = bound(0,skill*0.1,1);
-		trigger = cvar("bot_ai_keyboard_treshold");
-		v_forward_right = normalize(v_forward + v_right);
-		v_forward_left  = normalize(v_forward - v_right);
-		// Place in reverse order !! least important direction FIRST
-		
-		if (skill > 4.5)
+		// Emulate keyboard interface;
+		local vector keyboard;
+		local float blend;
+		if (time >= self.havocbot_keyboardtime)
 		{
-			if (flatdir * v_forward_right * -1 > trigger) keyboard = v_forward_right * -1;
-			if (flatdir * v_forward_left  * -1 > trigger) keyboard = v_forward_left  * -1;
+			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.button5=TRUE;
 		}
-		if (skill > 2.5)
-		{
-			if (flatdir * v_forward_right      > trigger) keyboard = v_forward_right;
-			if (flatdir * v_forward_left       > trigger) keyboard = v_forward_left;
-			if (flatdir * v_forward       *  1 > trigger) keyboard = v_forward * -1;
-		}
-		if (skill > 1.5)
-		{
-			if (flatdir * v_right              > trigger) keyboard = v_right;
-			if (flatdir * v_right         * -1 > trigger) keyboard = v_right   * -1;
-		}
-			if (flatdir * v_forward            > trigger) keyboard = v_forward;
-		//dprint(ftos(flatdir * v_forward),"\n");
-		keyboard = normalize(keyboard);
-		self.havocbot_keyboard = keyboard;
-		if (self.havocbot_ducktime>time) self.button5=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;
 	}
-	blend = bound(0,vlen(self.goalcurrent.origin-self.origin)/cvar("bot_ai_keyboard_distance"),1); // When getting close move with 360 degree
-	dir = (keyboard * blend + dir * (1-blend))*cvar("sv_maxspeed");
 
-	self.movement_x = dir * v_forward;
-	self.movement_y = dir * v_right;
-	if (self.flags & FL_INWATER) self.movement_z = dir * v_up; else self.movement_z = 0;
-
 	if ((dir * v_up) >= cvar("sv_jumpvelocity")*0.5 && (self.flags & FL_ONGROUND)) self.button2=1;
 	if (((dodge * v_up) > 0) && random()*frametime >= 0.2*bound(0,(10-skill)*0.1,1)) self.button2=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;
@@ -223,30 +263,39 @@
 		self.enemy = world;
 		return;
 	}
-	traceline(self.origin+self.view_ofs, self.enemy.origin+self.enemy.view_ofs*0.5,FALSE,world);
-	if (trace_ent.classname != "player")
-		self.havocbot_stickenemy =0;
-	else	if ( (trace_ent != self.enemy) || (vlen(self.enemy.origin - self.origin) > 1000) )
+	if (self.enemy)
+	{
+		if (!bot_shouldattack(self.enemy))
 		{
-			self.havocbot_stickenemy =0;
-			if( (self.health < 30) || (self.enemy.health < 0))
+			// enemy died or something, find a new target
+			self.enemy = world;
 			self.havocbot_chooseenemy_finished = time;
 		}
-	//dprint(ftos(self.havocbot_stickenemy));dprint(etos(self.enemy),"\n");
-	//dprint(ftos(time),"-");dprint(ftos(self.havocbot_chooseenemy_finished),"\n");
-	if (self.havocbot_stickenemy == 1)
-	{
-		// remain tracking him for a shot while (case he went after a small corner or pilar
-		self.havocbot_chooseenemy_finished = time + bound(0,skill*0.1,1)*1.8;
-		return;
+		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)
-	{
-		self.havocbot_stickenemy = 1;
 		return;
-	}
-	self.havocbot_chooseenemy_finished = time + cvar("bot_ai_enemydetectioninterval")*bound(0,(11-skill)*0.1,1);
-	self.havocbot_chooseenemy_finished = time + 0.01;
+	self.havocbot_chooseenemy_finished = time + cvar("bot_ai_enemydetectioninterval");
 	eye = (self.origin + self.view_ofs);
 	best = world;
 	bestrating = 100000000;
@@ -387,12 +436,22 @@
 		lag_additem(time + self.ping, 0, 0, world, self.origin, selfvel, self.goalcurrent.origin, '0 0 0');
 };
 
+.float bot_chooseweapontime;
 void() havocbot_ai =
 {
 	if (bot_strategytoken == self)
+	if (!bot_strategytoken_taken)
+	{
 		self.havocbot_role();
+		// token has been used this frame
+		bot_strategytoken_taken = TRUE;
+	}
 	havocbot_chooseenemy();
-	havocbot_chooseweapon();
+	if (self.bot_chooseweapontime < time)
+	{
+		self.bot_chooseweapontime = time + cvar("bot_ai_chooseweaponinterval");
+		havocbot_chooseweapon();
+	}
 	havocbot_aim();
 	lag_update();
 	if (self.bot_aimtarg)
@@ -400,18 +459,22 @@
 	else if (self.goalcurrent)
 	{
 		local vector now,v,next;//,heading;
-		local float distance,skillblend,distanceblend;
-		now = self.goalcurrent.origin - self.origin;
+		local float distance,skillblend,distanceblend,blend;
+		next = now = self.goalcurrent.origin - (self.origin + self.view_ofs);
 		distance = vlen(now);
 		//heading = self.velocity;
 		//dprint(self.goalstack01.classname,etos(self.goalstack01),"\n");
 		if(self.goalstack01 != self && self.goalstack01 != world)
-			next = self.goalstack01.origin - self.origin;
-		else
-			next = now;
+			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,distance/cvar("bot_ai_keyboard_distance"),1); 
-		v = (now * (distanceblend) + next * (1-distanceblend)) * (skillblend) + now * (1-skillblend);
+		distanceblend=bound(0,distance/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 < 2)
 			v_z = 0;

Modified: trunk/data/qcsrc/server/havocbot_roles.qc
===================================================================
--- trunk/data/qcsrc/server/havocbot_roles.qc	2008-02-25 19:37:08 UTC (rev 3425)
+++ trunk/data/qcsrc/server/havocbot_roles.qc	2008-02-25 19:37:46 UTC (rev 3426)
@@ -125,24 +125,33 @@
 	}
 };
 
+entity flaglist;
 void(float ratingscale) havocbot_goalrating_ctf_ourflag =
 {
 	local entity head;
-	if (self.team == COLOR_TEAM1) // red
-		head = find(world, classname, "item_flag_team1"); // red flag
-	else // blue
-		head = find(world, classname, "item_flag_team2"); // blue flag
-	navigation_routerating(head, ratingscale);
+	head = flaglist;
+	while (head)
+	{
+		if (self.team == head.team)
+			break;
+		head = head.enemy;
+	}
+	if (head)
+		navigation_routerating(head, ratingscale);
 };
 
 void(float ratingscale) havocbot_goalrating_ctf_enemyflag =
 {
 	local entity head;
-	if (self.team == COLOR_TEAM1) // red
-		head = find(world, classname, "item_flag_team2"); // blue flag
-	else // blue
-		head = find(world, classname, "item_flag_team1"); // red flag
-	navigation_routerating(head, ratingscale);
+	head = flaglist;
+	while (head)
+	{
+		if (self.team != head.team)
+			break;
+		head = head.enemy;
+	}
+	if (head)
+		navigation_routerating(head, ratingscale);
 };
 
 void(float ratingscale) havocbot_goalrating_ctf_enemybase =
@@ -153,30 +162,28 @@
 void(float ratingscale) havocbot_goalrating_ctf_ourstolenflag =
 {
 	local entity head;
-	if (self.team == COLOR_TEAM1) // red
-		head = find(world, classname, "item_flag_team1"); // red flag
-	else // blue
-		head = find(world, classname, "item_flag_team2"); // blue flag
+	head = flaglist;
+	while (head)
+	{
+		if (self.team == head.team)
+			break;
+		head = head.enemy;
+	}
+	if (head)
 	if (head.cnt != FLAG_BASE)
 		navigation_routerating(head, ratingscale);
 };
 
 void(float ratingscale) havocbot_goalrating_ctf_droppedflags =
 {
-	local entity redflag, blueflag;
-
-	redflag = find(world, classname, "item_flag_team1");
-	blueflag = find(world, classname, "item_flag_team2");
-
-	if (redflag == world)
-		error("havocbot: item_flag_team1 missing\n");
-	if (blueflag == world)
-		error("havocbot: item_flag_team2 missing\n");
-
-	if (redflag.cnt != FLAG_BASE) // red flag is carried or out in the field
-		navigation_routerating(redflag, ratingscale);
-	if (blueflag.cnt != FLAG_BASE) // blue flag is carried or out in the field
-		navigation_routerating(blueflag, ratingscale);
+	local entity head;
+	head = flaglist;
+	while (head)
+	{
+		if (head.cnt != FLAG_BASE) // flag is carried or out in the field
+			navigation_routerating(head, ratingscale);
+		head = head.enemy;
+	}
 };
 
 // CTF: (always teamplay)
@@ -236,10 +243,13 @@
 		return;
 	}
 	// check our flag
-	if (self.team == COLOR_TEAM1) // red
-		f = find(world, classname, "item_flag_team1");
-	else // blue
-		f = find(world, classname, "item_flag_team2");
+	f = flaglist;
+	while (f)
+	{
+		if (self.team == f.team)
+			break;
+		f = f.enemy;
+	}
 	if (f.cnt != FLAG_BASE && canreach(f))
 	{
 		dprint("changing role to interceptor\n");
@@ -286,10 +296,13 @@
 		return;
 	}
 	// check our flag
-	if (self.team == COLOR_TEAM1) // red
-		f = find(world, classname, "item_flag_team1");
-	else // blue
-		f = find(world, classname, "item_flag_team2");
+	f = flaglist;
+	while (f)
+	{
+		if (self.team == f.team)
+			break;
+		f = f.enemy;
+	}
 	if (f.cnt == FLAG_BASE)
 	{
 		dprint("changing role back\n");
@@ -327,10 +340,13 @@
 		return;
 	}
 	// check our flag
-	if (self.team == COLOR_TEAM1) // red
-		f = find(world, classname, "item_flag_team1");
-	else // blue
-		f = find(world, classname, "item_flag_team2");
+	f = flaglist;
+	while (f)
+	{
+		if (self.team == f.team)
+			break;
+		f = f.enemy;
+	}
 	if (f.cnt != FLAG_BASE && canreach(f))
 	{
 		dprint("changing role to interceptor\n");
@@ -387,10 +403,13 @@
 		return;
 	}
 	// check our flag
-	if (self.team == COLOR_TEAM1) // red
-		f = find(world, classname, "item_flag_team1");
-	else // blue
-		f = find(world, classname, "item_flag_team2");
+	f = flaglist;
+	while (f)
+	{
+		if (self.team == f.team)
+			break;
+		f = f.enemy;
+	}
 	if (f.cnt != FLAG_BASE && canreach(f))
 	{
 		dprint("changing role to interceptor\n");
@@ -451,6 +470,7 @@
 
 // CTF:
 // choose a role according to the situation
+void() havocbot_role_dm;
 void() havocbot_chooserole_ctf =
 {
 	local float r;
@@ -515,10 +535,12 @@
 
 
 
+entity kh_keylist;
 void(float ratingscale_team, float ratingscale_dropped, float ratingscale_enemy) havocbot_goalrating_kh =
 {
 	local entity head;
-	for(head = world; (head = find(head, classname, "item_kh_key")); )
+	head = kh_keylist;
+	while (head)
 	{
 		if(head.owner == self)
 			continue;
@@ -531,6 +553,7 @@
 			navigation_routerating(head, ratingscale_team);
 		else
 			navigation_routerating(head, ratingscale_enemy);
+		head = head.enemy;
 	}
 };
 

Modified: trunk/data/qcsrc/server/keyhunt.qc
===================================================================
--- trunk/data/qcsrc/server/keyhunt.qc	2008-02-25 19:37:08 UTC (rev 3425)
+++ trunk/data/qcsrc/server/keyhunt.qc	2008-02-25 19:37:46 UTC (rev 3426)
@@ -1,4 +1,4 @@
-#define FOR_EACH_KH_KEY(v) for(v = world; (v = find(v, classname, STR_ITEM_KH_KEY)); )
+#define FOR_EACH_KH_KEY(v) for(v = kh_keylist; v; v = v.enemy )
 
 // #define KH_PLAYER_USE_ATTACHMENT
 // #define KH_PLAYER_USE_CARRIEDMODEL
@@ -32,6 +32,7 @@
 	return 0;
 }
 
+entity kh_keylist;
 entity kh_controller;
 float kh_tracking_enabled;
 float kh_teams;
@@ -320,6 +321,10 @@
 			break;
 	}
 
+	// link into key list
+	key.enemy = kh_keylist;
+	kh_keylist = key;
+
 	sprint(initial_owner, strcat("You got the ^2", key.netname, "\n"));
 
 	WaypointSprite_Spawn("", 0, 0, key, '0 0 1' * KH_KEY_WP_ZSHIFT, world, key.team, key, waypointsprite_attachedforcarrier, FALSE);
@@ -338,6 +343,23 @@
 	else // it was dropped
 		WaypointSprite_DetachCarrier(key);
 
+	// remove key from key list
+	if (kh_keylist == key)
+		kh_keylist = kh_keylist.enemy;
+	else
+	{
+		o = kh_keylist;
+		while (o)
+		{
+			if (o.enemy == key)
+			{
+				o.enemy = o.enemy.enemy;
+				break;
+			}
+			o = o.enemy;
+		}
+	}
+
 	remove(key);
 }
 
@@ -467,7 +489,7 @@
 	if(!self.owner)
 		if(time > self.pain_finished)
 			kh_LoserTeam(self.team, self);
-	
+
 	if(self.owner)
 	if(kh_Key_AllOwnedByWhichTeam() != -1)
 	{
@@ -583,7 +605,7 @@
 		FOR_EACH_PLAYER(player)
 			if(player.team != teem)
 				++players;
-		
+
 		FOR_EACH_KH_KEY(key)
 			if(key.owner && key.team != teem)
 				++keys;
@@ -649,7 +671,7 @@
 	entity player;
 	string result;
 	result = "";
-	
+
 	// find a random player per team
 	for(i = 0; i < kh_teams; ++i)
 	{
@@ -764,11 +786,11 @@
 	// e is spectator? That's no team mate...
 	if(e.classname != "player")
 		return kh_GetCarrierSprite(self.team, -1);
-	
+
 	// e is no key carrier: simple case...
 	if(!e.kh_next)
 		return kh_GetCarrierSprite(self.team, e.team);
-	
+
 	// e is a key carrier: if any key is dropped or owned by another team, show
 	// the carrier sprite; otherwise show run here
 	if(kh_Key_AllOwnedByWhichTeam() == e.team)
@@ -835,8 +857,8 @@
 	setmodel(kh_controller, "models/keyhunt/key.md3");
 	kh_key_dropped = kh_controller.modelindex;
 	/*
-	dprint(vtos(kh_controller.mins)); 
-	dprint(vtos(kh_controller.maxs)); 
+	dprint(vtos(kh_controller.mins));
+	dprint(vtos(kh_controller.maxs));
 	dprint("\n");
 	*/
 #ifdef KH_PLAYER_USE_CARRIEDMODEL

Modified: trunk/pro/default.cfg
===================================================================
--- trunk/pro/default.cfg	2008-02-25 19:37:08 UTC (rev 3425)
+++ trunk/pro/default.cfg	2008-02-25 19:37:46 UTC (rev 3426)
@@ -160,8 +160,13 @@
 seta bot_suffix ""
 seta skill_auto 0 // when 1, "skill" gets adjusted to match the best player on the map
 // general bot AI cvars
+set bot_ai_thinkinterval 0.05
 set bot_ai_strategyinterval 2
 set bot_ai_enemydetectioninterval 0.5
+set bot_ai_dodgeupdateinterval 0.1
+set bot_ai_chooseweaponinterval 0.3
+set bot_ai_dangerdetectioninterval 0.1
+set bot_ai_dangerdetectionupdates 64
 set bot_ai_aimskill_blendrate 2
 set bot_ai_aimskill_fixedrate 15
 set bot_ai_aimskill_firetolerance_distdegrees 180
@@ -169,7 +174,7 @@
 set bot_ai_aimskill_firetolerance_maxdegrees 45
 set bot_ai_aimskill_mouse 1
 set bot_ai_keyboard_distance 250
-set bot_ai_keyboard_treshold 0.94
+set bot_ai_keyboard_treshold 0.57
 set bot_ai_aimskill_offset 1
 set bot_ai_aimskill_think 1
 // Better don't touch these, there are hard to tweak!




More information about the nexuiz-commits mailing list