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
  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  = "^3Beltot, France";
  level.ex_maps[level.ex_maps.size-1].loclname  = &"^3Beltot, France^1";

  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  = "^3Brecourt, France";
  level.ex_maps[level.ex_maps.size-1].loclname  = &"^3Brecourt, France^1";

  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  = "^3Burgundy, France";
  level.ex_maps[level.ex_maps.size-1].loclname  = &"^3Burgundy, France^1";

  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  = "^3Caen, France";
  level.ex_maps[level.ex_maps.size-1].loclname  = &"^3Caen, France^1";

  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  = "^3Carentan, France";
  level.ex_maps[level.ex_maps.size-1].loclname  = &"^3Carentan, France^1";

  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  = "^3El Alamein, Egypt";
  level.ex_maps[level.ex_maps.size-1].loclname  = &"^3El Alamein, Egypt^1";

  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  = "^3Leningrad, Russia";
  level.ex_maps[level.ex_maps.size-1].loclname  = &"^3Leningrad, Russia^1";

  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  = "^3Matmata, Tunisia";
  level.ex_maps[level.ex_maps.size-1].loclname  = &"^3Matmata, Tunisia^1";

  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  = "^3Moscow, Russia";
  level.ex_maps[level.ex_maps.size-1].loclname  = &"^3Moscow, Russia^1";

  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  = "^3St. Mere Eglise, France";
  level.ex_maps[level.ex_maps.size-1].loclname  = &"^3St. Mere Eglise, France^1";

  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  = "^3Stalingrad, Russia";
  level.ex_maps[level.ex_maps.size-1].loclname  = &"^3Stalingrad, Russia^1";

  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  = "^3Toujane, Tunisia";
  level.ex_maps[level.ex_maps.size-1].loclname  = &"^3Toujane, Tunisia^1";

  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  = "^3Villers-Bocage, France";
  level.ex_maps[level.ex_maps.size-1].loclname  = &"^3Villers-Bocage, France^1";

  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  = "^3Rostov, Russia";
  level.ex_maps[level.ex_maps.size-1].loclname  = &"^3Rostov, Russia^1";

  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  = "^3Wallendar, Germany";
  level.ex_maps[level.ex_maps.size-1].loclname  = &"^3Wallendar, Germany^1";

  level.ex_maps[level.ex_maps.size] = spawnstruct();
  level.ex_maps[level.ex_maps.size-1].mapname = "mtl_facility";
  level.ex_maps[level.ex_maps.size-1].longname  = "^1MTL Facility";
  level.ex_maps[level.ex_maps.size-1].loclname  = &"^1MTL Facility^3";

  level.ex_maps[level.ex_maps.size] = spawnstruct();
  level.ex_maps[level.ex_maps.size-1].mapname = "mp_HellsKeep_NightRain";
  level.ex_maps[level.ex_maps.size-1].longname  = "^1HellsKeep NightRain";
  level.ex_maps[level.ex_maps.size-1].loclname  = &"^1HellsKeep NightRain^3";

  level.ex_maps[level.ex_maps.size] = spawnstruct();
  level.ex_maps[level.ex_maps.size-1].mapname = "mtl_raiders";
  level.ex_maps[level.ex_maps.size-1].longname  = "^1MTL Raiders";
  level.ex_maps[level.ex_maps.size-1].loclname  = &"^1MTL Raiders^3";

  level.ex_maps[level.ex_maps.size] = spawnstruct();
  level.ex_maps[level.ex_maps.size-1].mapname = "mp_omaha_v2a";
  level.ex_maps[level.ex_maps.size-1].longname  = "^1Omaha v.2a";
  level.ex_maps[level.ex_maps.size-1].loclname  = &"^1Omaha v.2a^3";

  level.ex_maps[level.ex_maps.size] = spawnstruct();
  level.ex_maps[level.ex_maps.size-1].mapname = "mp_suicide_creek";
  level.ex_maps[level.ex_maps.size-1].longname  = "^1Suicide Creek";
  level.ex_maps[level.ex_maps.size-1].loclname  = &"^1Suicide Creek^3";

  level.ex_maps[level.ex_maps.size] = spawnstruct();
  level.ex_maps[level.ex_maps.size-1].mapname = "mp_tank_depot";
  level.ex_maps[level.ex_maps.size-1].longname  = "^1Tank Depot";
  level.ex_maps[level.ex_maps.size-1].loclname  = &"^1Tank Depot^3";

  level.ex_maps[level.ex_maps.size] = spawnstruct();
  level.ex_maps[level.ex_maps.size-1].mapname = "mp_the_garage";
  level.ex_maps[level.ex_maps.size-1].longname  = "^1The Garage";
  level.ex_maps[level.ex_maps.size-1].loclname  = &"^1The Garage^3";

  level.ex_maps[level.ex_maps.size] = spawnstruct();
  level.ex_maps[level.ex_maps.size-1].mapname = "mp_underworld";
  level.ex_maps[level.ex_maps.size-1].longname  = "^1Underworld";
  level.ex_maps[level.ex_maps.size-1].loclname  = &"^1Underworld^3";

  level.ex_maps[level.ex_maps.size] = spawnstruct();
  level.ex_maps[level.ex_maps.size-1].mapname = "mp_zaafrane";
  level.ex_maps[level.ex_maps.size-1].longname  = "^1Zaafrane, Tunisia";
  level.ex_maps[level.ex_maps.size-1].loclname  = &"^1Zaafrane, Tunisia^3";

  // 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 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
    if(level.ex_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()
{
  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
PlayerNoVote()
{
  level endon("VotingDone");

  // Map vote in progress
  self.mv_inprogress = newClientHudElem(self);
  self.mv_inprogress.x = 320;
  self.mv_inprogress.y = 140;
  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 = 165;
  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 = 185;
  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 = 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 = 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 pages, so it appears behind them
  self.mv_indicator = newClientHudElem(self);
  self.mv_indicator.alignY = "middle";
  self.mv_indicator.x = 192;
  self.mv_indicator.y = 113;
  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", 254, 17);

  // 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)
flipPage(player)
{
  // IMPORTANT: do not change player's choice during page flipping!
  // 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 choices
    for(i = 0; i <= maxMapsOnPage(oldpage)-1; i++)
    {
      player.mv_maps[i].alpha = 0;
      player.mv_votes[i].alpha = 0;
    }
    // Show new choices
    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);
  }
  // Only proces if not in page flipping mode
  if(player.mv_flipchoice == 0)
  {
    indpos = 9 - ((newpage * 10) - choice);
    self.mv_indicator.y = 113 + (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;

      // Recount votes
      players = getentarray("player", "classname");
      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(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()
{
  // Background
  level.mv_bg = newHudElem();
  level.mv_bg.archived = false;
  level.mv_bg.alpha = .7;
  level.mv_bg.x = 190;
  level.mv_bg.y = 45;
  level.mv_bg.sort = 100;
  level.mv_bg.color = (0,0,0);
  level.mv_bg setShader("white", 260, 85 + maxMapsOnPage(1) * 16);

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

  // Seperator (bottom line)
  level.mv_bline = newHudElem();
  level.mv_bline.archived = false;
  level.mv_bline.alpha = .3;
  level.mv_bline.x = 193;
  level.mv_bline.y = 110 + maxMapsOnPage(1) * 16;
  level.mv_bline.sort = 101;
  level.mv_bline setShader("white", 255, 1);

  // Time left
  level.mv_timeleft = newHudElem();
  level.mv_timeleft.archived = false;
  level.mv_timeleft.x = 195;
  level.mv_timeleft.y = 115 + maxMapsOnPage(1) * 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 = 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 for players allowed to vote
  players = getentarray("player", "classname");
  for(i = 0; i < players.size; i++)
  {
    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 = 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 = 445;
      players[i].mv_page.y = 115 + maxMapsOnPage(1) * 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(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;
}
