init()
{
	initMaps();
	level thread onPlayerConnected();
}

initMaps()
{
	level.ex_maps = [];

	// Catch-all map. KEEP THIS INDEX 0!
	level.ex_maps[0] = spawnstruct();
	level.ex_maps[0].mapname = "";
	level.ex_maps[0].longname = "Non-localized map name";
	level.ex_maps[0].loclname = &"Non-localized map name";

	// Add stock maps
	if(level.ex_stock_maps)
	{
		level.ex_maps[level.ex_maps.size] = spawnstruct();
		level.ex_maps[level.ex_maps.size-1].mapname = "mp_farmhouse";
		level.ex_maps[level.ex_maps.size-1].longname = "Beltot, France";
		level.ex_maps[level.ex_maps.size-1].loclname = &"Beltot, France";

		level.ex_maps[level.ex_maps.size] = spawnstruct();
		level.ex_maps[level.ex_maps.size-1].mapname = "mp_brecourt";
		level.ex_maps[level.ex_maps.size-1].longname = "Brecourt, France";
		level.ex_maps[level.ex_maps.size-1].loclname = &"Brecourt, France";

		level.ex_maps[level.ex_maps.size] = spawnstruct();
		level.ex_maps[level.ex_maps.size-1].mapname = "mp_burgundy";
		level.ex_maps[level.ex_maps.size-1].longname = "Burgundy, France";
		level.ex_maps[level.ex_maps.size-1].loclname = &"Burgundy, France";

		level.ex_maps[level.ex_maps.size] = spawnstruct();
		level.ex_maps[level.ex_maps.size-1].mapname = "mp_trainstation";
		level.ex_maps[level.ex_maps.size-1].longname = "Caen, France";
		level.ex_maps[level.ex_maps.size-1].loclname = &"Caen, France";

		level.ex_maps[level.ex_maps.size] = spawnstruct();
		level.ex_maps[level.ex_maps.size-1].mapname = "mp_carentan";
		level.ex_maps[level.ex_maps.size-1].longname = "Carentan, France";
		level.ex_maps[level.ex_maps.size-1].loclname = &"Carentan, France";

		level.ex_maps[level.ex_maps.size] = spawnstruct();
		level.ex_maps[level.ex_maps.size-1].mapname = "mp_decoy";
		level.ex_maps[level.ex_maps.size-1].longname = "El Alamein, Egypt";
		level.ex_maps[level.ex_maps.size-1].loclname = &"El Alamein, Egypt";

		level.ex_maps[level.ex_maps.size] = spawnstruct();
		level.ex_maps[level.ex_maps.size-1].mapname = "mp_leningrad";
		level.ex_maps[level.ex_maps.size-1].longname = "Leningrad, Russia";
		level.ex_maps[level.ex_maps.size-1].loclname = &"Leningrad, Russia";

		level.ex_maps[level.ex_maps.size] = spawnstruct();
		level.ex_maps[level.ex_maps.size-1].mapname = "mp_matmata";
		level.ex_maps[level.ex_maps.size-1].longname = "Matmata, Tunisia";
		level.ex_maps[level.ex_maps.size-1].loclname = &"Matmata, Tunisia";

		level.ex_maps[level.ex_maps.size] = spawnstruct();
		level.ex_maps[level.ex_maps.size-1].mapname = "mp_downtown";
		level.ex_maps[level.ex_maps.size-1].longname = "Moscow, Russia";
		level.ex_maps[level.ex_maps.size-1].loclname = &"Moscow, Russia";

		level.ex_maps[level.ex_maps.size] = spawnstruct();
		level.ex_maps[level.ex_maps.size-1].mapname = "mp_harbor";
		level.ex_maps[level.ex_maps.size-1].longname = "Rostov, Russia";
		level.ex_maps[level.ex_maps.size-1].loclname = &"Rostov, Russia";

		level.ex_maps[level.ex_maps.size] = spawnstruct();
		level.ex_maps[level.ex_maps.size-1].mapname = "mp_dawnville";
		level.ex_maps[level.ex_maps.size-1].longname = "St. Mere Eglise, France";
		level.ex_maps[level.ex_maps.size-1].loclname = &"St. Mere Eglise, France";

		level.ex_maps[level.ex_maps.size] = spawnstruct();
		level.ex_maps[level.ex_maps.size-1].mapname = "mp_railyard";
		level.ex_maps[level.ex_maps.size-1].longname = "Stalingrad, Russia";
		level.ex_maps[level.ex_maps.size-1].loclname = &"Stalingrad, Russia";

		level.ex_maps[level.ex_maps.size] = spawnstruct();
		level.ex_maps[level.ex_maps.size-1].mapname = "mp_toujane";
		level.ex_maps[level.ex_maps.size-1].longname = "Toujane, Tunisia";
		level.ex_maps[level.ex_maps.size-1].loclname = &"Toujane, Tunisia";

		level.ex_maps[level.ex_maps.size] = spawnstruct();
		level.ex_maps[level.ex_maps.size-1].mapname = "mp_breakout";
		level.ex_maps[level.ex_maps.size-1].longname = "Villers-Bocage, France";
		level.ex_maps[level.ex_maps.size-1].loclname = &"Villers-Bocage, France";

		level.ex_maps[level.ex_maps.size] = spawnstruct();
		level.ex_maps[level.ex_maps.size-1].mapname = "mp_rhine";
		level.ex_maps[level.ex_maps.size-1].longname = "Wallendar, Germany";
		level.ex_maps[level.ex_maps.size-1].loclname = &"Wallendar, Germany";
	}

	// Add custom maps
	scriptdata\_ex_votemaps::init();

	// Sort the array
	bubbleSort(1, level.ex_maps.size - 1);
}

bubbleSort(start, max)
{
	for(i = start; i < max; i++)
	{
		for(x = start; x <= max-i; x++)
		{
			r = strCompare(x, x+1);
			if(r == 2) mapSwap(x, x+1);
		}
	}
}

strCompare(istr1, istr2)
{
	ascii = " !\"#$%&'()*+,-./0123456789:;<=>?@AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz[\\]^_`{|}~";
	if(level.ex_maps[istr1].longname.size <= level.ex_maps[istr2].longname.size)
	{
		mode = 1;
		str1 = level.ex_maps[istr1].longname;
		str2 = level.ex_maps[istr2].longname;
	}
	else
	{
		mode = 2;
		str1 = level.ex_maps[istr2].longname;
		str2 = level.ex_maps[istr1].longname;
	}
	size1 = str1.size;
	size2 = str2.size;
	for(i = 0; i < str1.size; i++)
	{
		chr1 = str1[i];
		pos1 = -1;
		for(j = 0; j < ascii.size; j++)
			if(chr1 == ascii[j]) { pos1 = j; break; }

		chr2 = str2[i];
		pos2 = -1;
		for(j = 0; j < ascii.size; j++)
			if(chr2 == ascii[j]) { pos2 = j; break; }

		if(mode == 1)
		{
			if(pos1 < pos2) return 1;
			if(pos1 > pos2) return 2;
		}
		else
		{
			if(pos1 < pos2) return 2;
			if(pos1 > pos2) return 1;
		}
	}
	if(size1 == size2) return 0;
	if(mode == 1) return 1;
		else return 2;
}

mapSwap(i, j)
{
	temp[0] = spawnstruct();
	temp[0].mapname = "";
	temp[0].longname = "";
	temp[0].loclname = &"";

	temp[0].mapname	= level.ex_maps[i].mapname;
	temp[0].longname = level.ex_maps[i].longname;
	temp[0].loclname = level.ex_maps[i].loclname;

	level.ex_maps[i].mapname = level.ex_maps[j].mapname;
	level.ex_maps[i].longname = level.ex_maps[j].longname;
	level.ex_maps[i].loclname = level.ex_maps[j].loclname;

	level.ex_maps[j].mapname = temp[0].mapname;
	level.ex_maps[j].longname = temp[0].longname;
	level.ex_maps[j].loclname = temp[0].loclname;
}

onPlayerConnected()
{
	for (;;)
	{
		level waittill ("connected", player);
		player thread setInGameVoteDvars();
	}
}

SetInGameVoteDvars()
{
	self endon ("disconnect");

	self waittill ("joined_team");

	allow = [[level.ex_drm]]("ex_ingame_vote_allow_old", 0, 0, 1, "int");
	self setClientCvar ("ui_ingame_vote_allow_old", allow);
	wait 0.05;

	allow = [[level.ex_drm]]("ex_ingame_vote_allow_gametype", 1, 0, 1, "int");
	self setClientCvar ("ui_ingame_vote_allow_gametype", allow);
	wait 0.05;

	if(allow)
	{
		gt_str = "chq cnq ctf ctfb dm dom esd hm hq htf ihtf lib lms lts ons rbcnq rbctf sd tdm tkoth vip";
		gt_array = strtok(gt_str, " ");

		for(i = 0; i < gt_array.size; i++)
		{
			if((!isDefined(self)) || (!isPlayer(self)))
				return;

			gt = gt_array[i];
			svr_dvar = "ex_ingame_vote_allow_" + gt;
			cli_dvar = "ui_ingame_vote_allow_" + gt;
			val = [[level.ex_drm]](svr_dvar, 1, 0, 1, "int");
			self setClientCvar(cli_dvar, val);
			wait 0.05;
		}
	}

	allow = [[level.ex_drm]]("ex_ingame_vote_allow_map", 1, 0, 1, "int");
	self setClientCvar ("ui_ingame_vote_allow_map", allow);
	wait 0.05;

	if(allow)
	{
		number = level.ex_maps.size - 1;
		if(number > 160) number = 160; // 2*80

		for(i = 1; i <= number; i++)
		{
			if((!isDefined(self)) || (!isPlayer(self)))
				return;

			cli_dvar = "ui_ingame_vote_map_name_" + i;
			self setClientCvar(cli_dvar, level.ex_maps[i].longname);
			wait 0.05;
			cli_dvar = "ui_ingame_vote_map_cmd_" + i;
			self setClientCvar(cli_dvar, "callvote map " + level.ex_maps[i].mapname);
			wait 0.05;
		}

		self setClientCvar("ui_ingame_vote_map_2pages", (number > 80));
	}
}

main()
{
	if(PrepareMapVote())
	{
		if(level.ex_mvmusic)
		{
			mv_music = randomInt(10);
			musicplay("gom_music_" + mv_music);
		}
		RunMapVote();
	}
}

// Prepare players for map vote, and check if vote possible
PrepareMapVote()
{
	game["menu_team"] = "";

	// Prepare players for vote
	votingplayers = 0;
	players = getentarray("player", "classname");
	for(i = 0; i < players.size; i++)
	{
		resetTimeout();

		players[i] setClientCvar("g_scriptMainMenu", "");
		players[i] closeMenu();
		players[i] extreme\_ex_spawn::spawnSpectator();
		players[i] allowSpectateTeam("allies", false);
		players[i] allowSpectateTeam("axis", false);
		players[i] allowSpectateTeam("freelook", false);
		players[i] allowSpectateTeam("none", true);

		players[i].mv_allowvote = true;

		// No voting for spectators
		if(isDefined(players[i].pers["team"]) && players[i].pers["team"] == "spectator" || players[i].sessionteam == "spectator")
			players[i].mv_allowvote = false;

		// No voting for testclients (bots)
		if(isDefined(players[i].pers["isbot"]) && players[i].pers["isbot"])
			players[i].mv_allowvote = false;

		// No voting for non-clan players if clan voting enabled, unless it should be ignored
		if(level.ex_clanvoting && !level.ex_ignore_clanvoting)
			if(!isDefined(players[i].ex_name) || !isDefined(players[i].ex_clid) || !level.ex_clvote[players[i].ex_clid])
				players[i].mv_allowvote = false;

		// No voting for this player
		//if(players[i].name == "PatmanSan")
		//	players[i].mv_allowvote = false;

		if(players[i].mv_allowvote) votingplayers++;
	}

	// Any players?
	if(votingplayers == 0)
		return false;

	// Get the map rotation queue
	switch(level.ex_mapvotemode)
	{
		case 1: { x = extreme\_ex_maprotation::GetRandomMapRotation(); break; }
		case 2: { x = extreme\_ex_maprotation::GetPlayerBasedMapRotation(); break; }
		case 3: { x = extreme\_ex_maprotation::GetRandomPlayerBasedMapRotation(); break; }
		default: { x = extreme\_ex_maprotation::GetPlainMapRotation(); break; }
	}

	mv_maprot = undefined;
	if(isDefined(x))
	{
		if(isDefined(x.maps)) mv_maprot = x.maps;
		x delete();
	}

	// Any maps to begin with?
	if(!isDefined(mv_maprot))
		return false;

	if(level.ex_mapvotemax < mv_maprot.size) mv_mapvotemax = level.ex_mapvotemax;
		else mv_mapvotemax = mv_maprot.size;

	// Fill all candidate slots with the current map
	for(j = 0; j < mv_mapvotemax; j++)
	{
		level.mv_mapcan[j]["map"] = level.ex_currentmap;
		level.mv_mapcan[j]["mapname"] = &"MAPVOTE_REPLAY";
		level.mv_mapcan[j]["gametype"] = level.ex_currentgt;
		level.mv_mapcan[j]["gametypename"] = extreme\_ex_maps::getgtstringshort(level.ex_currentgt);
		level.mv_mapcan[j]["exec"] = undefined;
		level.mv_mapcan[j]["votes"] = 0;
	}

	if(level.ex_mapvotereplay) level.mv_mapcanmax = 1;
		else level.mv_mapcanmax = 0;

	// Get candidates
	i = 0;
	lastgametype = level.ex_currentgt;
	for(j = 0; j < mv_mapvotemax; j++)
	{
		// Do we need the last slot for current map (replay)?
		if(level.ex_mapvotereplay && j == mv_mapvotemax-1) break;

		// Make sure we know the maptype
		if(!isDefined(mv_maprot[i]["gametype"])) mv_maprot[i]["gametype"] = lastgametype;
			else lastgametype = mv_maprot[i]["gametype"];

		// Skip current map and gametype combination
		if(mv_maprot[i]["map"] == level.ex_currentmap && mv_maprot[i]["gametype"] == level.ex_currentgt)
		{
			i++;
			// Pointer changed, so again make sure we know the maptype
			if(!isDefined(mv_maprot[i]["gametype"])) mv_maprot[i]["gametype"] = lastgametype;
				else lastgametype = mv_maprot[i]["gametype"];
		}

		// Any maps left?
		if(!isDefined(mv_maprot[i])) break;

		level.mv_mapcan[j]["map"] = mv_maprot[i]["map"];
		level.mv_mapcan[j]["mapname"] = extreme\_ex_maps::getmapstring(mv_maprot[i]["map"]);
		level.mv_mapcan[j]["gametype"] = mv_maprot[i]["gametype"];
		level.mv_mapcan[j]["gametypename"] = extreme\_ex_maps::getgtstringshort(mv_maprot[i]["gametype"]);
		level.mv_mapcan[j]["exec"] = mv_maprot[i]["exec"];

		i++;
		level.mv_mapcanmax++;

		// Any maps left?
		if(!isDefined(mv_maprot[i])) break;
	}

	// Any maps left?
	if(level.mv_mapcanmax == 0) return false;
	return true;
}

// Map vote starts here
RunMapVote()
{
	level.mv_width = 260; // default: 260. max: 640. eXtremeGamerz setting: 460
	level.mv_originx1 = int((640 - level.mv_width) / 2);
	level.mv_originx2 = level.mv_originx1 + level.mv_width; // int( 320 + (level.mv_width / 2));

	CreateHud();
	players = getentarray("player", "classname");
	for(i = 0; i < players.size; i++)
		if(players[i].mv_allowvote) players[i] thread PlayerVote();
			else players[i] thread PlayerNoVote();

	thread VoteLogic();
	level waittill("VotingComplete");
}

// Thread for players not allowed to vote
// For players joining the vote during VoteLogic(), create HUD elements in this thread
// not in CreateHUD()
PlayerNoVote()
{
	level endon("VotingDone");
	
	// To vertically center HUD elements, find out how many map lines are displayed
	// If less than 5 maps, make sure we have enough space for HUD elements
	minMapLinesOnPage = maxMapsOnPage(1);
	if(minMapLinesOnPage < 5) minMapLinesOnPage = 5;

	// Map vote in progress
	self.mv_inprogress = newClientHudElem(self);
	self.mv_inprogress.x = 320;
	self.mv_inprogress.y = 70 + int(minMapLinesOnPage / 2) * 16;
	self.mv_inprogress.sort = 103;
	self.mv_inprogress.fontscale = 1.5;
	self.mv_inprogress.color = (1, 0, 0);
	self.mv_inprogress.alignX = "center";
	self.mv_inprogress.alignY = "middle";
	self.mv_inprogress.label = &"MAPVOTE_INPROGRESS";

	// You are not allowed to vote
	self.mv_notallowed = newClientHudElem(self);
	self.mv_notallowed.x = 320;
	self.mv_notallowed.y = 95 + int(minMapLinesOnPage / 2) * 16;
	self.mv_notallowed.sort = 103;
	self.mv_notallowed.fontscale = 1.3;
	self.mv_notallowed.color = (0, 1, 0);
	self.mv_notallowed.alignX = "center";
	self.mv_notallowed.alignY = "middle";
	self.mv_notallowed.label = &"MAPVOTE_NOTALLOWED";

	// Please wait...
	self.mv_wait = newClientHudElem(self);
	self.mv_wait.x = 320;
	self.mv_wait.y = 111 + int(minMapLinesOnPage / 2) * 16;
	self.mv_wait.sort = 103;
	self.mv_wait.fontscale = 1.3;
	self.mv_wait.color = (0, 1, 0);
	self.mv_wait.alignX = "center";
	self.mv_wait.alignY = "middle";
	self.mv_wait.label = &"MAPVOTE_PLEASEWAIT";

	// Now loop until the thread is signaled to end
	for(;;)
	{
		wait .1;
		if(isPlayer(self))
		{
			self.sessionstate = "spectator";
			self.spectatorclient = -1;
		}
	}
}

// Thread for players allowed to vote
PlayerVote()
{
	level endon("VotingDone");

	// Players start without a vote
	self.mv_choice = 0;
	self.mv_flipchoice = 0;

	// Create HUD elements for maps (max 10)
	for(i = 0; i <= maxMapsOnPage(onPage(self.mv_choice))-1; i++)
	{
		self.mv_maps[i] = newClientHudElem(self);
		self.mv_maps[i].archived = false;
		self.mv_maps[i].x = level.mv_originx1 + 5; //195;
		self.mv_maps[i].y = 105 + i * 16;
		self.mv_maps[i].sort = 104;
		self.mv_maps[i].fontscale = 1.3;
		self.mv_maps[i].label = level.mv_mapcan[i]["mapname"];
		self.mv_maps[i] setText(level.mv_mapcan[i]["gametypename"]);
	}

	// Create HUD elements for voting slots (max 10)
	for(i = 0; i <= maxMapsOnPage(onPage(self.mv_choice))-1; i++)
	{
		self.mv_votes[i] = newClientHudElem(self);
		self.mv_votes[i].archived = false;
		self.mv_votes[i].x = level.mv_originx2 - 20; //430;
		self.mv_votes[i].y = 105 + i * 16;
		self.mv_votes[i].sort = 104;
		self.mv_votes[i] setValue(level.mv_mapcan[i]["votes"]);
	}

	// Update page info
	self.mv_page.label = getPageInfo(onPage(self.mv_choice));

	// Create HUD element for selection bar. It starts invisible.
	// Keep sort number less than maps and votes, so it appears behind them
	self.mv_indicator = newClientHudElem(self);
	self.mv_indicator.x = level.mv_originx1 + 3; //193;
	self.mv_indicator.y = 104;
	self.mv_indicator.archived = false;
	self.mv_indicator.sort = 103;
	self.mv_indicator.alpha = 0;
	self.mv_indicator.color = (0, 0, 1);
	self.mv_indicator setShader("white", level.mv_width - 6, 17); //254;

	// Now loop until the thread is signaled to end
	for (;;)
	{
		wait .01;

		// Attack (FIRE) button to vote
		if(isplayer(self) && self attackButtonPressed() == true)
		{
			nextMap(self);
			while(isPlayer(self) && self attackButtonPressed() == true)
				wait.01;
		}

		// Melee button to flip pages
		if(isplayer(self) && self meleeButtonPressed() == true)
		{
			if(maxPages() > 1) flipPage(self);
			while(isPlayer(self) && self meleeButtonPressed() == true)
				wait.01;
		}

		if(isPlayer(self))
		{
			self.sessionstate = "spectator";
			self.spectatorclient = -1;
		}
	}
}

// Select next map (player clicked attack button)
nextMap(player)
{
	// Show indicator if first vote
	if(player.mv_choice == 0)
		player.mv_indicator.alpha = .8;

	// Is this first click after page flipping?
	if(player.mv_flipchoice != 0)
	{
		if(onPage(player.mv_choice) == onPage(player.mv_flipchoice)) player.mv_choice++;
			else player.mv_choice = player.mv_flipchoice;
		player.mv_indicator.alpha = .8;
		player.mv_flipchoice = 0;

	}
	else player.mv_choice++;

	if (player.mv_choice > level.mv_mapcanmax)
		player.mv_choice = 1;

	showChoice(player, player.mv_choice);
}

// Flip a page (player clicked melee button)
// IMPORTANT: do not change player's choice during page flipping!
flipPage(player)
{
	// Hide the indicator
	player.mv_indicator.alpha = 0;

	// Init temporary choice on first flip
	if(player.mv_flipchoice == 0) player.mv_flipchoice = player.mv_choice;

	// Set next page. Rotate if on last page already
	page = onPage(player.mv_flipchoice);
	page++;
	if(page > maxPages()) page = 1;

	// Calculate temporary choice based on new page.
	player.mv_flipchoice = (page * 10)-9;

	showChoice(player, player.mv_flipchoice);

	// Show indicator if this is the page with the player's choice on it
	if(player.mv_choice != 0 && (onPage(player.mv_choice) == onPage(player.mv_flipchoice)))
		player.mv_indicator.alpha = .8;
}

// Show players's choice, and auto-flip page if needed
showChoice(player, choice)
{
	if(choice == 1) oldpage = maxPages();
		else oldpage = onPage(choice-1);
	newpage = onPage(choice);

	// Is a page flip needed?
	if(newpage != oldpage)
	{
		// Remove old maps and votes
		for(i = 0; i <= maxMapsOnPage(oldpage)-1; i++)
		{
			player.mv_maps[i].alpha = 0;
			player.mv_votes[i].alpha = 0;
		}
		// Show new maps and votes
		for(i = 0; i <= maxMapsOnPage(newpage)-1; i++)
		{
			player.mv_maps[i].label = level.mv_mapcan[(newpage * 10)-10+i]["mapname"];
			player.mv_maps[i] setText(level.mv_mapcan[(newpage * 10)-10+i]["gametypename"]);
			player.mv_votes[i] setValue(level.mv_mapcan[(newpage * 10)-10+i]["votes"]);
			player.mv_maps[i].alpha = 1;
			player.mv_votes[i].alpha = 1;
		}
		// Update page info
		player.mv_page.label = getPageInfo(newpage);
	}
	// Update indicator, and show selected map if not in page flipping mode
	if(player.mv_flipchoice == 0)
	{
		indpos = 9 - ((newpage * 10) - choice);
		self.mv_indicator.y = 104 + (indpos * 16);
		self playLocalSound("flagchange");
	}
}

// Big brother is watching votes
VoteLogic()
{
	mv_musicstop = undefined;

	for (; level.ex_mapvotetime >= 0; level.ex_mapvotetime--)
	{
		for(t = 0; t < 10; t++)
		{
			// Reset votes
			for(i = 0; i < level.mv_mapcanmax; i++) level.mv_mapcan[i]["votes"] = 0;

			// Get current players
			players = getentarray("player", "classname");

			// Spawn no-vote thread for new players (joined during vote)
			for(i = 0; i < players.size; i++)
			{
				if(isPlayer(players[i]) && !isDefined(players[i].mv_allowvote))
				{
					players[i].mv_allowvote = false;
					players[i] thread PlayerNoVote();
				}
			}

			// Recount votes
			for(i = 0; i < players.size; i++)
				if(players[i].mv_allowvote && players[i].mv_choice != 0)
					level.mv_mapcan[players[i].mv_choice - 1]["votes"]++;

			// Update map votes on player's HUD, depending on page displayed (scary stuff)
			for(i = 0; i < players.size; i++)
			{
				if(players[i].mv_allowvote)
				{
					if(players[i].mv_flipchoice != 0) isonpage = onPage(players[i].mv_flipchoice);
						else isonpage = onPage(players[i].mv_choice);
					for(j = 0; j < maxMapsOnPage(isonpage); j++)
						players[i].mv_votes[j] setValue(level.mv_mapcan[(isonpage * 10)-10+j]["votes"]);
				}
			}

			if(level.ex_mvmusic && !isDefined(mv_musicstop) && level.ex_mapvotetime <= 10)
			{
				musicstop(10);
				mv_musicstop = true;
			}

			wait .1;
		}
		// Update time left HUD
		level.mv_timeleft setValue(level.ex_mapvotetime);
	}

	// Stop player threads
	level notify("VotingDone");

	// Wait for threads to die
	wait 0.2;

	// Count the votes
	mv_newmapnum = 0;
	mv_topvotes = 0;
	for(i = 0; i < level.mv_mapcanmax; i++)
	{
		if (level.mv_mapcan[i]["votes"] > mv_topvotes)
		{
			mv_newmapnum = i;
			mv_topvotes = level.mv_mapcan[i]["votes"];
		}
	}

	// Select the winning map and gametype
	map = level.mv_mapcan[mv_newmapnum]["map"];
	mapname = level.mv_mapcan[mv_newmapnum]["mapname"];
	gametype = level.mv_mapcan[mv_newmapnum]["gametype"];
	gametypename = extreme\_ex_maps::getgtstring(gametype);
	exec = level.mv_mapcan[mv_newmapnum]["exec"];

	// Write to cvars
	if(!isDefined(exec)) exec = "";
		else exec = " exec " + exec;
	setCvar("sv_maprotationcurrent", exec + " gametype " + gametype + " map " + map);
	wait 0.1;

	// Fade all player-based HUD elements
	players = getentarray("player", "classname");
	for(i = 0; i < players.size; i++)
	{
		if(isPlayer(players[i]) && isDefined(players[i].mv_allowvote))
		{
			if(players[i].mv_allowvote)
			{
				// For players allowed to vote
				for(j = 0; j < maxMapsOnPage(1); j++)
				{
					if(isDefined(players[i].mv_votes[j]))
					{
						players[i].mv_votes[j] fadeOverTime(1);
						players[i].mv_votes[j].alpha = 0;
					}
				}

				for(j = 0; j < maxMapsOnPage(1); j++)
				{
					if(isDefined(players[i].mv_maps[j]))
					{
						players[i].mv_maps[j] fadeOverTime(1);
						players[i].mv_maps[j].alpha = 0;
					}
				}

				if(isDefined(players[i].mv_indicator))
				{
					players[i].mv_indicator fadeOverTime(1);
					players[i].mv_indicator.alpha = 0;
				}

				if(isDefined(players[i].mv_page))
				{
					players[i].mv_page fadeOverTime(1);
					players[i].mv_page.alpha = 0;
				}

				if(isDefined(players[i].mv_howto))
				{
					players[i].mv_howto fadeOverTime(1);
					players[i].mv_howto.alpha = 0;
				}

				if(isDefined(players[i].mv_headers))
				{
					players[i].mv_headers fadeOverTime(1);
					players[i].mv_headers.alpha = 0;
				}
			}
			else
			{
				// For players not allowed to vote
				if(isDefined(players[i].mv_inprogress))
				{
					players[i].mv_inprogress fadeOverTime(1);
					players[i].mv_inprogress.alpha = 0;
				}

				if(isDefined(players[i].mv_notallowed))
				{
					players[i].mv_notallowed fadeOverTime(1);
					players[i].mv_notallowed.alpha = 0;
				}

				if(isDefined(players[i].mv_wait))
				{
					players[i].mv_wait fadeOverTime(1);
					players[i].mv_wait.alpha = 0;
				}
			}
		}
	}
	// Fade all level-based HUD elements
	level.mv_timeleft fadeOverTime(1);
	level.mv_bline fadeOverTime(1);
	level.mv_title fadeOverTime(1);
	level.mv_titlebar fadeOverTime(1);
	level.mv_bg fadeOverTime(1);

	level.mv_timeleft.alpha = 0;
	level.mv_bline.alpha = 0;
	level.mv_title.alpha = 0;
	level.mv_titlebar.alpha = 0;
	level.mv_bg.alpha = 0;

	wait 1;
	DeleteHud();

	// Announce winner
	WinnerIs(mapname, gametypename);

	// Signal the end of map vote
	level notify("VotingComplete");
}

// Announce the winning map
WinnerIs(mapname, gametypename)
{
	wait 1;

	// And the winner is...
	level.mv_winner = newHudElem();
	level.mv_winner.archived = false;
	level.mv_winner.x = 320;
	level.mv_winner.y = 90;
	level.mv_winner.fontscale = 1.3;
	level.mv_winner.alignX = "center";
	level.mv_winner.label = &"MAPVOTE_WINNER";

	// Winning map name
	level.mv_winner_map = newHudElem();
	level.mv_winner_map.archived = false;
	level.mv_winner_map.x = 320;
	level.mv_winner_map.y = 120;
	level.mv_winner_map.color = (0,1,0);
	level.mv_winner_map.fontscale = 2;
	level.mv_winner_map.alignX = "center";
	level.mv_winner_map.label = mapname;

	// Winning game type
	level.mv_winner_gt = newHudElem();
	level.mv_winner_gt.archived = false;
	level.mv_winner_gt.x = 320;
	level.mv_winner_gt.y = 144;
	level.mv_winner_gt.fontscale = 1.5;
	level.mv_winner_gt.alignX = "center";
	level.mv_winner_gt.label = gametypename;

	wait 5;

	level.mv_winner fadeOverTime(1);
	level.mv_winner.alpha = 0;
	level.mv_winner_map fadeOverTime(1);
	level.mv_winner_map.alpha = 0;
	level.mv_winner_gt fadeOverTime(1);
	level.mv_winner_gt.alpha = 0;

	wait 1;

	level.mv_winner destroy();
	level.mv_winner_map destroy();
	level.mv_winner_gt destroy();
}

// Create basic HUD elements
CreateHud()
{
	// Make sure the vote window is at least 5 map lines high
	minMapLinesOnPage = maxMapsOnPage(1);
	if(minMapLinesOnPage < 5) minMapLinesOnPage = 5;

	// Background
	level.mv_bg = newHudElem();
	level.mv_bg.archived = false;
	level.mv_bg.alpha = .7;
	level.mv_bg.x = level.mv_originx1; //190;
	level.mv_bg.y = 45;
	level.mv_bg.sort = 100;
	level.mv_bg.color = (0,0,0);
	level.mv_bg setShader("white", level.mv_width, 85 + minMapLinesOnPage * 16); //260;

	// Title bar
	level.mv_titlebar = newHudElem();
	level.mv_titlebar.archived = false;
	level.mv_titlebar.alpha = .3;
	level.mv_titlebar.x = level.mv_originx1 + 3; //193;
	level.mv_titlebar.y = 47;
	level.mv_titlebar.sort = 101;
	level.mv_titlebar setShader("white", level.mv_width - 5, 21); //255;

	// Separator (bottom line)
	level.mv_bline = newHudElem();
	level.mv_bline.archived = false;
	level.mv_bline.alpha = .3;
	level.mv_bline.x = level.mv_originx1 + 3; //193;
	level.mv_bline.y = 110 + minMapLinesOnPage * 16;
	level.mv_bline.sort = 101;
	level.mv_bline setShader("white", level.mv_width - 5, 1); //255;
	
	// Time left
	level.mv_timeleft = newHudElem();
	level.mv_timeleft.archived = false;
	level.mv_timeleft.x = level.mv_originx1 + 5; //195;
	level.mv_timeleft.y = 115 + minMapLinesOnPage * 16;
	level.mv_timeleft.sort = 102;
	level.mv_timeleft.fontscale = 1;
	level.mv_timeleft.label = &"MAPVOTE_TIMELEFT";
	level.mv_timeleft setValue(level.ex_mapvotetime);

	// Title
	level.mv_title = newHudElem();
	level.mv_title.archived = false;
	level.mv_title.x = level.mv_originx1 + 5; //195;
	level.mv_title.y = 50;
	level.mv_title.sort = 102;
	level.mv_title.fontscale = 1.3;
	level.mv_title.label = &"MAPVOTE_TITLE";

	// Create additional info ONLY for players allowed to vote
	players = getentarray("player", "classname");
	for(i = 0; i < players.size; i++)
	{
		// Catch players joining the map vote just now (do not allow them to vote)
		if(isPlayer(players[i]) && !isDefined(players[i].mv_allowvote))
			players[i].mv_allowvote = false;

		if(players[i].mv_allowvote)
		{
			// Votes column header
			players[i].mv_headers = newClientHudElem(players[i]);
			players[i].mv_headers.archived = false;
			players[i].mv_headers.x = level.mv_originx2 - 5; //445;
			players[i].mv_headers.y = 90;
			players[i].mv_headers.sort = 102;
			players[i].mv_headers.fontscale = 1;
			players[i].mv_headers.alignX = "right";
			players[i].mv_headers.label = &"MAPVOTE_HEADERS";

			// How-to instructions
			players[i].mv_howto = newClientHudElem(players[i]);
			players[i].mv_howto.archived = false;
			players[i].mv_howto.x = 320;
			players[i].mv_howto.y = 80;
			players[i].mv_howto.sort = 102;
			players[i].mv_howto.fontscale = 1;
			players[i].mv_howto.alignX = "center";
			players[i].mv_howto.alignY = "middle";
			players[i].mv_howto.label = &"MAPVOTE_HOWTO";

			// Page info
			players[i].mv_page = newClientHudElem(players[i]);
			players[i].mv_page.archived = false;
			players[i].mv_page.x = level.mv_originx2 - 5; //445;
			players[i].mv_page.y = 115 + minMapLinesOnPage * 16;
			players[i].mv_page.sort = 102;
			players[i].mv_page.fontscale = 1;
			players[i].mv_page.alignX = "right";
			players[i].mv_page.label = &"MAPVOTE_PAGE1";
		}
	}
}

// Destroy all HUD elements
DeleteHud()
{
	// Destroy all player-based HUD elements
	players = getentarray("player", "classname");
	for(i = 0; i < players.size; i++)
	{
		if(isPlayer(players[i]) && isDefined(players[i].mv_allowvote))
		{
			if(players[i].mv_allowvote)
			{
				// For players allowed to vote
				for(j = 0; j < maxMapsOnPage(1); j++)
					if(isDefined(players[i].mv_votes[j]))
						players[i].mv_votes[j] destroy();

				for(j = 0; j < maxMapsOnPage(1); j++)
					if(isDefined(players[i].mv_maps[j]))
						players[i].mv_maps[j] destroy();

				if(isDefined(players[i].mv_indicator))
					players[i].mv_indicator destroy();

				if(isDefined(players[i].mv_page))
					players[i].mv_page destroy();

				if(isDefined(players[i].mv_howto))
					players[i].mv_howto destroy();

				if(isDefined(players[i].mv_headers))
					players[i].mv_headers destroy();
			}
			else
			{
				// For players not allowed to vote
				if(isDefined(players[i].mv_inprogress))
					players[i].mv_inprogress destroy();

				if(isDefined(players[i].mv_notallowed))
					players[i].mv_notallowed destroy();

				if(isDefined(players[i].mv_wait))
					players[i].mv_wait destroy();
			}
		}
	}
	// Destroy all level-based HUD elements
	if(isDefined(level.mv_timeleft)) level.mv_timeleft destroy();
	if(isDefined(level.mv_bline)) level.mv_bline destroy();
	if(isDefined(level.mv_title)) level.mv_title destroy();
	if(isDefined(level.mv_titlebar)) level.mv_titlebar destroy();
	if(isDefined(level.mv_bg)) level.mv_bg destroy();
}

// Calculate the number of pages available
maxPages()
{
	pages = int((level.mv_mapcanmax + 9) / 10);
	return pages;
}

// Calculate which page the player is on
onPage(choice)
{
	if(choice != 0)
	{
		page = int((choice + 9) / 10);
		if(page > maxPages()) page = 1;
	}
	else page = 1;
	return page;
}

// Calculate number of maps on page
maxMapsOnPage(page)
{
	maps = level.mv_mapcanmax;
	mapsonpage = 0;
	for(i = 1; i <= page; i++)
	{
		if(maps >= 10)
		{
			mapsonpage = 10;
			maps = maps - 10;
		}
		else
		{
			if(maps != 0)
			{
				mapsonpage = maps;
				maps = 0;
			}
			else mapsonpage = 0;
		}
	}
	return mapsonpage;
}

// Get localized string for current page
getPageInfo(page)
{
	pagexofx = &"MAPVOTE_PAGE1";
	switch(maxPages())
	{
		case 1:
			switch(page)
			{
				case 1: pagexofx = &"MAPVOTE_PAGE1OF1"; break;
			}
			break;
		case 2:
			switch(page)
			{
				case 1: pagexofx = &"MAPVOTE_PAGE1OF2"; break;
				case 2: pagexofx = &"MAPVOTE_PAGE2OF2"; break;
			}
			break;
		case 3:
			switch(page)
			{
				case 1: pagexofx = &"MAPVOTE_PAGE1OF3"; break;
				case 2: pagexofx = &"MAPVOTE_PAGE2OF3"; break;
				case 3: pagexofx = &"MAPVOTE_PAGE3OF3"; break;
			}
			break;
		case 4:
			switch(page)
			{
				case 1: pagexofx = &"MAPVOTE_PAGE1OF4"; break;
				case 2: pagexofx = &"MAPVOTE_PAGE2OF4"; break;
				case 3: pagexofx = &"MAPVOTE_PAGE3OF4"; break;
				case 4: pagexofx = &"MAPVOTE_PAGE4OF4"; break;
			}
			break;
		case 5:
			switch(page)
			{
				case 1: pagexofx = &"MAPVOTE_PAGE1OF5"; break;
				case 2: pagexofx = &"MAPVOTE_PAGE2OF5"; break;
				case 3: pagexofx = &"MAPVOTE_PAGE3OF5"; break;
				case 4: pagexofx = &"MAPVOTE_PAGE4OF5"; break;
				case 5: pagexofx = &"MAPVOTE_PAGE5OF5"; break;
			}
			break;
	}
	return pagexofx;
}
