#include extreme\_ex_weapons;

main()
{
	createBotWeaponsArray();

	// don't add bots again if using the ready-up system
	if(level.ex_readyup && isDefined(game["readyup_done"])) return;

	// mbots are handled differently
	if(level.ex_mbot)
	{
		level.wp = [];
		level.spawnpoints = getentarray("mp_tdm_spawn", "classname");

		level.ex_mbots_allies = 0;
		level.ex_mbots_axis = 0;

		setcvar("mbot_add", "");
		setcvar("mbot_remove", "");
		setcvar("mbot_skill", "");
		setcvar("mbot_speed", "");

		level.wpfile = ("mbot/" + level.ex_currentmap + "_" + level.ex_currentgt + ".wp");
		if(level.ex_mbot_dev)
		{
			extreme\_ex_mbot_dev::init();
			level.wpfile = ("mbot/" + level.ex_currentmap + "_" + level.ex_currentgt + ".tmp");
			bFlag = true;
			if(!buildNodeInfo(level.wpfile))
			{
				wpf = ("mbot/" + level.ex_currentmap + "_" + level.ex_currentgt + ".wp");
				if(!buildNodeInfo(wpf)) bFlag = false;
					else extreme\_ex_mbot_dev::dumpWPs(level.wpfile);
			}
		}
		else
		{
			if(!buildNodeInfo(level.wpfile)) bFlag = false;
				else bFlag = true;
		}

		if(!bFlag && !level.ex_mbot_dev)
		{
			logprint("MBOTS: ABORTED. INVALID WAYPOINT FILE FOR MAP " + level.ex_currentmap + "\n");
			return;
		}

		addVel();

		setBotSkill(level.ex_mbot_skill, true);
		setBotSpeed(level.ex_mbot_speed, true);

	}

	level waittill("gobots");

	if(level.ex_mbot) thread mbotStart();
		else if(level.ex_testclients) thread dbotStart();
}

//******************************************************************************
// eXtreme DUMB Bots
//******************************************************************************
dbotStart()
{
	wait( [[level.ex_fpstime]](5) );

	players = getentarray("player", "classname");
	for(i = 0; i < players.size; i++)
		if(isDefined(players[i].pers) && isDefined(players[i].pers["isbot"])) level.ex_testclients--;

	for(i = 0; i < level.ex_testclients; i++)
	{
		wait( [[level.ex_fpstime]](1) );

		if(i & 1) team = "axis";
			else team = "allies";

		ent[i] = addtestclient();

		if(isDefined(ent[i])) ent[i] thread dbotAdd(team);
			else logprint("BOT DEBUG: failed to spawn bot " + i + "\n");
	}
}

dbotAdd(team)
{
	level endon("ex_gameover");
	self endon("disconnect");

	// wait here while the teams are configured
	while(!isDefined(self.pers["team"]) || !isDefined(game["allies"]) || !isDefined(game["axis"])) wait( [[level.ex_fpstime]](0.05) );

	// flag the bot
	self.pers["isbot"] = true;

	// pass server info
	self notify("menuresponse", game["menu_serverinfo"], "team");
	wait( [[level.ex_fpstime]](0.5) ); // DO NOT REMOVE, CHANGE OR DISABLE!

	// choose a team
	while(self.pers["team"] == "spectator")
	{
		self notify("menuresponse", game["menu_team"], team);
		wait( [[level.ex_fpstime]](0.5) );
	}

	// select weapon(s)
	self thread dbotLoadout();
}

dbotLoadout()
{
	// set the weapon variable
	weapon = undefined;
	if(level.ex_testclients_diag) logprint(self.name + " selecting weapons...\n");

	// keep looping here until all the weapons have been selected
	for(;;)
	{
		wait( [[level.ex_fpstime]](0.05) );

		if(self.pers["team"] == "allies") weapon = level.ex_botweapons_allies[randomInt(level.ex_botweapons_allies.size)];
			else weapon = level.ex_botweapons_axis[randomInt(level.ex_botweapons_axis.size)];

		if(!isDefined(weapon))
		{
			logprint("BOT DEBUG: bot weapons array contains an undefined slot. Fix that!\n");
			continue;
		}

		// check for weapon class only
		if(level.ex_wepo_class)
		{
			switch(level.ex_wepo_class)
			{
				case 1: classname = "pistol"; break;      // pistol only
				case 2: classname = "sniper"; break;      // sniper only
				case 3: classname = "mg"; break;          // mg only
				case 4: classname = "smg"; break;         // smg only
				case 5: classname = "rifle"; break;       // rifle only
				case 6: classname = "boltrifle"; break;   // bolt action rifle only
				case 7: classname = "shotgun"; break;     // shotgun only
				case 8: classname = "rl"; break;          // panzerschreck only
				case 9: classname = "boltsniper"; break;  // bolt and sniper only
				case 10: classname = "knife"; break;      // knives only
				default: classname = "boltsniper"; break; // boltsniper fallback
			}

			// if the weapon is not part of this class, shouldn't happen, but just in case!
			if(!isWeaponType(weapon, classname)) continue;
		}

		// force secondary weapon to "none" if secondary system is on
		if(level.ex_wepo_secondary && isDefined(self.pers["weapon"]))
		{
			self.sweapon = "none";
			self notify("menuresponse", game["menu_weapon_" + self.pers["team"] +"_sec"], "none");
			break;
		}

		// only allow pistol as primary on pistol-only class
		if(isWeaponType(weapon, "pistol") && level.ex_wepo_class != 1) continue;

		// only allow knife as primary on knife-only class
		if(isWeaponType(weapon, "knife") && level.ex_wepo_class != 10) continue;

		// if the weapon limiter is on, and this weapon is not allowed, get another one!
		if(level.ex_wepo_limiter)
		{
			if(level.ex_teamplay && level.ex_wepo_limiter_perteam)
			{
				if(self.pers["team"] == "allies")
				{
					if(isDefined(level.weapons[weapon].allow_allies) && level.weapons[weapon].allow_allies == 0) continue;
				}
				else
				{
					if(isDefined(level.weapons[weapon].allow_axis) && level.weapons[weapon].allow_axis == 0) continue;
				}
			}
			else
			{
				if(isDefined(level.weapons[weapon].allow) && level.weapons[weapon].allow == 0) continue;
			}
		}

		// choose a primary weapon
		if(!isDefined(self.pers["weapon"]))
		{
			self notify("menuresponse", game["menu_weapon_" + self.pers["team"]], weapon);
			wait( [[level.ex_fpstime]](1) );
			if(level.ex_testclients_diag && isDefined(self.pers["weapon"]))
				logprint(self.name + " (" + self.pers["team"] + ") selected primary weapon " + weapon + "\n");

			if(!level.ex_wepo_secondary && isDefined(self.pers["weapon"])) break;
		}
	}
}

//******************************************************************************
// eXtreme AI Bots
// Based on MBot v0.7 by Maks Deryabin aka Spec; modifications by Cepe7a
// Adapted to eXtreme+ by PatmanSan
//******************************************************************************
mbotStart()
{
	level.ex_mbot_init = true;

	mbot_allies = level.ex_mbot_allies;
	if(level.ex_mbot_allies >= 32) level.ex_mbot_axis = 0;
	mbot_axis = level.ex_mbot_axis;
	mbot_onteam = mbot_allies + mbot_axis;
	if(mbot_onteam >= 32) level.ex_mbot_spec = 0;

	mbot_selector = 1;
	while(mbot_allies > 0 || mbot_axis > 0)
	{
		wait( [[level.ex_fpstime]](3) );

		if( (mbot_selector&1) && (mbot_axis > 0) )
		{
			mbot_axis--;
			addBot("axis");
		}
		else if(mbot_allies > 0)
		{
			mbot_allies--;
			addBot("allies");
		}

		mbot_selector++;
	}

	for(i = 0; i < level.ex_mbot_spec; i++)
	{
		wait( [[level.ex_fpstime]](1) );
		addBot("spectator");
	}

	level.ex_mbot_init = false;

	level thread dvarCheck();
}

//------------------------------------------------------------------------------
// Level functions
//------------------------------------------------------------------------------
dvarCheck()
{
	level endon("intermission");

	for(;;)
	{
		tmp = getCvar("mbot_add");
		if(tmp != "")
		{
			setCvar("mbot_add", "");
			addbot(tmp);
		}

		tmp = getCvar("mbot_remove");
		if(tmp != "")
		{
			setCvar("mbot_remove", "");
			removeBot(tmp);
		}

		tmp = getCvar("mbot_skill");
		if(tmp != "")
		{
			tmp = getCvarInt("mbot_skill");
			setCvar("mbot_skill", "");
			if(tmp != level.ex_mbot_skill) setBotSkill(tmp);
		}

		tmp = getCvar("mbot_speed");
		if(tmp != "")
		{
			tmp = getCvarInt("mbot_speed");
			setCvar("mbot_speed", "");
			if(tmp != level.ex_mbot_speed) setBotSpeed(tmp);
		}

		wait( [[level.ex_fpstime]](1) );
	}
}

addBot(team)
{
	if(!isDefined(team)) team = "autoassign";

	bot = undefined;
	players = getentarray("player", "classname");
	if(!level.ex_mbot_init && team != "spectator")
	{
		for(i = 0; i < players.size; i++)
		{
			if(isDefined(players[i].isbot) && (players[i].pers["team"] == "spectator" || players[i].sessionteam == "spectator"))
			{
				bot = players[i];
				break;
			}
		}
	}

	if(!isDefined(bot)) bot = addtestclient();

	if(isDefined(bot))
	{
		bot.isbot = true;
		bot.pers["dontkick"] = true; // exclude from kick monitor
		if(level.ex_diana && (randomInt(100) < 25)) bot.pers["diana"] = true;
		iprintlnbold(&"MBOT_BOTADDED", [[level.ex_pname]](bot));

		switch(team)
		{
			case "axis": bot thread botJoin(team); break;
			case "allies": bot thread botJoin(team); break;
			case "spectator": bot thread botJoin(team); break;
			default: bot thread botJoin("autoassign"); break;
		}
	}
	else iprintln(&"MBOT_MAXBOTS");
}

removeBot(team)
{
	players = getentarray("player", "classname");

	switch(team)
	{
		case "allies":
			for(i = 0; i < players.size; i++)
			{
				if(!isDefined(players[i].isbot)) continue;
				if(players[i].pers["team"] == team)
				{
					players[i] notify("ex_dead");
					players[i] notify("killed_player");
					level.ex_mbots_allies--;
					players[i] thread botJoin("spectator");
					iprintlnbold(&"MBOT_BOTREMOVED", [[level.ex_pname]](players[i]));
				}
			}
			break;
		case "axis":
			for(i = 0; i < players.size; i++)
			{
				if(!isDefined(players[i].isbot)) continue;
				if(players[i].pers["team"] == team)
				{
					players[i] notify("ex_dead");
					players[i] notify("killed_player");
					level.ex_mbots_axis--;
					players[i] thread botJoin("spectator");
					iprintlnbold(&"MBOT_BOTREMOVED", [[level.ex_pname]](players[i]));
				}
			}
			break;
		case "all":
			for(i = 0; i < players.size; i++)
			{
				if(!isDefined(players[i].isbot)) continue;
				players[i] notify("ex_dead");
				players[i] notify("killed_player");
				players[i] thread botJoin("spectator");
				iprintlnbold(&"MBOT_BOTREMOVED", [[level.ex_pname]](players[i]));
			}

			level.ex_mbots_allies = 0;
			level.ex_mbots_axis = 0;
			break;
		default:
			for(i = 0; i < players.size; i++)
			{
				if(players[i].name == team)
				{
					if(!isDefined(players[i].isbot))
					{
						iprintln(&"MBOT_NOTABOT", [[level.ex_pname]](players[i]));
						continue;
					}

					if(players[i].pers["team"] == "allies")
						level.ex_mbots_allies--;
					else if(players[i].pers["team"] == "axis")
						level.ex_mbots_axis--;

					players[i] notify("ex_dead");
					players[i] notify("killed_player");
					players[i] thread botJoin("spectator");
					iprintlnbold(&"MBOT_BOTREMOVED", [[level.ex_pname]](players[i]));
					break;
				}
			}
			break;
	}
}

setBotSkill(num, force)
{
	if(!isDefined(force)) force = false;

	if(num > 10) num = 10;
		else if(num < 0) num = 0;

	if(force || level.ex_mbot_skill != num)
	{
		level.ex_mbot_skill = num;
		level.botwaittime = 0.55 - (level.ex_mbot_skill / 20);
		iprintlnbold(&"MBOT_SKILL", level.ex_mbot_skill);
	}
}

setBotSpeed(num, force)
{
	if(!isDefined(force)) force = false;

	if(num > 220) num = 220;
		else if(num < 50) num = 50;

	if(force || level.ex_mbot_speed != num)
	{
		level.ex_mbot_speed = num;
		iprintlnbold(&"MBOT_SPEED", level.ex_mbot_speed);
	}
}

//------------------------------------------------------------------------------
// Bot weapon functions
//------------------------------------------------------------------------------
botJoin(team)
{
	self endon("disconnect");
	self endon("joined_spectators");

	// wait here while the teams are configured
	while(!isDefined(self.pers["team"]) || !isDefined(game["allies"]) || !isDefined(game["axis"])) wait( [[level.ex_fpstime]](0.05) );

	// pass server info
	if(!isDefined(self.pers["skipserverinfo"]))
	{
		self notify("menuresponse", game["menu_serverinfo"], "team");
		wait( [[level.ex_fpstime]](0.5) ); // DO NOT REMOVE, CHANGE OR DISABLE!
	}

	// choose a team
	self notify("menuresponse", game["menu_team"], team);
	self waittill("joined_team");
	if(self.pers["team"] == "allies") level.ex_mbots_allies++;
		else if(self.pers["team"] == "axis") level.ex_mbots_axis++;

	// start monitoring the bot. That thread terminates if bot joins spectators
	self thread botStart();

	// keep looping here until all the weapons have been selected
	for(;;)
	{
		wait( [[level.ex_fpstime]](0.05) );
		if(self.pers["team"] == "allies") weapon = level.ex_botweapons_allies[randomInt(level.ex_botweapons_allies.size)];
			else weapon = level.ex_botweapons_axis[randomInt(level.ex_botweapons_axis.size)];

		// check for weapon class only
		if(level.ex_wepo_class)
		{
			switch(level.ex_wepo_class)
			{
				case 1: classname = "pistol"; break;       // pistol only
				case 2: classname = "sniper"; break;       // sniper only
				case 3: classname = "mg"; break;           // mg only
				case 4: classname = "smg"; break;          // smg only
				case 5: classname = "rifle"; break;        // rifle only
				case 6: classname = "boltrifle"; break;    // bolt action rifle only
				case 7: classname = "shotgun"; break;      // shotgun only
				case 8: classname = "rl"; break;           // panzerschreck only
				case 9: classname = "boltsniper"; break;   // bolt and sniper only
				case 10: classname = "knife"; break;     // knives only
				default: classname = "boltsniper"; break;  // boltsniper fallback
			}

			// if the weapon is not part of this class, shouldn't happen, but just in case!
			if(!isWeaponType(weapon, classname)) continue;
			
		}

		// check if this is also a valid bot weapon
		bweapon = maps\mp\gametypes\_weapons::getMBotWeapon(weapon);
		if(level.ex_testclients_diag) logprint(self.name + " (" + self.pers["team"] + ") trying to select weapon " + weapon + " (" + bweapon + ")\n");
		if(bweapon == "dummy1_mp") continue;

		// force secondary weapon to "none" if secondary system is on
		if(level.ex_wepo_secondary && isDefined(self.pers["weapon"]))
		{
			self.sweapon = "none";
			self notify("menuresponse", game["menu_weapon_" + self.pers["team"] +"_sec"], "none");
			break;
		}

		// only allow pistol as primary on pistol-only class
		if(isWeaponType(weapon, "pistol") && level.ex_wepo_class != 1) continue;

		// if the weapon limiter is on, and this weapon is not allowed, get another one!
		if(!level.ex_wepo_class && level.ex_wepo_limiter && !level.weapons[weapon].allow) continue;

		// choose a primary weapon
		if(!isDefined(self.pers["weapon"]))
		{
			self.oweapon = weapon;
			self.pweapon = bweapon;
			self notify("menuresponse", game["menu_weapon_" + self.pers["team"]], weapon);
			wait( [[level.ex_fpstime]](1) );
			if(level.ex_testclients_diag && isDefined(self.pers["weapon"]))
				logprint(self.name + " (" + self.pers["team"] + ") selected primary weapon " + weapon + "\n");

			if(!level.ex_wepo_secondary && isDefined(self.pers["weapon"])) break;
		}
	}
}

botLoadout()
{
	currpweapon = self getweaponslotweapon("primary");
	self takeweapon(currpweapon);
	self.pers["weapon"] = self.pweapon;
	self setWeaponSlotWeapon("primary", self.pers["weapon"]);
	self setweaponslotammo("primary", 999);
	self setweaponslotclipammo("primary", 999);
	self setspawnweapon(self.pers["weapon"]);
	self.pclipammo = self getWeaponSlotClipAmmo("primary");
}

addBotWeapon(weapon, ammo, clipammo)
{
	if(isDefined(self.pers["weapon"]))
	{
		currpweapon = self getweaponslotweapon("primary");
		self takeweapon(currpweapon);
		self giveweapon(weapon);
		self setweaponslotammo("primary", ammo);
		self setweaponslotclipammo("primary", clipammo);
		self setspawnweapon(weapon);
		self switchtoweapon(weapon);
	}
}

//------------------------------------------------------------------------------
// Bot main logic
//------------------------------------------------------------------------------
botStart()
{
	self endon("disconnect");
	self endon("joined_spectators");

	for(;;)
	{
		self waittill("spawned_player");
		wait( [[level.ex_fpstime]](0.2) );
		self thread botMainLoop();
	}
}

botMainLoop()
{
	self endon("disconnect");
	self endon("ex_dead");
	self endon("spawned");

	self freezecontrols(true);
	wait( [[level.ex_fpstime]](1) );
	self.botorg = spawn("script_origin", self.origin);
	self linkto(self.botorg);

	self.state = "start";
	self.alert = false;
	self.next = undefined;
	self.skiprotate = undefined;

	self thread checkEnemy();

	while(isAlive(self))
	{
		switch(self.state)
		{
			case "idle":
				if(!self.alert)
				{
					if(self.next.type != "w")
						self mbotStopLoopSound();

					switch(self.next.type)
					{
						case "w":
							self.state = "move";
							self.next = self getNextNode();
							self thread goToNode(self.next.origin);
							break;

						case "g":
							if(randomInt(100) < 50)
							{
								self.state = "throw";
								self thread throwFragGrenade();
							}
							else if(randomInt(100) < 10)
							{
								self.state = "throw";
								self thread throwSmokeGrenade();
							}
							else self.state = "done";
							break;

						case "f":
							self.state = "fall";
							self thread fallGravity();
							break;

						case "c":
							if(randomInt(3))
							{
								self.state = "camp";
								time = randomInt(8) + 2;
								self thread makeCamp(time);
							}
							else self.state = "done";
							break;

						case "j":
							self.state = "jump";
							self thread jumpGravity();
							break;

						case "m":
							self.state = "mantle";
							self thread doMantle();
							break;

						case "l":
							self.state = "climb";
							self thread climbUp();
							break;
					}
				}
				else wait( [[level.ex_fpstime]](1) );
				break;

			case "move":
				if(checkBotCollision())
				{
					self stopMoving();

					self.pclipammo = self getweaponslotclipammo("primary");
					self setweaponslotammo("primary", 0);
					self setweaponslotclipammo("primary", 0);
					self freezecontrols(false);

					for(;;)
					{
						self.state = "camp";

						if(checkBotCollision())
						{
							n = int( (randomFloat(1) + 0.1) / 0.05);
							for(i = 0; i < n; i++) wait( [[level.ex_fpstime]](0.05) );
						}
						else
						{
							self freezecontrols(true);
							n = int( (randomFloat(1) + 0.1) / 0.05);
							for(i = 0; i < n; i++) wait( [[level.ex_fpstime]](0.05) );
							if(!checkBotCollision()) break;
							self freezecontrols(false);
						}
					}

					self freezecontrols(true);
					self givemaxammo(self.pers["weapon"]);
					self setweaponslotclipammo("primary", self.pclipammo);

					self.state = "move";
					self thread goToNode(self.next.origin);
					break;
				}

				if(self.alert)
				{
					self stopMoving();

					for(;;)
					{
						if(self.alert)
						{
							n = int( (randomFloat(1) + 0.1) / 0.05);
							for(i = 0; i < n; i++) wait( [[level.ex_fpstime]](0.05) );
						}
						else
						{
							n = int( (randomFloat(1) + 0.1) / 0.05);
							for(i = 0; i < n; i++) wait( [[level.ex_fpstime]](0.05) );
							if(!self.alert) break;
						}
					}

					self.state = "move";
					self thread goToNode(self.next.origin);
					break;
				}
				wait( [[level.ex_fpstime]](0.01) );
				break;

			case "done":
				self.state = "move";
				self.next = self getNextNode();
				self thread goToNode(self.next.origin);
				break;

			case "start":
				self.state = "move";
				self.next = self getNextNode();
				self thread goToNode(self.next.origin);
				// unhide mbots (hidden in _ex_main::exprespawn)
				self show();
				break;

			default:
				wait( [[level.ex_fpstime]](0.01) );
				break;
		}
	}
}

checkEnemy()
{
	self endon("disconnect");
	self endon("ex_dead");
	self endon("spawned");

	self.alert = false;
	self.enemy = undefined;
	target = undefined;
	waittarget = 0;
	waitanim = 0;

	self.pclipammo = self getweaponslotclipammo("primary");

	while(isAlive(self))
	{
		wait( [[level.ex_fpstime]](level.botwaittime + waitanim) );

		if(!isAlive(self) || !isDefined(self.mark)) return;

		waitanim = 0;
		eye = self.mark[0].origin;
		newtarget = undefined;
		target_mark = undefined;
		friendAttacker  = undefined;
		friendDist = 99999;
		if(isDefined(target) && target.pers["team"] != self.pers["team"])
			target_mark = self visibleMark(target, true);

		if(isDefined(target_mark))
		{
			newtarget = target;
		}
		else
		{
			players = getentarray("player", "classname");
			for(i = 0; i < players.size; i++)
			{
				player = players[i];
				if(player.pers["team"] != self.pers["team"])
				{
					target_mark = self visibleMark(player, true);
					if(isDefined(target_mark))
					{
						newtarget = player;
						break;
					}
				}
				else if(isDefined(player.isbot))
				{
					dist = distance(self.origin, player.origin);
					if(dist < 1000 && dist < friendDist && isDefined(player.alert) && player.alert)
					{
						target_mark = self visibleMark(player, false);
						if(isDefined(target_mark))
						{
							friendAttacker = player;
							friendDist = dist;
						}
					}
				}
			}
		}

		target = newtarget;
		if(isDefined(target))
		{
			if(self.state == "jump" || self.state == "mantle" || self.state == "climb")
			{
				waitanim = 2;
				continue;
			}

			if(!isDefined(self.enemy) || self.enemy != target)
			{
				self.skiprotate = undefined;
				stopRotate();
				rotwait = randomFloat(1-(level.ex_mbot_skill*0.1))+0.1;
				self doRotateOrg(target_mark, rotwait);
				wait( [[level.ex_fpstime]](rotwait) );
			}
			self.enemy = target;
			if(!self.alert)
			{
				if(self.state == "camp")
				{
					self givemaxammo(self.pers["weapon"]);
					self setweaponslotclipammo("primary", self.pclipammo);
				}
				self.alert = true;
			}

			if(self.state == "idle" || self.state == "move" || self.state == "camp")
			{
				vtarget = vectorNormalize(target_mark - eye);
				self.pclipammo = self getweaponslotclipammo("primary");
				self setPlayerAngles(vectorToAngles(vtarget));
				self freezecontrols(false);
			}

			waittarget = randomInt(5)+5;
		}
		else
		{
			if(self.alert)
			{
				if(!waittarget || (isDefined(self.enemy) && !isAlive(self.enemy)))
				{
					self.enemy = undefined;
					waittarget = 0;
				}
				else
				{
					waittarget--;
					continue;
				}
				self.alert = false;
				self.pclipammo = self getweaponslotclipammo("primary");

				if(self.state != "camp")
				{
					self freezecontrols(true);
				}
				else
				{
					self setweaponslotammo("primary", 0);
					self setweaponslotclipammo("primary", 0);
				}
			}
			else if(isDefined(friendAttacker) && (self.state == "move" || self.state == "camp"))
			{
				self notify("stoprotate");
				self notify("endrotate");
				wait( [[level.ex_fpstime]](0.01) );

				angles = friendAttacker getplayerangles();
				self doRotateAng(angles, randomFloat(1-(level.ex_mbot_skill*0.1))+0.1);
				self thread blockRotate(1);
			}
		}
	}
}

visibleMark(player, checkallsurface)
{
	self endon("disconnect");
	self endon("ex_dead");
	self endon("spawned");

	if(!isDefined(player))
		return undefined;

	if(!isalive(player) || !isDefined(player.mark))
		return undefined;

	bot_maxdist = level.ex_mbot_maxdist;
	if(bot_maxdist < 100) bot_maxdist = 100;

	eye = self.mark[0].origin;
	dist = distance(eye, player.mark[0].origin);
	if(dist > bot_maxdist)
		return undefined;

	dist = vectornormalize(player.origin - eye);
	angles = self getplayerangles();
	vfwd = anglestoforward(angles);
	dot = vectordot(vfwd, dist);
	if(dot > 1) dot = 1;
	viewangle = acos(dot);
	if(isDefined(self.enemy))
	{
		if(viewangle > 45)
			return undefined;
	}
	else
	{
		if(viewangle > level.ex_mbot_viewangle)
			return undefined;
	}

	for(j = 0; j < player.mark.size; j++)
	{
		if(j == 0 && level.ex_mbot_skill <= 5) continue;

		if(sighttracepassed(eye, player.mark[j].origin, false, self))
		{
			trace = bullettrace(eye, player.mark[j].origin, true, self);
			if(checkallsurface && trace["surfacetype"] != "default" && trace["surfacetype"] != "none")
				return undefined;

			accuracy = (randomFloat(10-(level.ex_mbot_skill*1)), 0, 0);
			return player.mark[j].origin + accuracy;
		}
	}
	return undefined;
}

checkBotCollision()
{
	self endon("disconnect");
	self endon("ex_dead");
	self endon("spawned");

	players = getentarray("player", "classname");
	for(i = 0; i < players.size; i++)
	{
		player = players[i];
		dist = distance(self.origin, player.origin);

		if(player == self || !isalive(player) || dist > 96)
			continue;

		if(dist < 32)
			return false;
		else if(dist < 64)
		{
			if(isDefined(player.next) && self.next.origin == player.next.origin && closer(self.next.origin, player.origin, self.origin))
				return true;
		}

		if(self.next.next.size)
		{
			next = level.wp[self.next.next[0]];
			if(isDefined(player.next) && next.origin == player.next.origin)
				return true;
		}
	}

	return false;
}

getNextNode()
{
	self endon("disconnect");
	self endon("ex_dead");
	self endon("spawned");

	next = undefined;

	self.skipmove = undefined;
	if(isDefined(self.gotostart) && isDefined(level.startwp))
	{
		next = level.wp[level.startwp];
		self.gotostart = undefined;
		self.skipmove = true;
		self.botorg moveto(next.origin, 0.01, 0, 0);
		self.botorg waittill("movedone");
		return next;
	}

	if(isDefined(level.startwp) && (!isDefined(self.next) || (isDefined(level.endwp) && self.next == level.wp[level.endwp] && self.next != level.wp[level.startwp])))
	{
		next = level.wp[level.startwp];
		self.skipmove = true;
		self.botorg moveto(next.origin, 0.01, 0, 0);
		self.botorg waittill("movedone");
		return next;
	}

	if(isDefined(self.next) && self.next.next.size != 0)
	{
		n = randomInt(self.next.next.size);
		next = level.wp[self.next.next[n]];
	}
	else
	{
		if(isDefined(level.startwp) && self.next != level.wp[level.startwp])
		{
			next = level.wp[level.startwp];
			self.skipmove = true;
			self.botorg moveto(next.origin, 0.01, 0, 0);
			self.botorg waittill("movedone");
			return next;
		}

		next = self findStartNode();
	}

	return next;
}

findStartNode()
{
	if(isDefined(level.spawnpoints))
	{
		for(i = 0; i < level.spawnpoints.size; i++)
		{
			if(distance(self.origin, level.spawnpoints[i].origin) < 16)
				return level.wp[i];
			wait( [[level.ex_fpstime]](0.01) );
		}
	}

	next = undefined;
	for (;;)
	{
		next = level.wp[randomInt(level.spawnpoints.size)];
		if(!isDefined(self.next) || self.next != next)
			break;
	}

	return next;
}

goToNode(nodeOrg)
{
	self endon("disconnect");
	self endon("ex_dead");
	self endon("spawned");
	self endon("stopmove");

	if(nodeOrg == self.botorg.origin || isDefined(self.skipmove))
	{
		self.skipmove = undefined;
		self.state = "idle";
		return;
	}

	dist = distance(self.botorg.origin, nodeOrg);
	moveTime = dist / level.ex_mbot_speed;
	target = vectorNormalize(nodeOrg - self.origin);
	angles = vectorToAngles(target);

	self stopRotate();
	self thread doRotateOrg(nodeOrg, randomFloat(2-(level.ex_mbot_skill*0.2))+0.1);
	self thread mbotPlayLoopSound("step_bot_run", .43);
	self.botorg moveto(nodeOrg, moveTime, 0, 0);

	while(1)
	{
		if(distance(self.origin, nodeOrg) < 32)
		{
			self.state = "idle";
			return;
		}
		wait( [[level.ex_fpstime]](0.1) );
	}
}

stopMoving()
{
	self endon("disconnect");
	self endon("ex_dead");
	self endon("spawned");

	self notify("stopmove");

	self.skiprotate = undefined;
	self stopRotate();
	self mbotStopLoopSound();
	self.botorg moveto((self.origin + (1, 1, 0)), 0.01, 0, 0);
	wait( [[level.ex_fpstime]](0.1) );
}

throwFragGrenade()
{
	self endon("disconnect");
	self endon("ex_dead");
	self endon("spawned");

	if(self.botgrenadecount > 0)
	{
		self.skiprotate = undefined;
		self stopRotate();
		self doRotateAng(self.next.angles, 0.1);
		wait( [[level.ex_fpstime]](0.1) );

		self addBotWeapon(self.botgrenade, 999, 999);
		self thread extreme\_ex_utils::execClientCommand("-attack; +frag; -frag");
		self switchtooffhand(self.pers["fragtype"]);

		for(i = 0; i < 20; i++)
		{
			self freezecontrols(false);
			wait( [[level.ex_fpstime]](0.05) );
		}

		self freezecontrols(true);
		self.botgrenadecount--;
		self setWeaponClipAmmo(self.pers["fragtype"], self.botgrenadecount);
		self addBotWeapon(self.pers["weapon"], 999, self.pclipammo);
	}

	self.state = "done";
}

throwSmokeGrenade()
{
	self endon("disconnect");
	self endon("ex_dead");
	self endon("spawned");

	if(self.botsmokecount > 0)
	{
		self.skiprotate = undefined;
		self stopRotate();
		self doRotateAng(self.next.angles, 0.1);
		wait( [[level.ex_fpstime]](0.1) );

		self addBotWeapon(self.botsmoke, 999, 999);
		self thread extreme\_ex_utils::execClientCommand("-attack; +smoke; -smoke");
		self switchtooffhand(self.pers["smoketype"]);

		for(i = 0; i < 20; i++)
		{
			self freezecontrols(false);
			wait( [[level.ex_fpstime]](0.05) );
		}

		self freezecontrols(true);
		self.botsmokecount--;
		self setWeaponClipAmmo(self.pers["smoketype"], self.botsmokecount);
		self addBotWeapon(self.pers["weapon"], 999, self.pclipammo);
	}

	self.state = "done";
}

fallGravity()
{
	self endon("disconnect");
	self endon("ex_dead");
	self endon("spawned");

	if(isDefined(self.next.next[0]))
	{
		destOrg = level.wp[self.next.next[0]].origin;

		ang = vectorToAngles((vectorNormalize(destOrg - self.next.origin)));
		vel = anglesToForward((0.0, ang[1], 0.0));

		dst = distance((self.next.origin[0], self.next.origin[1], 0), (destOrg[0], destOrg[1], 0));
		height = self.next.origin[2] - destOrg[2];
		sqr = getVelSqr(height);
		dst = (dst) / sqr;
		
		if(dst > 240) dst = 240;
		vel = (vel[0]*dst, vel[1]*dst, 0);
		
		self stopRotate();
		self thread doRotateAng((30.0, ang[1], 0.0), 0.1);

		self.botorg movegravity(vel, sqr);
		self.botorg waittill("movedone");
		self thread mbotPlaySurface("Land_");
		self.botorg moveto(destOrg, 0.01, 0, 0);
		self.botorg waittill("movedone");
	}

	self.state = "done";
}

jumpGravity()
{
	self endon("disconnect");
	self endon("ex_dead");
	self endon("spawned");

	if(isDefined(self.next.next[0]))
	{
		destOrg = level.wp[self.next.next[0]].origin;

		vmax = 240;
		ang = vectorToAngles((vectorNormalize(destOrg - self.next.origin)));
		ang = (0, ang[1], 0);
		vel = anglesToForward(ang);
		vel = (vel[0], vel[1], 1);

		dst = distance(destOrg, self.next.origin);
		if(destOrg[2] >= self.next.origin[2])
		{
			dst = dst * 1.6;
			if(dst > vmax)
				dst = vmax;
			vel = (dst*vel[0], dst*vel[1], vmax);
		}
		else
		{
			h = self.next.origin[2] - destOrg[2];
			dst = dst*1.6 - h*(1.25+h/(vmax*10));
			if(dst > vmax)
				dst = vmax;
			vel = (dst*vel[0], dst*vel[1], vmax);
		}

		self.skiprotate = undefined;
		self stopRotate();
		self thread doRotateAng(ang, 0.5);

		self.botorg movegravity(vel, 10);
		
		if(destOrg[2] >= self.next.origin[2])
		{
			wait( [[level.ex_fpstime]](0.5) );
			while(self.botorg.origin[2] > destOrg[2]) wait( [[level.ex_fpstime]](0.01) );
		}
		else
		{
			wait( [[level.ex_fpstime]](0.5) );
			while(self.botorg.origin[2] > destOrg[2]+32 ) wait( [[level.ex_fpstime]](0.01) );
		}

		self thread mbotPlaySurface("Land_");
		self.botorg moveto(destOrg, 0.1, 0, 0);
		self.botorg waittill("movedone");
	}

	self.state = "done";
}

doMantle()
{
	self endon("disconnect");
	self endon("ex_dead");
	self endon("spawned");

	if(isDefined(self.next.next[0]))
	{
		self.skiprotate = undefined;
		self stopRotate();
		wait( [[level.ex_fpstime]](0.1) );
	
		next = level.wp[self.next.next[0]];
		dist = distance(self.next.origin, next.origin);
		ang = vectorToAngles((vectorNormalize(next.origin - self.next.origin)));
		vec = anglesToForward((-80.0, ang[1], 0.0));
		vec = maps\mp\_utility::vectorScale(vec, dist);
		destOrg = self.next.origin + vec;
		self setplayerangles((20, ang[1], 0));

		self addBotWeapon("mantle_up_bot", 0, 0);
		self.botorg playSound("bot_raise_weap");
		wait( [[level.ex_fpstime]](0.1) );

		moveTime = distance(self.next.origin, destOrg)/100;
		self.botorg moveto(destOrg, moveTime, 0, 0);
		self.botorg waittill("movedone");

		if(isDefined(self.next.mode) && self.next.mode == 1 && isDefined(next.next[0]))
		{
			self.next = next;

			vec = anglesToForward((10, ang[1], 0));
			vec = maps\mp\_utility::vectorScale(vec, 32);
			destOrg = self.next.origin + vec;
			moveTime = distance(self.botorg.origin, destOrg)/100;

			self addBotWeapon("mantle_over_bot", 0, 0);
			wait( [[level.ex_fpstime]](0.1) );

			self.botorg moveto(destOrg, moveTime, 0, 0);
			self.botorg waittill("movedone");

			self addBotWeapon(self.pers["weapon"], 999, self.pclipammo);
			self.state = "fall";

			destOrg = level.wp[self.next.next[0]].origin + (0, 0, 20);
			self.botorg movegravity((0.0, 0.0, 0.0), 10);
			while(self.botorg.origin[2] > destOrg[2]) wait( [[level.ex_fpstime]](0.01) );

			self.botorg moveto((destOrg - (0, 0, 20)), .075, 0, 0);
			self.botorg playsound("bot_land");
			self.botorg waittill("movedone");
		}
		else
		{
			self addBotWeapon(self.pers["weapon"], 999, self.pclipammo);
		}
	}

	self.state = "done";
}

climbUp()
{
	self endon("disconnect");
	self endon("ex_dead");
	self endon("spawned");

	if(isDefined(self.next.next[0]))
	{
		next = level.wp[self.next.next[0]];

		height = next.origin[2] - self.next.origin[2] - 10;
		if(height < 10)
		{
			self.state = "done";
			return;
		}

		destOrg = self.next.origin + (0.0, 0.0, height);
		moveTime = distance(self.next.origin, destOrg)/100;

		self.skiprotate = undefined;
		self stopRotate();
		wait( [[level.ex_fpstime]](0.1) );

		ang = vectorToAngles((vectorNormalize(next.origin - self.next.origin)));
		self setplayerangles((-50, ang[1], 0));

		self.botorg playSound("bot_raise_weap");
		self addBotWeapon("climb_up_bot", 0, 0);
		wait( [[level.ex_fpstime]](0.1) );

		self thread mbotPlayLoopSound("step_bot_climb", .4);
		self.botorg moveto(destOrg, moveTime, 0, 0);
		self.botorg waittill("movedone");

		self mbotStopLoopSound();

		self addBotWeapon(self.pers["weapon"], 999, self.pclipammo);
	}

	self.state = "done";
}

makeCamp(time)
{
	self endon("disconnect");
	self endon("ex_dead");
	self endon("spawned");

	if(isDefined(self.next.angles))
	{
		self stopRotate();
		self doRotateAng(self.next.angles, 0.5);
	}

	self.pclipammo = self getweaponslotclipammo("primary");
	self setweaponslotammo("primary", 0);
	self setweaponslotclipammo("primary", 0);

	self freezecontrols(false);

	n = int(time / 0.05);
	for(i = 0; i < n; i++)
	{
		wait( [[level.ex_fpstime]](0.05) );
		if(isDefined(self.gotostart)) break;
	}

	if(!self.alert)
	{
		self freezecontrols(true);
		self givemaxammo(self.pers["weapon"]);
		self setweaponslotclipammo("primary", self.pclipammo);
		wait( [[level.ex_fpstime]](1) );
	}

	self.state = "done";
}

mbotPlayLoopSound(alias, interval)
{
	self endon("stoploopsound");

	if(!isDefined(self.isPlayingLoopSound) || !self.isPlayingLoopSound)
	{
		self.isPlayingLoopSound = true;

		if(alias == "step_bot_run")
		{
			while(isDefined(self.botorg))
			{
				self thread mbotPlaySurface("step_walk_");
				wait( [[level.ex_fpstime]](interval) );
			}
		}
		else
		{
			while(isDefined(self.botorg))
			{
				self.botorg playSound(alias);
				wait( [[level.ex_fpstime]](interval) );
			}
		}
	}
}

mbotPlaySurface(alias)
{
	trace=bulletTrace(self.origin, self.origin-(0,0,512), false, self);
	if(trace["surfacetype"] == "none")
		self playsound(alias + "default");
	else
		self playsound(alias + trace["surfacetype"]);
}

mbotStopLoopSound()
{
	self.isPlayingLoopSound = false;
	self notify("stoploopsound");
}

playerDamage(eAttacker, iDamage)
{
	self endon("ex_dead");

	if(!isDefined(self.isbot) || !isPlayer(eAttacker) || self == eAttacker) return;

	if(!isDefined(self.state) || self.state == "jump" || self.state == "mantle" || self.state == "climb") return;

	if(isDefined(self.alert) && !self.alert)
	{
		self notify("stoprotate");
		self notify("endrotate");
		wait( [[level.ex_fpstime]](0.01) );

		self doRotateOrg(eAttacker.origin, randomFloat(1-(level.ex_mbot_skill*0.1))+0.1);
		self thread blockRotate(1);
	}
}

playerKilled()
{
	if(isDefined(self.isbot))
	{
		self.skiprotate = undefined;
		self stopRotate();
		self mbotStopLoopSound();
		self unlink();
		if(isDefined(self.botorg)) self.botorg delete();
	}

	if(isDefined(self.mark))
	{
		for(i = 0; i < self.mark.size; i++) 
		{
			if(isDefined(self.mark[i]))
			{
				self.mark[i] unlink();
				self.mark[i] delete();
			}
		}

		self.mark = undefined;
	}
}

doRotateOrg(target, roundsec)
{
	self endon("ex_dead");
	self endon("stoprotate");

	if(isDefined(self.skiprotate)) return;

	newangles = vectorToAngles(vectorNormalize(target - self.origin));
	self thread doRotateAng(newangles, roundsec);
}

doRotateAng(newangles, roundsec)
{
	self endon("ex_dead");
	self endon("stoprotate");

	if(!isDefined(newangles)) return;
	if(isDefined(self.skiprotate)) return;

	iter = 20;

	iterinc = 360/iter;
	iterwait = roundsec/iter;

	angles = vectorToAngles(anglestoforward(self getplayerangles()));
	newangles = vectorToAngles(anglestoforward(newangles));

	yaw = angleSubtract(newangles[1], angles[1]);
	pitch = angleSubtract(newangles[0], angles[0]);

	if(yaw < 0)
		dyaw = iterinc * (-1);
	else
		dyaw = iterinc;

	if(pitch < 0)
		dpitch = iterinc * (-1);
	else
		dpitch = iterinc;

	iyaw = abs(yaw) / iterinc;
	ipitch = abs(pitch) / iterinc;

	while(1)
	{
		if(iyaw > 1)
			angles = anglesAdd(angles, (0, dyaw, 0));
		if(ipitch > 1)
			angles = anglesAdd(angles, (dpitch, 0, 0));

		self setplayerangles(angles);

		if(iyaw > 1)
			iyaw -= 1;
		if(ipitch > 1)
			ipitch -= 1;

		if(iyaw <= 1 && ipitch <= 1) break;
		wait( [[level.ex_fpstime]](iterwait) );
	}
	self setplayerangles(newangles);
	self notify("endrotate");
}

stopRotate()
{
	if(isDefined(self.skiprotate)) return;
	self notify("stoprotate");
	wait( [[level.ex_fpstime]](0.01) );
}

blockRotate(time)
{
	self endon("ex_dead");
	self notify("stopblockrotate");
	self endon("stopblockrotate");

	self.skiprotate = true;
	wait( [[level.ex_fpstime]](time) );
	self.skiprotate = undefined;
}

//------------------------------------------------------------------------------
// Utils
//------------------------------------------------------------------------------
createBotWeaponsArray()
{
	classname = undefined;
	allteamweapons = false;
	allmodernweapons = false;

	level.ex_botweapons_allies = [];
	level.ex_botweapons_axis = [];

	// build the botweapon arrays
	for(i = 0; i < level.weaponnames.size; i++)
	{
		weaponname = level.weaponnames[i];

		if(level.weapons[weaponname].classname == "sniperlr") continue;

		addtoteam = false;
		addtobothteams = false;

		if(weaponname != "fraggrenade" && weaponname != "smokegrenade" && weaponname != "binoculars_mp")
		{
			if(level.ex_all_weapons)
			{
				addtobothteams = true;
			}
			else if(level.ex_modern_weapons)
			{
				switch(level.ex_wepo_class)
				{
					case 1: classname = "pistol"; break; // pistol only
					case 2: classname = "sniper"; break; // sniper only
					case 3: classname = "mg"; break; // mg only
					case 4: classname = "smg"; break; // smg only
					case 7: classname = "shotgun"; break; // shotgun only
					default: allmodernweapons = true; break; // all modern menus
				}

				if(allmodernweapons || isWeaponType(weaponname, classname)) // all modern weapons, or matching class weapon
				{
					addtobothteams = true;
				}
			}
			else
			{
				switch(level.ex_wepo_class)
				{
					case 1: classname = "pistol"; break;     // pistol only
					case 2: classname = "sniper"; break;     // sniper only
					case 3: classname = "mg"; break;         // mg only
					case 4: classname = "smg"; break;        // smg only
					case 5: classname = "rifle"; break;      // rifle only
					case 6: classname = "boltrifle"; break;  // bolt action rifle only
					case 7: classname = "shotgun"; break;    // shotgun only
					case 8: classname = "rl"; break;         // panzerschreck/bazooka only
					case 9: classname = "boltsniper"; break; // bolt and sniper only
					case 10: classname = "knife"; break;     // knives only
					default: allteamweapons = true; break;   // all team weapons
				}

				if(allteamweapons) // all team weapons for allies and axis
				{
					addtoteam = true;
				}
				else // weapon class (secondary system disabled)
				{
					if(level.ex_wepo_team_only) // team based, only add weapons of this type that match the game allies and the game axis
					{
						if(isWeaponType(weaponname, classname)) addtoteam = true;
					}
					else // not team based so add all weapons of this type
					{
						if(isWeaponType(weaponname, classname))
						{
							addtobothteams = true;
						}
					}

					if(level.ex_wepo_sidearm && isWeaponType(weaponname, "pistol")) // if sidearm (pistol) is allowed add it
					{
						if(level.ex_wepo_team_only) // only add pistols that match the game allies and the game axis
						{
							if(isWeaponType(weaponname, classname)) addtoteam = true;
						}
						else // not team based so add all pistols
						{
							addtobothteams = true;
						}
					}
				}
			}

			if(addtobothteams || (addtoteam && isWeaponType(weaponname, game["allies"]))) level.ex_botweapons_allies[level.ex_botweapons_allies.size] = weaponname;
			if(addtobothteams || (addtoteam && isWeaponType(weaponname, game["axis"]))) level.ex_botweapons_axis[level.ex_botweapons_axis.size] = weaponname;
		}
	}

	if(level.ex_testclients_diag)
	{
		logprint("*********************************\n");
		logprint("* Bot weapons debug begin       *\n");
		logprint("*********************************\n");

		for(i = 0; i < level.ex_botweapons_allies.size; i++)
			logprint("Allied bot weapon [" + i + "] = " + level.ex_botweapons_allies[i] + " \n");

		logprint("\n");

		for(i = 0; i < level.ex_botweapons_axis.size; i++)
			logprint("Axis bot weapon [" + i + "] = " + level.ex_botweapons_axis[i] + " \n");

		logprint("********************************\n");
		logprint("* BOT weapon debug end         *\n");
		logprint("********************************\n");

		logprint("\n");
	}
}

buildNodeInfo(file)
{
	println("MBOT: Loading bot waypoints from ", file, ".\n");
	f = openfile(file, "read");
	if(f == -1)
	{
		println("MBOT ERROR: File ", file, " not found.\n");
		return false;
	}

	freadln(f);
	s = fgetarg(f, 0);
	if(!isDefined(s) || s != "mbotwp")
	{
		closefile(f);
		println("MBOT ERROR: Unknown file format.\n");
		return false;
	}

	i = 0;
	while(freadln(f) != -1)
	{
		s = fgetarg(f, 0);
		t = strtok(s, " ,");

		level.wp[i] = spawnstruct();
		level.wp[i].origin = (strToFlt(t[0]), strToFlt(t[1]), strToFlt(t[2]));

		switch(t[3])
		{
			case "w":
				level.wp[i].type = "w";
				level.wp[i].next = [];
				level.wp[i].stance = int(t[4]);

				for(k = 0; k < int(t[5]); k++)
					level.wp[i].next[k] = int(t[6+k]);

				if(t.size == 9)
				{
					k += 6;
					level.wp[i].angles = (strToFlt(t[k]), strToFlt(t[k+1]), 0.0);
				}
				break;

			case "g":
				level.wp[i].type = "g";
				level.wp[i].next = [];
				level.wp[i].stance = int(t[4]);

				k = 0;
				for(k = 0; k < int(t[5]); k++)
					level.wp[i].next[k] = int(t[6+k]);

				k += 6;
				level.wp[i].angles = (strToFlt(t[k]), strToFlt(t[k+1]), 0.0);

				break;

			case "f":
				level.wp[i].type = "f";
				level.wp[i].next = [];
				level.wp[i].stance = int(t[4]);

				for(k = 0; k < int(t[5]); k++)
					level.wp[i].next[k] = int(t[6+k]);
				break;

			case "c":
				level.wp[i].type = "c";
				level.wp[i].next = [];
				level.wp[i].stance = int(t[4]);

				k = 0;
				for(k = 0; k < int(t[5]); k++)
					level.wp[i].next[k] = int(t[6+k]);

				k += 6;
				level.wp[i].angles = (strToFlt(t[k]), strToFlt(t[k+1]), 0.0);
				break;

			case "j":
				level.wp[i].type = "j";
				level.wp[i].next = [];
				level.wp[i].stance = int(t[4]);

				for(k = 0; k < int(t[5]); k++)
					level.wp[i].next[k] = int(t[6+k]);
				break;

			case "m":
				level.wp[i].type = "m";
				level.wp[i].next = [];
				level.wp[i].stance = int(t[4]);

				k = 0;
				for(k = 0; k < int(t[5]); k++)
					level.wp[i].next[k] = int(t[6+k]);

				k += 6;
				level.wp[i].mode = int(t[k]);
				break;

			case "l":
				level.wp[i].type = "l";
				level.wp[i].next = [];
				level.wp[i].stance = int(t[4]);

				for(k = 0; k < int(t[5]); k++)
					level.wp[i].next[k] = int(t[6+k]);
				break;

			default:
				closefile(f);
				println("MBOT ERROR: ", file, (" has invalid waypoint #" + i + " (unknown type).\n"));
				return false;
		}
		i++;
	}
	closefile(f);

	if(i == 0)
	{
		println("MBOT ERROR: ", file, " has not data.\n");
		return false;
	}

	for(k = 0; k < level.wp.size; k++)
	{
		if(level.wp[k].next.size == 0)
			println("MBOT ERROR: ", file, (" has waypoint #" + k + " with no next waypoint.\n"));

		if((level.wp[k].type == "f" || level.wp[k].type == "j" || level.wp[k].type == "m" || level.wp[k].type == "l") && level.wp[k].next.size > 1)
			println("MBOT ERROR: ", file, (" has waypoint #" + k + " of type \"" + level.wp[k].type + "\" with more than one next waypoints"));
	}

	println("MBOT: Loaded.\n");
	return true;
}

strToFlt(str)
{
	if((!isDefined(str)) || (!str.size)) return(0);

	switch(str[0])
	{
		case "+" :
			sign = 1;
			offset = 1;
			break;
		case "-" :
			sign = -1;
			offset = 1;
			break;
		default :
			sign = 1;
			offset = 0;
			break;
	}

	str2 = getsubstr(str, offset);
	parts = strtok(str2, ".");

	intpart = strToInt(parts[0]);
	decpart = strToInt(parts[1]);

	if(decpart < 0) return(0);

	if(decpart)
		for(i = 0; i < parts[1].size; i ++)
			decpart = decpart / 10;

	return((intpart + decpart) * sign);
}

strToInt(str)
{
	if((!isDefined(str)) || (!str.size)) return(0);

	ctoi = [];
	ctoi["0"] = 0;
	ctoi["1"] = 1;
	ctoi["2"] = 2;
	ctoi["3"] = 3;
	ctoi["4"] = 4;
	ctoi["5"] = 5;
	ctoi["6"] = 6;
	ctoi["7"] = 7;
	ctoi["8"] = 8;
	ctoi["9"] = 9;

	switch(str[0])
	{
		case "+" :
			sign = 1;
			offset = 1;
			break;
		case "-" :
			sign = -1;
			offset = 1;
			break;
		default :
			sign = 1;
			offset = 0;
			break;
	}

	val = 0;

	for(i = offset; i < str.size; i ++)
	{
		switch(str[i])
		{
			case "0" :
			case "1" :
			case "2" :
			case "3" :
			case "4" :
			case "5" :
			case "6" :
			case "7" :
			case "8" :
			case "9" :
				val = val * 10 + ctoi[str[i]];
				break;
			default :
				return(0);
		}
	}

	return(val * sign);
}

abs(var)
{
	if(var < 0) var = var * (-1);
	return var;
}

angleSubtract(a1, a2)
{
	a = a1-a2;
	if(abs(a) > 180)
	{
		if(a < -180)
			a += 360;
		else if(a > 180)
			a -= 360;
	}
	return a;
}

anglesAdd(a1, a2)
{
	v = [];
	v[0] = a1[0] + a2[0];
	v[1] = a1[1] + a2[1];
	v[2] = a1[2] + a2[2];

	for (i=0; i<3; i++)
	{
		while (v[i] > 360)
			v[i] -= 360;
		while (v[i] < -360)
			v[i] += 360;
	}
	return (v[0], v[1], v[2]);
}

addVel()
{
	level.velsqr = [];
	addVelSqr(12, 0.17);
	addVelSqr(25, 0.25);
	addVelSqr(37, 0.31);
	addVelSqr(50, 0.36);
	addVelSqr(75, 0.44);
	addVelSqr(100, 0.51);
	addVelSqr(125, 0.56);
	addVelSqr(150, 0.62);
	addVelSqr(175, 0.67);
	addVelSqr(200, 0.71);
	addVelSqr(250, 0.8);
	addVelSqr(300, 0.88);
	addVelSqr(350, 0.94);
	addVelSqr(400, 1.01);
	addVelSqr(450, 1.07);
	addVelSqr(500, 1.13);
	addVelSqr(600, 1.21);
	addVelSqr(650, 1.29);
	addVelSqr(700, 1.34);
	addVelSqr(750, 1.38);
	addVelSqr(800, 1.43);
	addVelSqr(850, 1.5);
	addVelSqr(900, 1.51);
	addVelSqr(1000, 1.6);
}

addVelSqr(h, sqr)
{
	i = level.velsqr.size;
	level.velsqr[i] = spawnstruct();
	level.velsqr[i].h = h;
	level.velsqr[i].sqr = sqr;
}

getVelSqr(h)
{
	hprev = level.velsqr[0].h;
	for (i=1; i<level.velsqr.size; i++)
	{
		hcur = level.velsqr[i].h;
		if(h > hcur)
		{
			hprev = hcur;
			continue;
		}
		hcur = hprev + ((hcur - hprev) / 2);
		if(h < hcur)
			sqr = level.velsqr[i-1].sqr;
		else
			sqr = level.velsqr[i].sqr;
		return sqr;
	}

	return level.velsqr[i-1].sqr;
}

mapSupportsMBots(map, type)
{
	f = openfile(("mbot/" + map + "_" + type + ".wp"), "read");
	if(f != -1)
	{
		closefile(f);
		return true;
	}

	return false;
}

debugBot(logweap, procname)
{
	if(!isDefined(level.debugbotweapons)) return;
	//if(self.name != "bot1") return;

	logproc = true;
	if(!isDefined(procname)) logproc = false;

	if(logproc) logprint("\nWEAPON DEBUG: " + self.name + ", procedure " +  procname + "\n");
		else logprint("\nWEAPON DEBUG: " + self.name + "\n");

	if(logweap)
	{
		primary = self getWeaponSlotWeapon("primary");
		secondary = self getWeaponSlotWeapon("primaryb");
		current = self getCurrentWeapon();

		if(level.ex_wepo_secondary)
		{
			logprint("WEAPON DEBUG: self.pers[\"weapon1\"]: " + self.pers["weapon1"] + " -- actual primary " + primary + "\n");
			logprint("WEAPON DEBUG: self.pers[\"weapon2\"]: " + self.pers["weapon2"] + " -- actual secondary " + secondary + "\n");
		}
		else
			logprint("WEAPON DEBUG:  self.pers[\"weapon\"]: " + self.pers["weapon"] + " -- actual primary " + primary + " (secondary: " + secondary + ")\n");

		logprint("WEAPON DEBUG:       current weapon: " + current + "\n");
	}
}
