
   new const PLUGINNAME[] = "AMX Map Handler"
   new const VERSION[] = "0.2.3"
   new const AUTHOR[] = "jghg"
   /*
   Copyleft 2003
   http://amxmod.net/forums/viewtopic.php?p=130900
   AMX MAP HANDLER
   ===============
   *IMPORTANT*
   *IMPORTANT*
   *IMPORTANT*  This  plugin  should  be  put  BEFORE  mapchooser.amx and
   mapsmenu.amx
   *IMPORTANT* in your plugins.ini file!
   *IMPORTANT*
   *IMPORTANT*
   Adding and removing maps from your server has never been this easy.
   With AMX Map Handler you just put the maps into your server and don't
   have to worry about editing mapcycle.txt or maps.ini, since this is
   done automatically.
   Maps.ini  is  used  by  the  Mapchooser  ("Time  to  choose!") and the
   Mapsmenu plugins.
   Mapcycle.txt  is  updated  with  a  console  command,  and maps.ini is
   updated
   on every map startup. Because of this it is important that you put the
   amx  file  of  this  BEFORE  mapchooser.amx  and  mapsmenu.amx in your
   plugins.ini,
   or you will have to restart map two times before you can see the newly
   added
   maps.
   After your first run you can open up maps.ini to change the default
   name  of  each map (that's within the quotes). If you remove commented
   maps
   from  server for a while, and then put them back, AMX Map Handler will
   remember
   the map name as you typed it. Nifty, eh?
   The default name of a map is its file name.
   But wait, there's more. If you download within 15 minutes, we'll add
   this nifty map listing command that has the look and feel of the
   amx_help command. It lists all *valid* maps.
   Have fun.
   /jghg
   HOW TO INSTALL
   ==============
   1. Name this file amx_maphandler.sma.
   2. Compile it into amx_maphandler.amx.
   3. Put amx_maphandler.amx into amx/plugins directory.
   4.   Open   up  amx/plugins/plugins.ini  and  add  line  RIGHT  BEFORE
   mapsmenu.amx and mapchooser.amx saying: amx_maphandler.amx
   5. Done. Type reload in your server.
   6.  Note  that  mapsmenu.amx has a default limit of 64 maps or so. You
   might  want  to change it to 128 if you have many maps and know how to
   do it.
   HOW TO USE
   ==========
   amx_listmaps [page]
           - Use this command to list maps. Works for both clients and
           server console.
   amx_mh_page
           - This is a cvar that sets how many maps should be listed
           each page. Default is 15.
           AMX Map Handler automatically summons a textfile with all valid bsp
           files residing in maps folder when a level loads. Do not touch
           this textfile. Everything is handled automatically.
   amx_mh_filename
           - This is a cvar with path/filename of the dynamically created
           maplist. Default is addons/amx/amx_maphandler.ini
   amx_mapcycle <file.txt>
           - Generates a mapcycle-style file. Specify "mapcycle.txt" to
           output to standard mapcycle file. The maps are placed in random order.
           Beware: the file you specify will be overwritten. There seems also
           be a limit of HLDS of 100 maps in mapcycle. Only 100 maps will be
           added to list.
   amx_mh_mapsini
           - This is a cvar with path/filename to maps.ini file. Default is
           addons/amx/maps.ini.
  
   VERSIONS
   ========
   0.1            First version
   0.2            Changed plugin name. :-)
               Added a lot of stuff. :-)
   0.2.1        Minor  tweak. Doesn't anymore remember map description if
   it's the same as the filename. :-)
   0.2.2        If  you  didn't  have  a file at specified amx_mh_mapsini
   location,  descriptions  wouldn't  be  added  until  next time when it
   exists.
   0.2.3        Maps  with uppercase letters would be sorted first in the
   files. This version converts all map names to lowercase.
   TO DO
   =====
   ?
   */
   #include <amxmod>
   #include <amxmisc>
   #pragma dynamic 20000
   #define MAXMAPS 128 // 120
   #define MAPLENGTH 32 // 30
   #define DESCRIPTION 64
   #define MAPDESC MAPLENGTH + DESCRIPTION
   #define STANDARDPAGESIZE 15
   #define MAXMAPSINMAPCYCLE 100
   // Globals below
   new bool:inited = false
   // Globals above
   stock charOccurances(string[],matchchar[]) {
       if (strlen(matchchar) != 1)
           return -1
       new occurances
       new len = strlen(string)
       for (new i = 0;i < len;i++) {
           if (string[i] == matchchar[0]) {
               occurances++
           }
       }

       return occurances
   }
   writemaps() {
       server_print("[%s] DEBUG - Initing", PLUGINNAME)
       // Get valid maps to multiarray
       new  pos  =  0,  fileName[MAPLENGTH + 4], mapName[MAPLENGTH], len,   mapsCount, bspPos
       new maps[MAXMAPS][MAPLENGTH]
       do {
           pos = read_dir("maps", pos, fileName, MAPLENGTH + 3, len)
           //server_print("[%s]  DEBUG - Reading %s at pos %d with length   %d", PLUGINNAME, fileName, pos, len)
           if (len >= 5) {
               bspPos = len - 4
               if (pos && containi(fileName,".bsp") == bspPos) {
                   copy(mapName,bspPos,fileName)
                   //server_print("%s:    DEBUG   -   found   map:   %s",   PLUGINNAME, mapName)
                   if (is_map_valid(mapName)) {
                       strtolower(mapName)  //  Nothing  wrong  with this   native.
                       maps[mapsCount] = mapName
                       mapsCount++
                   }
               }
           }
       }
       while (pos && mapsCount < MAXMAPS)
       // Sort
       new bool:sorted, skoj[MAPLENGTH], iLen, iLen2, shortestLen, j
       do {
           sorted = true
           for (new i = 0;i < mapsCount - 1;i++) {
               iLen = strlen(maps[i])
               iLen2 = strlen(maps[i + 1])
               if (iLen2 < iLen)
                   shortestLen = iLen2
               else
                   shortestLen = iLen

               for (j = 0;j < shortestLen;j++) {
                   if (maps[i + 1][j] < maps[i][j]) {
                       skoj = maps[i]
                       maps[i] = maps[i + 1]
                       maps[i + 1] = skoj

                       sorted = false
                       break
                   }
                   else if (maps[i + 1][j] > maps[i][j])
                       break
               }
               if (j == shortestLen) {
                   if (iLen2 < iLen) {
                       skoj = maps[i]
                       maps[i] = maps[i + 1]
                       maps[i + 1] = skoj

                       sorted = false
                   }
               }
           }
       }
       while (!sorted)
       // Get file name, write to file, start writing mapsCount
       new outfile[128]
       get_cvar_string("amx_mh_filename",outfile,127)

       new mapsCountString[8]
       numtostr(mapsCount,mapsCountString,7)

       if (file_exists(outfile))
           if (!delete_file(outfile)) {
               server_print("[AMX]  ERROR,  Couldn't  delete  %s!  Failed   initializing %s.",outfile,PLUGINNAME)
               return 0
           }
       if (!write_file(outfile,mapsCountString)) {
           server_print("[AMX]   ERROR,  Couldn't  write  to  %s!  Failed   initializing %s.",outfile,PLUGINNAME)
           return 0
       }
       for (new i = 0;i < mapsCount;i++)
           if (!write_file(outfile,maps[i])) {
               server_print("[AMX]  ERROR,  Couldn't  write to %s! Failed   initializing %s.",outfile,PLUGINNAME)
               return 0
           }
       return 1
   }
   public listmaps(id) {
       new infile[128]
       get_cvar_string("amx_mh_filename",infile,127)
       if (!inited || !file_exists(infile)) {
           console_print(id, "[AMX] Map list initialization failed.")
           return PLUGIN_HANDLED
       }
       new page = get_cvar_num("amx_mh_page")
       if (!page)
           page = STANDARDPAGESIZE

       new mapsCount, mapsCountString[10], mcs_len
       read_file(infile,0,mapsCountString,9,mcs_len)
       if (mcs_len > 0)
           mapsCount = strtonum(mapsCountString)
       else {
           console_print(id,  "[AMX]  Odd error. You aren't fiddling with   the wrong config files, are you?")
           return PLUGIN_HANDLED
       }
       new startPos = 1, arg[4], argCount = read_argc()
       if (argCount == 2) {
           read_argv(1,arg,3)
           for (new i = 0;i < strlen(arg);i++) {
               if (!isdigit(arg[i])) {
                   console_print(id,  "[AMX] You must specify a numerical   value.")
                   return PLUGIN_HANDLED
               }
           }
           startPos = strtonum(arg)
           if (startPos < 1)
               startPos = 1
           else if (startPos > mapsCount)
               startPos = mapsCount - page + 1
       }
       else if (argCount != 1) {
           console_print(id, "[AMX] Incorrect use.")
           return PLUGIN_HANDLED
       }
       new textline[64], textlength, i
       console_print(id, "----- Valid maps on server -----")
       for (i = startPos;i < startPos + page && i <= mapsCount;i++) {
           read_file(infile,i,textline,63,textlength)
           console_print(id,"%d: %s",i,textline)
       }
       console_print(id,"-----  Maps  %d  -  %d of %d -----",startPos,i - 1,mapsCount)
       if (i <= mapsCount)
           console_print(id,"-----   Use   'amx_listmaps   %d'  for  more   -----",i)
       else
           console_print(id,"-----   Use   'amx_listmaps'  for  beginning   -----")
       return PLUGIN_HANDLED
   }
   public mapcycle(id,level,cid) {
       if (!cmd_access(id,level,cid,2)) {
           console_print(id,"[AMX]  Incorrect  use.  Specify  a file that   should   be   written  as  mapcycle.  Mapcycle.txt  replaces  standard   mapcycle.")
           return PLUGIN_HANDLED
       }

       new argCount = read_argc()
       if (argCount != 2) {
           console_print(id,"[AMX]  Incorrect  use.  Specify  a file that   should   be   written  as  mapcycle.  Mapcycle.txt  replaces  standard   mapcycle.")
           return PLUGIN_HANDLED
       }
       new infile[128]
       get_cvar_string("amx_mh_filename",infile,127)

       if (!inited || !file_exists(infile)) {
           console_print(id,"[AMX] Map list initialization failed.")
           return PLUGIN_HANDLED
       }

       new mapsCount, mapsCountString[10], mcs_len
       read_file(infile,0,mapsCountString,9,mcs_len)
       if (mcs_len > 0)
           mapsCount = strtonum(mapsCountString)
       else {
           console_print(id,"[AMX]  Odd  error.  You aren't fiddling with   the wrong config files, are you?")
           return PLUGIN_HANDLED
       }
       new outfile[128]
       read_argv(1,outfile,127)
       if (file_exists(outfile)) {
           if (!delete_file(outfile)) {
               console_print(id,"[AMX]  Failed to delete already existing   '%s'.",outfile)
               return PLUGIN_HANDLED
           }
       }
       new maps[MAXMAPS][MAPLENGTH]
       new textline[MAPLENGTH], textlength, line = 1
       do {
           line      =     read_file(infile,line,textline,MAPLENGTH     -   1,textlength)
           if (line)
               maps[line - 2] = textline
       }
       while (line)
       new moveUp, temp[MAPLENGTH], mapsCount2
       for (new i = 0;i < mapsCount && i < MAXMAPSINMAPCYCLE;i++) {
           moveUp = random_num(i,mapsCount - 1)
           temp = maps[i]
           maps[i] = maps[moveUp]
           maps[moveUp] = temp
           if (!write_file(outfile,maps[i])) {
               console_print(id,"[AMX]  Failed while trying to write '%s'   to '%s'.",textline,outfile)
               return PLUGIN_HANDLED
           }
           else {
               mapsCount2++
           }
       }
       console_print(id,"[AMX]    Successfully    wrote    %d   maps   to   %s.",mapsCount2,outfile)

       return PLUGIN_HANDLED
   }
   writemapsini() {
       server_print("[AMX] Starting dump of maps...")
       // Check if we inited properly.
       new infile[128]
       get_cvar_string("amx_mh_filename",infile,127)
       if (!inited || !file_exists(infile)) {
           server_print("[AMX] Map list initialization failed.")
           return PLUGIN_HANDLED
       }
       // Get mapsCount.
       new mapsCount, mapsCountString[10], mcs_len
       read_file(infile,0,mapsCountString,9,mcs_len)
       if (mcs_len > 0)
           mapsCount = strtonum(mapsCountString)
       else {
           server_print("[AMX]  Odd  error.  You aren't fiddling with the   wrong config files, are you?")
           return PLUGIN_HANDLED
       }

       // Read in the valid maps.
       new maps[MAXMAPS][MAPDESC]
       new textline[MAPDESC], textlength, line = 1
       do {
           line = read_file(infile,line,textline,MAPDESC - 1,textlength)
           if (line)
               maps[line - 2] = textline
       }
       while (line)
       // Check if maps.ini exists.
       new mapsini[128]
       get_cvar_string("amx_mh_mapsini",mapsini,127)
       if (!file_exists(mapsini)) {
           // Doesn't exist. Just write all maps to the file.
           if (!initMapsini(mapsini)) {
               server_print("[AMX]     Error:     couldn't     initialize   '%s'!",mapsini)
               return PLUGIN_HANDLED
           }
           //  Write  "tab  +  quote  +  mapname + quote" to all posts in   maps[][] that doesn't already have that.
           for (new i = 0;i < mapsCount;i++) {
               if (charOccurances(maps[i],"^"") == 0)
                   format(maps[i],MAPDESC -   strlen(maps[i]),"%s^t^"%s^"",maps[i],maps[i])
           }
           // Write contents of maps[][] to file.
           new mapsCount2
           for (new i = 0;i < mapsCount && i < MAXMAPS;i++) {
               if (!write_file(mapsini,maps[i])) {
                   server_print("[AMX]  Failed while trying to write '%s'   to '%s'.",maps[i],mapsini)
                   return PLUGIN_HANDLED
               }
               else {
                   mapsCount2++
               }
           }
           server_print("[AMX]    Successfully    wrote    %d   maps   to   '%s'.",mapsCount2,mapsini)
           return PLUGIN_HANDLED
       }
       //  This far, the maps.ini file exists. We need to make a new one.   Make sure it doesn't exist, or delete it.
       new tempfile[] = "addons/amx/__tempfile3252.ini"
       if (file_exists(tempfile)) {
           if (!delete_file(tempfile)) {
               server_print("[AMX]   Error:   couldn't  delete  temporary   initialization file '%s'!",tempfile)
               return PLUGIN_HANDLED
           }
       }
       if (!initMapsini(tempfile)) {
           server_print("[AMX]       Error:      couldn't      initialize   '%s'!",tempfile)
           if (file_exists(tempfile)) {
               if (!delete_file(tempfile))
                   server_print("[AMX]  Error:  couldn't delete temporary   initialization file '%s'!",tempfile)
           }
           return PLUGIN_HANDLED
       }
       /*
       Read  a line from current maps.ini starting from line 0. Only keep   commented lines with two quotes.
       If it contains two quotes, write that line to the temp file.
       */
       textline = "\0"
       textlength = 0
       line = 1
       new firstWord[MAPLENGTH], bool:matches
   /*
       // If first character is a ; in firstWord, strip it
       server_print("firstword: %s",firstWord)
       if (firstWord[0] == ';') {
           server_print("Stripping firstword: %s to...",firstWord)
           replace(firstWord,MAPLENGTH,";","")
           server_print("...%s",firstWord)
       }
   */
       new occurances
       do {
           matches = false
           line = read_file(mapsini,line,textline,MAPDESC - 1,textlength)
           if (line && textlength > 0) {
               occurances = charOccurances(textline,"^"")
               if (textline[0] == ';' && occurances  == 2) {
                   if (parse(textline,firstWord,MAPLENGTH - 1)) {
                       replace(firstWord,MAPLENGTH,";","")
                       for (new i = 0;i < mapsCount;i++) {
                           if (equali(firstWord,maps[i])) {
                               // Strip ; from textline
                               replace(textline,MAPDESC,";","")
                               maps[i] = textline
                               matches = true
                               break
                           }
                       }
                   }
                   if (!matches) {
                       if (!write_file(tempfile,textline)) {
                           server_print("[AMX]  Error:  couldn't write to   temporary file '%s'!",tempfile)
                           break
                       }
                   }
               }
               else {
                   // If textline has two quotes on the line, do this:
                   //  Get  first  word of textline and see if it matches   any of the strings
                   //  in  maps[][].  If so update the maps[][] with that   whole line and break.
                   // Make sure it is lower case so it looks proper. :-)
                   // If it doesn't match, write it to temp file with a ;   before it.
                   if (charOccurances(textline,"^"") == 2) {
                       if (parse(textline,firstWord,MAPLENGTH - 1)) {
                           for (new i = 0;i < mapsCount;i++) {
                               if (equali(firstWord,maps[i])) {
                                   strtolower(textline)
                                   maps[i] = textline
                                   matches = true
                                   break
                               }
                           }
                       }
                       if (!matches) {
                           new mapNameLen = strlen(firstWord)
                           if (textline[textlength - (2 + mapNameLen)] ==   '^"' &&    equal(textline[textlength    -    (1   +   mapNameLen)],firstWord,mapNameLen) && textline[textlength - 1] == '^"') {
                               //  Don't  write line, a comment with just   the filename isn't needed to save.
                           }
                           else {
                               new commented[MAPDESC + 1] = ";"
                               copy(commented[1],MAPDESC + 1,textline)
                               if (!write_file(tempfile,commented)) {
                                   server_print("[AMX]   Error:  couldn't   write to temporary file '%s'!",tempfile)
                                   if (file_exists(tempfile)) {
                                       if (!delete_file(tempfile))
                                           server_print("[AMX]     Error:   couldn't delete temporary initialization file '%s'!",tempfile)
                                   }

                                   return PLUGIN_HANDLED
                               }
                           }
                       }
                   }
               }
           }
       }
       while (line)

       //  Write "tab + quote + mapname + quote" to all posts in maps[][]
   that doesn't already have that.
       for (new i = 0;i < mapsCount;i++) {
           if (charOccurances(maps[i],"^"") == 0)
               format(maps[i],MAPDESC -
   strlen(maps[i]),"%s^t^"%s^"",maps[i],maps[i])
       }

       // Write maps[][] to tempfile
       for (new i = 0;i < mapsCount;i++) {
           if (!write_file(tempfile,maps[i])) {
               server_print("[AMX]  Error:  couldn't  write  to temporary   file '%s'!",tempfile)
               if (file_exists(tempfile)) {
                   if (!delete_file(tempfile))
                       server_print("[AMX]    Error:    couldn't   delete   temporary initialization file '%s'!",tempfile)
               }
               return PLUGIN_HANDLED
           }
       }
       // Delete old maps.ini.
       if (!delete_file(mapsini)) {
           server_print("[AMX] Error: couldn't delete old '%s' to replace   it with a new one! New one is saved to '%s'.",mapsini,tempfile)
           return PLUGIN_HANDLED
       }

       // Write a new maps.ini from tempfile.
       textline = "\0"
       textlength = 0
       line = 0
       new writtenLines
       do {
           line      =     read_file(tempfile,line,textline,MAPDESC     -   1,textlength)
           if (line) {
               if (!write_file(mapsini,textline)) {
                   server_print("[AMX]  Error: couldn't write to map file   '%s'! Map list saved to '%s'.",mapsini,tempfile)
                   return PLUGIN_HANDLED
               }
               else
                   writtenLines++
           }
       }
       while (line)
       server_print("[AMX]     Successfully    wrote    %d    lines    to   '%s'.",writtenLines,mapsini)
       // Delete the temporary file if it still exists
       if (file_exists(tempfile)) {
           if (!delete_file(tempfile))
               server_print("[AMX]   Error:   couldn't  delete  temporary   initialization file '%s'!",tempfile)
       }
       return PLUGIN_HANDLED
   }
   initMapsini(mapsini[]) {
       new formattedLine[128]
       new amxversion[32]
       if (!write_file(mapsini,"; Maps configuration file"))
           return 0
       format(formattedLine,127,";  Automatically generated by %s version   %s",PLUGINNAME,VERSION)
       if (!write_file(mapsini,formattedLine))
           return 0
       get_cvar_string("amx_version",amxversion,31)
       format(formattedLine,127,"; AMX Mod version: %s",amxversion)
       if (!write_file(mapsini,formattedLine))
           return 0
       format(formattedLine,127,";      Running      %s     server     on   %s",is_dedicated_server() ? "dedicated" : "listen",is_linux_server() ?   "Linux" : "Windows")
       if (!write_file(mapsini,formattedLine))
           return 0
       get_cvar_string("amx_mh_mapsini",amxversion,31);
       format(formattedLine,127,"; File location: %s",amxversion)
       if (!write_file(mapsini,formattedLine)) // amx_mh_mapsini
           return 0
       if  (!write_file(mapsini,"; This file is used by Maps Menu plugin,   Nextmap chooser and maybe even more..."))
           return 0

           //; File location: $moddir/addons/amx/maps.ini
       return 1
   }
   public plugin_init() {
       register_plugin(PLUGINNAME,VERSION,AUTHOR)
       register_concmd("amx_listmaps","listmaps",0,"[page  #]  - displays   all valid maps in server")
       register_concmd("amx_mapcycle","mapcycle",ADMIN_CFG,"<file.txt>  -   specify  target  file  to  write all valid maps in random order to it,   mapcycle style")
       //register_concmd("amx_mapsini","writemapsini",ADMIN_CFG,": writes   all valid maps to maps.ini")
       new stdPageSize[4]
       numtostr(STANDARDPAGESIZE,stdPageSize,3)
       register_cvar("amx_mh_page",stdPageSize)
       /* Use this later?
       new basedir[32], workdir[64]
       get_localinfo( "amx_basedir", basedir , 31 )
       format( workdir, 63, "%s/maps.ini" , basedir )
       */
       register_cvar("amx_mh_filename","addons/amx/amx_maphandler.ini",FCVAR_SPONLY)
       register_cvar("amx_mh_mapsini","addons/amx/maps.ini",FCVAR_SPONLY)
       inited = false
       new currentMap[64]
       get_mapname(currentMap, 63);
       server_print("[AMX] Starting map: %s", currentMap)
       if (writemaps()) {
           inited = true
           writemapsini()
       }
       else
           server_print("[AMX] Failed initializing %s.",PLUGINNAME)
   }
