/*
* Follower by kolton
*
* you should set "LocalChat" in mode 2 for all chars:
*	Config.LocalChat.Enabled = true; // enable the LocalChat system
*	Config.LocalChat.Mode = 2; // 0 = disabled, 1 = chat from 'say' (recommended), 2 = all chat (for manual play)
*
* version: 29.02.2020
*
* silent-automated follower changes:
*	- silent follower will check the leader's act and will go to it.
*	- when leader makes tp the follower will try to use it and will precast/buff.
*	- the follower will go after leader in town, using his tp, and will do town activities (if autoTownChores = true).
*	- lines 986-1000 will stop HC chars, in order to allow the loot of their corpses. *** you should avoid the chat commands until you get successful loot.
*	- quiting/ending the game will be done using the random delay Config.QuitListDelay.
*	- check the additional commands: b, ancs, ancsoff, ai, map, restart, end, dist:x, gamble, cowopen, bosses (countess, andariel, summoner, duriel, mephisto, chaos, nihlathak, throne), say ...
*
* Commands:
*
* Main commands:
*	1 - take leader's tp from town / move to leader's town
*	2 - take leader's tp to town
*
*	3					| town manager
*	<charname> 3		|
*
*	p					| pick items, potions and open containers
*	<charname> p		|
*
*	c					| get softcore player corpse
*	<charname> c		|
*
*	s 					| toggle stop in town near stash. if outside of town followers will make own tp and stop near stash.
*	<charname> s		|
*
* Attack:
*	a 					| toggle attack
*	<charname> a		|
*
*	aon					| attack on
*	<charname> aon		|
*
*	aoff				| attack off
*	<charname> aoff		|
*
* Teleport:
*	*** characters without teleport skill will ignore tele command ***
*	tele				| toggle teleport
*	<charname> tele		|
*
*	tele on				| teleport on
*	<charname> tele on	|
*
*	tele off			| teleport off
*	<charname> tele off	|
*
* Skills:
*	all skill <skillid>			| change skill (refer to sdk\skills.txt)
*	<charname> skill <skillid>	|
*	<class> skill <skillid>		| change skill for all characters of certain class *** any part of class name will do *** for example: "sorc skill 36", "zon skill 0", "din skill 106"
*
* Auras:
*	all aura <skillid>			| change aura for paladins (refer to sdk\skills.txt)
*	<charname> aura <skillid>	|
*
* Town:
*	a2-5 - move to appropriate act (after quest) !NOTE: Disable 'no sound' or game will crash!
*	use a2,a3,a4,a5 chat commands after completing the last quest. trying to get next act/town without having wp yet will lead to Connection Interrupted
*	talk <npc name> - talk to a npc in town
*	ai					| toggle anti-idle
*	<charname> ai		|
*	ancs - prepare for ancients quest a5q5, overwrite the config settings with more potions and towncheck restricted
*	ancsoff - revert to char config from ancients config. *** or you can use .reload for every follower
*	gamble 				| start gambling
*	<charname> gamble	|
*
* Misc:
*	cow - enter red cow portal
*	<charname> cowopen	- this command requires the custom ...\CowOpen.js file (https://raw.githubusercontent.com/blizzhackers/documentation/master/kolbot/custom-scripts/CowOpen.js)
*
*	<charname> x - teleporter go to desired area and make tp | x = countess , andariel, summoner, duriel, mephisto, chaos, nihlathak(using HoP wp), throne
*
*	wp					| activate a nearby wp
*	<charname> wp		|
*
*	bo - barbarian precast
*	b - refresh buff all followers
*
*	tp				| make a TP. Needs a TP tome if not using custom libs.
*	<charname> tp	|
*
*	move				| move in a random direction based on player position (use if you're stuck by followers)
*	m					| 
*	<charname> m		|
*
*	dist:x	- minimum distance to leader. default value 6. you can set it in range 6 - 30
*
*	area: ... x: ... y: ... |  move to leader position in the same area *** leader should set a key in ToolsThread.js, like case 111: say("area: " + me.area + " x: " + me.x + " y: " + me.y); ***
*
*	map					| activate mh in the follower windows
*	<charname> map		| ** you should reload the char config where you don't want to see the already activated mh
*
*	reload - reload script instructions. Use only in case of emergency, or after editing character config.
*
*	quit				| exit game
*	<charname> quit		|
*
*	restart				| restart profiles
*	<charname> restart 	|
*
* 	end					| stop follower profile and release the key, after a random delay
*	<charname> end		|
*
* Communication between 2 teams, 2nd leader is set as follower for the 1st. *** usefull for getting experience when start a new low char(team) helped by 2nd high level team, cause you can change the distance between the teams (10-20).
*	say area	- the 2nd leader will announce his position
*	say x		| 2nd leader will repeat the command
*	say x y		|
*/

function FollowerSilent() {
	var i, j, stop, leader, leaderUnit, piece, skill, result, unit, player, coord, map, tick, ai, gold,
		commanders = [Config.Leader],
		attack = true,
		openContainers = true,
		classes = ["amazon", "sorceress", "necromancer", "paladin", "barbarian", "druid", "assassin"],
		action = "",
		charClass = classes[me.classid],
		BCDuration = 0,
		BCTick = 0,
		autoTownChores = true, // automatic town activities after arriving from field/areas
		field = false, // variable used in autoTownChores
		dist = 6, // distance to leader. It can be changed with chat "dist:x" by leader.
		logCharOnExit = true; // log items of the current char before exit

	// Override config values
	Config.LocalChat.Enabled = true;
	Config.LocalChat.Mode = 2;

	Config.TownCheck = true;
	Config.OpenChests = true;

	// Get closer to leader, or other player
	this.getCloser = function () {
		if (me.inTown) {
			return;
		}

		if (leaderUnit && getDistance(me.x, me.y, leaderUnit.x, leaderUnit.y) <= 65) {
			if (getDistance(me.x, me.y, leaderUnit.x, leaderUnit.y) > dist) {
				Pather.moveToUnit(leaderUnit, true);
			}
		} else if (!leaderUnit) {
			player = getUnit(0);

			if (player) {
				do {
					if (player.name !== me.name) {
						Pather.moveToUnit(player, true);

						break;
					}
				} while (player.getNext());
			}
		}
	};

	this.precast = function () {
		Precast.doPrecast(true);

		if (me.classid !== 4 || !me.getSkill(155, 0)) {

			return true;
		}

		BCDuration = (20 + me.getSkill(155, 1) * 10 + (me.getSkill(138, 0) + me.getSkill(149, 0)) * 5) * 1000;
		BCTick = getTickCount();

		return true;
	};

	// Change areas to where leader is
	this.checkExit = function (unit, area) {
		if (unit.inTown) {
			return false;
		}

		var i, target,
			exits = getArea().exits;

		for (i = 0; i < exits.length; i += 1) {
			if (exits[i].target === area) {
				return 1;
			}
		}

		if (unit.inTown) {
			target = getUnit(2, "waypoint");

			if (target && getDistance(me, target) < 20) {
				return 3;
			}
		}

		target = getUnit(2, "portal");

		if (target) {
			do {
				if (target.objtype === area) {
					Pather.usePortal(null, null, target);

					return 2;
				}
			} while (target.getNext());
		}

		// Arcane<->Cellar portal
		if ((me.area === 74 && area === 54) || (me.area === 54 && area === 74)) {
			Pather.usePortal(null);

			return 4;
		}

		// Arcane<->Canyon of the Magi
		if ((me.area === 74 && area === 46) || (me.area === 46 && area === 74)) {
			Pather.usePortal(null);

			return 4;
		}

		// Tal-Rasha's tomb->Duriel's lair
		if (me.area >= 66 && me.area <= 72 && area === 73) {
			Pather.useUnit(2, 100, area);

			return 4;
		}

		// Throne->Chamber
		if (me.area === 131 && area === 132) {
			target = getUnit(2, 563);

			if (target) {
				Pather.usePortal(null, null, target);

				return 4;
			}
		}

		return false;
	};

	// Talk to a NPC
	this.talk = function (name) {
		if (!me.inTown) {
			me.overhead("ÿc1Going to talk in town!");
			Town.goToTown();
		}

		if (typeof name === "string") {
			name = name.toLowerCase();
		} else {
			me.overhead("ÿc1No NPC name given.");

			return false;
		}

		var npc, names;

		switch (me.act) {
		case 1:
			names = [NPC.Gheed, NPC.Charsi, NPC.Akara, NPC.Kashya, NPC.Cain, NPC.Warriv];

			break;
		case 2:
			names = [NPC.Fara, NPC.Lysander, NPC.Greiz, NPC.Elzix, NPC.Jerhyn, NPC.Meshif, NPC.Drognan, NPC.Atma, NPC.Cain];

			break;
		case 3:
			names = [NPC.Alkor, NPC.Asheara, NPC.Ormus, NPC.Hratli, NPC.Cain];

			break;
		case 4:
			names = [NPC.Halbu, NPC.Tyrael, NPC.Jamella, NPC.Cain];

			break;
		case 5:
			names = [NPC.Larzuk, NPC.Malah, NPC.Qual_Kehk, NPC.Anya, NPC.Nihlathak, NPC.Cain];

			break;
		}

		if (names.indexOf(name) === -1) {
			me.overhead("ÿc1Invalid NPC.");

			return false;
		}

		if (!Town.move(name === NPC.Jerhyn ? "palace" : name)) {
			Town.move("portalspot");
			me.overhead("ÿc1Failed to move to town spot.");

			return false;
		}

		npc = getUnit(1);

		if (npc) {
			do {
				if (npc.name.replace(/ /g, "").toLowerCase().indexOf(name) > -1) {
					npc.openMenu();
					me.cancel();
					me.overhead("ÿc2Done talking.");

					return true;
				}
			} while (npc.getNext());
		}

		me.overhead("ÿc1NPC not found.");
		Town.move("portalspot");

		return false;
	};

	// Change act after completing last act quest
	this.changeAct = function (act) {
		var npc, preArea, target;

		preArea = me.area;

		switch (act) {
		case 2:
			if (me.area >= 40) {
				break;
			}

			Town.move(NPC.Warriv);

			npc = getUnit(1, 155);

			if (npc) {
				npc.openMenu();
				Misc.useMenu(0x0D36);
			}

			break;
		case 3:
			if (me.area >= 75) {
				break;
			}

			Town.move("palace");

			npc = getUnit(1, 201);

			if (npc) {
				npc.openMenu();
				me.cancel();
			}

			Town.move(NPC.Meshif);

			npc = getUnit(1, 210);

			if (npc) {
				npc.openMenu();
				Misc.useMenu(0x0D38);
			}

			break;
		case 4:
			if (me.area >= 103) {
				break;
			}

			if (me.inTown) {
				Town.move(NPC.Cain);

				npc = getUnit(1, 245);

				if (npc) {
					npc.openMenu();
					me.cancel();
				}

				Town.move("portalspot");
				Pather.usePortal(102, null);
			}

			delay(1500);

			target = getUnit(2, 342);

			if (target) {
				Pather.moveTo(target.x - 3, target.y - 1);
			}

			Pather.usePortal(null);

			break;
		case 5:
			if (me.area >= 109) {
				break;
			}

			Town.move(NPC.Tyrael);

			npc = getUnit(1, NPC.Tyrael);

			if (npc) {
				npc.openMenu();
				me.cancel();

				try {
					Pather.useUnit(2, 566, 109);
				} catch (a5e) {

				}
			}

			break;
		}

		delay(2000);

		while (!me.area) {
			delay(500);
		}

		if (me.area === preArea) {
			me.cancel();
			Town.move("portalspot");
			print("ÿc1Act change failed.");

			return false;
		}

		Town.move("portalspot");
		me.overhead("ÿc8Act change successful.");

		if (act === 2) {
			me.overhead("Don't forget to talk to Drognan after getting the Viper Amulet!");
		}

		return true;
	};

	this.nextArea = function (area) { // only the exceptions from area = ± 1

		switch (area) {
		case 1:
			return [39];
		case 2:
			return [8];
		case 3:
			return [9, 17];
		case 4:
			return [10, 38];
		case 6:
			return [11, 20];
		case 7:
			return [12, 26];
		case 8:
			return [2];
		case 9:
			return [3, 13];
		case 10:
			return [4, 5, 14];
		case 11:
			return [6, 15];
		case 12:
			return [7, 16];
		case 13:
			return [9];
		case 14:
			return [10];
		case 15:
			return [11];
		case 16:
			return [12];
		case 17:
			return [3, 18, 19];
		case 18:
			return [17];
		case 19:
			return [17];
		case 39:
			return [1];
		case 40:
			return [47, 50];
		case 41:
			return [55];
		case 42:
			return [56];
		case 43:
			return [62];
		case 44:
			return [65];
		case 45:
			return [58];
		case 46:
			return [66, 67, 68, 69, 70, 71, 72];
		case 55:
			return [41, 59];
		case 56:
			return [42];
		case 57:
			return [60];
		case 58:
			return [45, 61];
		case 59:
			return [55];
		case 60:
			return [57];
		case 61:
			return [58];
		case 62:
			return [43];
		case 65:
			return [44];
		case 66:
		case 67:
		case 68:
		case 69:
		case 70:
		case 71:
		case 72:
			return [46, 73];
		case 76:
			return [84, 85];
		case 78:
			return [86, 88];
		case 80:
			return [92, 94, 95];
		case 81:
			return [92, 96, 97];
		case 82:
			return [98, 99];
		case 83:
			return [100];
		case 84:
		case 85:
			return [76];
		case 86:
		case 88:
			return [78];
		case 87:
			return [90];
		case 89:
			return [91];
		case 90:
			return [87];
		case 91:
			return [89];
		case 92:
			return [80, 81];
		case 94:
		case 95:
			return [80];
		case 96:
		case 97:
			return [81];
		case 98:
		case 99:
			return [82];
		case 100:
			return [83];
		case 109:
			return [121];
		case 111:
			return [125];
		case 112:
			return [126];
		case 113:
			return [115];
		case 115:
			return [113, 117];
		case 117:
			return [127];
		case 118:
			return [120];
		case 120:
			return [128]; // 118 is getting C/I for using the exit
		case 125:
			return [111];
		case 126:
			return [112];
		case 127:
			return [117];
		case 128:
			return [120];
		default:
			return [];
		}
	};

	this.pickPotions = function (range) {
		if (me.dead) {
			return false;
		}

		Town.clearBelt();

		while (!me.idle) {
			delay(40);
		}

		var status,
			pickList = [],
			item = getUnit(4);

		if (item) {
			do {
				if ((item.mode === 3 || item.mode === 5) && item.itemType >= 76 && item.itemType <= 78 && getDistance(me, item) <= range) {
					pickList.push(copyUnit(item));
				}
			} while (item.getNext());
		}

		pickList.sort(Pickit.sortItems);

		while (pickList.length > 0) {
			item = pickList.shift();

			if (item && copyUnit(item).x) {
				status = Pickit.checkItem(item).result;

				if (status && Pickit.canPick(item)) {
					Pickit.pickItem(item, status);
				}
			}
		}

		return true;
	};

	this.openContainers = function (range) {
		var unit, ox, oy,
			unitList = [],
			containers = ["chest", "loose rock", "hidden stash", "loose boulder", "corpseonstick", "casket", "armorstand", "weaponrack", "barrel", "holeanim",
							"roguecorpse", "ratnest", "corpse", "goo pile", "largeurn", "urn", "chest3", "jug", "skeleton", "guardcorpse", "sarcophagus",
							"cocoon", "basket", "stash", "hollow log", "hungskeleton", "pillar", "skullpile", "skull pile", "jar3", "jar2", "jar1", "bonechest", "woodchestl",
							"woodchestr", "barrel wilderness", "burialchestr", "burialchestl", "explodingchest", "chestl", "chestr", "icecavejar1", "icecavejar2",
							"icecavejar3", "icecavejar4", "deadperson", "deadperson2", "evilurn", "tomb1l", "tomb3l", "tomb2", "tomb3", "object2", "groundtomb", "groundtombl", "crate", "tomb"
						];

		ox = me.x;
		oy = me.y;
		unit = getUnit(2);

		if (unit) {
			do {
				if (containers.indexOf(unit.name.toLowerCase()) > -1 && unit.mode === 0 && getDistance(me, unit) <= range) {
					unitList.push(copyUnit(unit));
				}
			} while (unit.getNext());
		}

		while (unitList.length > 0) {
			unitList.sort(Sort.units);

			unit = unitList.shift();

			if (unit) {
				Misc.openChest(unit);
				Pickit.pickItems();
			}
		}

		return true;
	};

	this.chatEvent = function (nick, msg) {
		if (msg && nick === Config.Leader) {
			switch (msg) {
			case "tele":
			case me.name + " tele":
				if (Pather.teleport) {
					Pather.teleport = false;

					me.overhead("ÿc1Teleport off.");
				} else {
					Pather.teleport = true;

					me.overhead("ÿc2Teleport on.");
				}

				break;
			case "tele off":
			case me.name + " tele off":
				Pather.teleport = false;

				me.overhead("ÿc1Teleport off.");

				break;
			case "tele on":
			case me.name + " tele on":
				Pather.teleport = true;

				me.overhead("ÿc2Teleport on.");

				break;
			case "a":
			case me.name + " a":
				if (attack) {
					attack = false;

					me.overhead("ÿc1Attack off.");
				} else {
					attack = true;

					me.overhead("ÿc2Attack on.");
				}

				break;
			case "flash":
				Packet.flash(me.gid);

				break;
			case "aoff":
			case me.name + " aoff":
				attack = false;

				me.overhead("ÿc1Attack off.");

				break;
			case "aon":
			case me.name + " aon":
				attack = true;

				me.overhead("ÿc2Attack on.");

				break;
			case "quit":
			case me.name + " quit":
				me.overhead("ÿc1quit game ...");
				if (logCharOnExit) {
					MuleLogger.logChar(); // log the char
				}
				delay(rand(Config.QuitListDelay[0] * 1e3, Config.QuitListDelay[1] * 1e3));
				quit();

				break;
			case "s": // go to stash and stop
			case me.name + " s":
				if (!me.inTown) {
					Town.goToTown();
				}

				if (stop) {
					stop = false;

					me.overhead("ÿc2Resuming.");
				} else {
					Town.move("stash");
					stop = true;

					me.overhead("ÿc1Stopping.");
					delay(500);
				}

				break;
			case "r":
				if (me.playertype != 1 && me.mode === 17) {
					me.revive();
				}

				break;
			case "reload":
				showConsole();
				print("reload");
				say("you must type manually the reload command in d2 follower console");
				//hideConsole();

				break;
			case "ancs": // prepare ancients quest a5q5, overwrite the config settings
			case me.name + " ancs":
				Config.MercWatch = false;
				Config.TownCheck = false;
				Config.TownHP = 0;
				Config.LifeChicken = 0;
				Config.ManaChicken = 0;
				Config.HealStatus = false;
				Config.HPBuffer = 3;
				Config.MPBuffer = 3;
				Config.RejuvBuffer = 5;

				if (me.inTown) {
					Town.doChores(true);
					me.overhead("ÿc2Prepared with potions for ancients");
				}

				print("ÿc2Prepared with settings for ancients");

				break;
			case "ancsoff": // revert to char config from ancients config
				Config.MercWatch = true;
				Config.TownCheck = true;
				Config.TownHP = 30;
				Config.HPBuffer = 0;
				Config.MPBuffer = 0;
				Config.RejuvBuffer = 5;
				me.overhead("ÿc1Configuration settings reverted");

				break;
			case "ai": // anti-idle
			case me.name + " ai":
				tick = getTickCount() + rand(150e4, 175e4); // trigger anti-idle every ~30 minutes

				if (!me.inTown) {
					Town.goToTown();
				}

				if (ai) {
					ai = false;
					Config.AutoMap = false;
					me.overhead("ÿc1anti-idle off");
				} else {
					ai = true;
					me.overhead("ÿc2anti-idle on");

					while (ai) {
						delay(1000);

						if (!ai) {
							break;
						}

						if ((getTickCount() - tick) > 0) {
							sendPacket(1, 0x40); // quest status refresh, working as anti-idle
							tick += rand(150e4, 175e4);
						}
					}
				}

				break;
			case "map": // load the mh in the follower d2 window
			case me.name + " map":
				if (map) {
					map = false;
					Config.AutoMap = false;
					me.overhead("ÿc1Hide the mh ÿc0-> type .reload");
					print("ÿc1for hiding the mh, you should manually type ÿc0.reload ÿc4in the chat.");
				} else {
					map = true;
					load("tools/mapthread.js");
					Config.AutoMap = true;
					me.overhead("ÿc2Show the map");
				}

				break;
			case "end": // stop follower profiles after a random delay
			case me.name + " end":
				me.overhead("ÿc1game ended. ÿc0leaving ...");
				if (logCharOnExit) {
					MuleLogger.logChar(); // log the char
				}
				delay(rand(Config.QuitListDelay[0] * 1e3, Config.QuitListDelay[1] * 1e3));
				D2Bot.printToConsole(me.profile + " - end run " + me.gamename);
				D2Bot.stop(me.profile, true);

				break;
			case "restart": // restart the follower profiles after a random delay
			case me.name + " restart":
				me.overhead("ÿc1rejoin game...");
				delay(rand(Config.QuitListDelay[0] * 1e3, Config.QuitListDelay[1] * 1e3));
				D2Bot.printToConsole(me.profile + " restart " + me.gamename);
				D2Bot.restart();

				break;
			default:
				if (me.classid === 3 && msg.indexOf("aura ") > -1) {
					piece = msg.split(" ")[0];

					if (piece === me.name || piece === "all") {
						skill = parseInt(msg.split(" ")[2], 10);

						if (me.getSkill(skill, 1)) {
							me.overhead("ÿc3Active aura is: " + skill);

							Config.AttackSkill[2] = skill;
							Config.AttackSkill[4] = skill;

							Skill.setSkill(skill, 0);
							//Attack.init();
						} else {
							me.overhead("ÿc1I don't have that aura.");
						}
					}

					break;
				}

				if (msg.indexOf("skill ") > -1) {
					piece = msg.split(" ")[0];

					if (charClass.indexOf(piece) > -1 || piece === me.name || piece === "all") {
						skill = parseInt(msg.split(" ")[2], 10);

						if (me.getSkill(skill, 1)) {
							me.overhead("Attack skill is: " + skill);

							Config.AttackSkill[1] = skill;
							Config.AttackSkill[3] = skill;

							//Attack.init();
						} else {
							me.overhead("ÿc1I don't have that skill.");
						}
					}

					break;
				}

				action = msg;

				break;
			}
		}

		if (msg && msg.split(" ")[0] === "leader" && commanders.indexOf(nick) > -1) {
			piece = msg.split(" ")[1];

			if (typeof piece === "string") {
				if (commanders.indexOf(piece) === -1) {
					commanders.push(piece);
				}

				me.overhead("ÿc4Switching leader to " + piece);

				Config.Leader = piece;
				leader = Misc.findPlayer(Config.Leader);
				leaderUnit = Misc.getPlayerUnit(Config.Leader);
			}
		}
	};

	// Start
	addEventListener("chatmsg", this.chatEvent);

	for (i = 0; i < 120; i += 1) {
		leader = Misc.findPlayer(Config.Leader);

		if (leader) {
			break;
		}

		delay(1000);
	}

	if (!leader) {
		print("ÿc1Leader not found - " + me.gamename);
		delay(2e5, 3e5);

		if (logCharOnExit) {
			MuleLogger.logChar(); // log the char
		}

		delay(rand(Config.QuitListDelay[0] * 1e3, Config.QuitListDelay[1] * 1e3));
		quit();

	} else if (leader) {
		me.overhead("ÿc2Leader found.");
	}

	while (!Misc.inMyParty(Config.Leader)) {
		delay(500);
	}

	me.overhead("ÿc2Partied.");
	delay(500);

	// Main Loop
	while (Misc.inMyParty(Config.Leader)) {
		leaderUnit = Misc.getPlayerUnit(Config.Leader);

		if (me.mode === 17) {
			if (me.playertype != 1) {
				while (!me.inTown) {
					me.revive();
					delay(1000);
				}

				Town.move("portalspot");
				print("ÿc2Revived!");

			} else if (me.playertype == 1) { // stop the HC screen to allow the loot of dead player corpse
				Config.MercWatch = false;
				Config.TownCheck = false;
				Config.HealStatus = false;
				Config.TownHP = 0;
				Config.LifeChicken = 0;
				Config.ManaChicken = 0;
				Config.MercChicken = 0;
				Config.TownCheck = false;

				while(true) {
					action = "";
					delay(6e5);
				}
			}
		}

		while (stop) {
			if (!me.inTown) {
				Town.goToTown();
				Town.move("stash");
			}

			delay(500);
		}

		if (!me.inTown) {
			field = true;

			if (!leaderUnit || !copyUnit(leaderUnit).x) {
				leaderUnit = Misc.getPlayerUnit(Config.Leader);

				if (leaderUnit) {
					me.overhead("ÿc2Leader unit found.");
				}
			}

			this.getCloser();

			if (me.classid === 4 && me.getSkill(155, 0) && ((getTickCount() - BCTick >= BCDuration - 30000) || !me.getState(51))) {
				this.precast;
			}

			if (attack) {
				Attack.clear(15, false, false, false, true);
				this.pickPotions(15);
			}

			Pickit.pickItems();
			if (openContainers) {
				this.openContainers(15);
			}

			if (leader.area !== me.area) {
				while (leader.area === 0) {
					delay(100);
				}

				result = this.checkExit(leader, leader.area);

				switch (result) {
				case 1:
					if (Math.abs(leader.area - me.area) === 1 || this.nextArea(me.area).indexOf(leader.area) > -1) {
						me.overhead("Taking exit.");
						delay(500);
						Pather.moveToExit(leader.area, true);
					}

					break;
				case 2:
					me.overhead("Taking portal.");

					break;
				case 3:
					me.overhead("ÿc8Taking waypoint.");
					delay(500);
					Pather.useWaypoint(leader.area, true);

					break;
				case 4:
					me.overhead("Special transit.");

					break;
				}

				while (me.area === 0) {
					delay(100);
				}

				if (!me.inTown && (leader.inTown || Misc.getPlayerAct(Config.Leader) !== me.act)) {
					if (!Pather.usePortal(null, leader.name)) {
						me.overhead("ÿc1Failed to use leader portal.");
						Town.goToTown();
						delay(200);
					}
				}
			}
		}

		if (me.inTown) {
			if (!leader.inTown && Misc.getPlayerAct(Config.Leader) === me.act) {
				me.overhead("ÿc2Ready");
				Town.move("portalspot");
				Attack.weaponSwitch(0);

				while (!Pather.usePortal(leader.area, leader.name) && leader.area !== me.area) {
					me.overhead("ÿc1Failed to use leader portal.");

					if (Misc.getPlayerAct(Config.Leader) !== me.act) {

						break;
					}
				}

				if (!me.inTown) {
					me.overhead("ÿc2Precast");
					delay(rand(100, 200));
					this.precast;

					if (me.classid === 4) {
						Attack.clear(10, false, false, false, true);
						delay(3e3);
						this.precast;
					}

					while (!Misc.getPlayerUnit(Config.Leader) && !me.dead && !action) {
						Attack.clear(10, false, false, false, true);
						delay(200);

						if (leader.area !== me.area) {

							break;
						}
					}
				}
			}

			if (Misc.getPlayerAct(Config.Leader) !== me.act) {
				me.overhead("ÿc8Going to leader's town.");
				Town.goToTown(Misc.getPlayerAct(Config.Leader));
				delay(200);
				Town.move("portalspot");
			}

			if (field && autoTownChores && leader.inTown && !action) {
				me.overhead("ÿc4Running town chores");
				Town.doChores();
				field = false;
				Town.move("portalspot");
			}
		}

		switch (action) {
		case "cow":
			if (me.area === 1) {
				Town.move("portalspot");

				if (!Pather.usePortal(39)) {
					me.overhead("ÿc1Failed to use cow portal.");
				}
			}

			break;
		case me.name + " cowopen":
			include("bots/cowopen.js");
			CowOpen();

			break;
		case "move":
		case "m":
		case me.name + " m":
			coord = CollMap.getRandCoordinate(me.x, -1, 1, me.y, -1, 1, 5);
			Pather.moveTo(coord.x, coord.y);

			break;
		case "wp":
		case me.name + " wp":
			if (me.inTown && !leader.inTown) {
				me.overhead("ÿc8Taking waypoint to leader");
				Town.move("waypoint");
				Pather.useWaypoint(leader.area, true);

				break;
			}

			delay(rand(1, 3) * 500);

			unit = getUnit(2, "waypoint");

			if (unit) {
WPLoop:
				for (i = 0; i < 3; i += 1) {
					if (getDistance(me, unit) > 3) {
						Pather.moveToUnit(unit);
					}

					unit.interact();

					for (j = 0; j < 100; j += 1) {
						if (j % 20 === 0) {
							me.cancel();
							delay(300);
							unit.interact();
						}

						if (getUIFlag(0x14)) {
							break WPLoop;
						}

						delay(10);
					}
				}
			}

			if (getUIFlag(0x14)) {
				me.overhead("ÿc2Got wp.");
			} else {
				me.overhead("ÿc1Failed to get wp.");
			}

			me.cancel();

			break;
		case "c":
		case me.name + " c":
			if (me.playertype != 1 && !me.inTown) {
				Town.getCorpse();
			}

			break;
		case "p":
		case me.name + " p":
			me.overhead("ÿc4!Picking items.");
			Pickit.pickItems();
			this.pickPotions(20);

			if (openContainers) {
				this.openContainers(20);
			}

			me.overhead("ÿc2!Done picking.");

			break;
		case "1":
			if (me.inTown && leader.inTown && Misc.getPlayerAct(Config.Leader) !== me.act) {
				me.overhead("ÿc8Going to leader's town.");
				Town.goToTown(Misc.getPlayerAct(Config.Leader));
				Town.move("portalspot");
			} else if (me.inTown) {
				say("Going outside.");
				Town.goToTown(Misc.getPlayerAct(Config.Leader));
				Town.move("portalspot");

				if (!Pather.usePortal(leader.area, leader.name)) {
					break;
				}

				while (!Misc.getPlayerUnit(Config.Leader) && !me.dead) {
					Attack.clear(10);
					delay(200);
				}
			}

			break;
		case "2":
			if (!me.inTown) {
				delay(150);
				me.overhead("ÿc4Going to town.");
				Pather.usePortal(null, leader.name);
			}

			break;
		case "3":
		case me.name + " 3":
			if (me.inTown) {
				me.overhead("ÿc4Running town chores");
				Town.doChores(true);
				Town.move("portalspot");
				me.overhead("ÿc2Ready");
			}

			break;
		case "gamble":
		case me.name + " gamble":
			if (me.inTown) {
				me.overhead("ÿc4Start gambling");
				gold = Config.GambleGoldStart;
				Config.GambleGoldStart = me.gold;
				Town.gamble();
				Config.GambleGoldStart = gold;
				Town.move("portalspot");
				me.overhead("ÿc2Ready");
			}

			break;
		case "h":
			if (me.classid === 4) {
				Skill.cast(130);
			}

			break;
		case "bo":
			if (me.classid === 4) {
				me.overhead("ÿc2BO");
				this.precast;
			}

			break;
		case "b": // buff all followers
			me.overhead("ÿc2refresh buff");
			delay(rand(100, 200));
			this.precast;

			break;
		case "a2":
		case "a3":
		case "a4":
		case "a5":
			this.changeAct(parseInt(action[1], 10));

			break;
		case "tp":
		case me.name + " tp":
			unit = me.findItem("tbk", 0, 3);

			if (unit && unit.getStat(70)) {
				unit.interact();

				break;
			}

			unit = me.findItem("tsc", 0, 3);

			if (unit) {
				unit.interact();

				break;
			}

			me.overhead("ÿc1No TP scrolls or tomes.");

			break;
		case me.name + " countess":
			Town.doChores();
			Pather.useWaypoint(6);
			this.precast;

			if (!Pather.moveToExit([20, 21, 22, 23, 24, 25], true)) {
				throw new Error("Failed to move to Countess");
			}

			Pather.makePortal(true);

			break;
		case me.name + " andariel":
			Town.doChores();
			Pather.useWaypoint(35);
			this.precast;

			if (!Pather.moveToExit([36, 37], true)) {
				throw new Error("Failed to move to Catacombs Level 4");
			}

			Pather.makePortal(true);

			break;
		case me.name + " summoner":
			Town.doChores();
			Pather.useWaypoint(74);
			this.precast;

			if (!Pather.moveToPreset(me.area, 2, 357, -3, -3)) {
				throw new Error("Failed to move to Summoner");
			}

			Pather.makePortal(true);

			break;
		case me.name + " duriel":
			Town.doChores();
			Pather.useWaypoint(46);
			this.precast;

			if (!Pather.moveToExit(getRoom().correcttomb, true)) {
				throw new Error("Failed to move to Tal Rasha's Tomb");
			}

			if (!Pather.moveToPreset(me.area, 2, 152, -11, 3)) {
				throw new Error("Failed to move to Orifice");
			}

			Pather.makePortal(true);

			break;
		case me.name + " mephisto":
			Town.doChores();
			Pather.useWaypoint(101);
			this.precast;

			if (!Pather.moveToExit(102, true)) {
				throw new Error("Failed to move to Durance Level 3");
			}

			Pather.makePortal(true);

			break;
		case me.name + " chaos":
			Town.doChores();
			Pather.useWaypoint(107);
			this.precast;

			if (!Pather.moveToExit(108, true)) {
				throw new Error("Failed to move to Chaos Sanctuary");
			}

			Pather.makePortal(true);

			break;
		case me.name + " nihlathak":
			Town.doChores();
			Pather.useWaypoint(123);
			this.precast;

			if (!Pather.moveToExit(124, true)) {
				throw new Error("Failed to go to Nihlathak");
			}

			Pather.makePortal(true);

			break;
		case me.name + " throne":
			Town.doChores();
			Pather.useWaypoint(129);
			this.precast;

			if (!Pather.moveToExit([130, 131], true)) {
				throw new Error("Failed to move to Throne of Destruction.");
			}

			Pather.makePortal(true);

			break;
		}

		// Talk to NPC
		if (action.indexOf("talk") > -1) {
			this.talk(action.split(" ")[1]);
		}

		 // Move to leader announced position
		 // Leader should set a key in ToolsThread.js, like (Numpad /) case 111: say("area: " + me.area + " x: " + me.x + " y: " + me.y);
		if (action && action.split(" ")[0] === "area:") {
			var la = parseInt(action.split(" ")[1], 10),
				lx = parseInt(action.split(" ")[3], 10),
				ly = parseInt(action.split(" ")[5], 10);

			if (me.area === la) {
				me.overhead("ÿc8moving to leader, area: ÿc0" + la + "ÿc8 x: ÿc0" + lx + "ÿc8 y: ÿc0" + ly);
				Pather.moveTo(lx, ly, 3, true);
			} else {
				if (me.inTown) {
					say("ÿc1I'm in town ÿc0- " + me.area);
				} else {
					say("ÿc1different area, ÿc0cannot get leader position");
				}

				action = "";
			}
		}

		// Change the distance to leader - used in getCloser function
		if (action && action.split(":")[0] === "dist") {
			dist = (parseInt(action.split(":")[1], 10) > 6 && parseInt(action.split(":")[1], 10) <= 30) ? parseInt(action.split(":")[1], 10) : 6;
			me.overhead("ÿc4distance to leader ÿc0= " + dist);
		}

		// Communication between 2 teams, 2nd leader is set as follower for the 1st. the commands will be repetead by the 2nd leader
		if (action && action.split(" ")[0] === "say" && action.length > 4) {
			if (action.split(" ")[1] === "area") {
				say("area: " + me.area + " x: " + me.x + " y: " + me.y);
			} else if (action.split(" ")[2]) {
				say(action.split(" ")[1] + " " + action.split(" ")[2]);
			} else {
				say(action.split(" ")[1]);
			}
		}

		action = "";

		delay(100);
	}

	return true;
}