/*********************************************************
 * Bugblatter Map Management Extension - V4.0            *
 *********************************************************
 *                                                       *
 * This plug-in is linux & win32 friendly.               *
 *                                                       *
 * Version 4.0:                                          *
 *                                                       *
 *  - Removed warnings compiling on Admin Mod 2.50.56    *
 *                                                       *
 * Version 3.9:                                          *
 *  - Mixed/wrong case nominations using the full map    *
 *    name no longer cause an error when attempting to   *
 *    change the map on linux                            *
 *  - Removed debug message left in log on release 3.8   *
 *  - Correction to German custom map download message.  *
 *  - Puts version number in log on start of map.        *
 *                                                       *
 * Version 3.8:                                          *
 *                                                       *
 *  - Coloured / longer timed text on AM 2.50.53/2.51.00 *
 *  - Corrected error in reporting results of an         *
 *    admin_vote_map when yes won, but insufficient      *
 *    votes were obtained to change.                     *
 *  - Setting bbmap_banlast to more than the number of   *
 *    maps you have will no longer cause the plugin to   *
 *    cause a runtime error when a map is nominated and  *
 *    will now correctly report that the map is banned.  *
 *                                                       *
 *                                                       *
 * Version 3.7:                                          *
 *                                                       *
 *  - BUGFIX: after an extension if there weren't enough *
 *    nominations the plugin would think it had already  *
 *    used the maps in mapcycle.txt that it put in the   *
 *    menu for the first vote, and if there weren't      *
 *    enough more maps in mapcycle.txt it would start    *
 *    using maps from maps.ini when it didn't need to    *
 *  - BUGFIX: timeleft reported the wrong value when     *
 *    mode was set to off.                               *
 *                                                       *
 * Version 3.6:                                          *
 *                                                       *
 *  - Support for Admin Mod 2.51.00 with built-in event  *
 *    handling so no need for logd                       *
 *  - BUGFIX: admin_map now calls the originial version  *
 *    in plugin_base when the mode is set to "off"       *
 *  - BUGFIX: bbmap_duration settings for wins / frags   *
 *    did not work when the plugin was in cycle mode     *
 *  - Uses value of mp_chattime to set duration when     *
 *    changing maps instead of a fixed 10 seconds        *
 *  - BUGFIX: if say 10 people rockthevote, then the     *
 *    plugin is switched to cycle mode, then enough      *
 *    players who haven't rocked leave for 10 to be      *
 *    a big enough % to start the vote, the vote would   *
 *    start even though its in cycle mode. Now it won't  *
 *                                                       *
 *                                                       *
 * Version 3.5:                                          *
 *                                                       *
 *  - Update to german translation strings               *
 *  - Updated for language plugin version 3.3            *
 *  - Fixed harmless AMX error 4 when you typed a bad    *
 *    map name into admin_vote_map.                      *
 *  - Retries map change after a failure                 *
 *  - Default value for bbmap_rockthevote N% was 0. It   *
 *    has been changed to 60.                            *
 *                                                       *
 * Version 3.4:                                          *
 *                                                       *
 *  - Fixed bug in the 3.3 rockthevote that made it rock *
 *    as soon as one player used the command, if they    *
 *    used it *before* the rock time limit.              *
 *                                                       *
 *  - No longer counts bots when evaluating rockthevote  *
 *    conditions.                                        *
 *                                                       *
 * Version 3.3:                                          *
 *                                                       *
 *  - All chat commands now work in team chat too        *
 *                                                       *
 *  - Prevents spamming chat with numbers while vote is  *
 *    in progress.                                       *
 *                                                       *
 *  - Players can now use rockthevote when an admin is   *
 *    present. Admins can cancel it with admin_abortvote *
 *                                                       *
 *  - Admins using rockthevote in chat won't start an    *
 *    immediate vote until other criteria are met as per *
 *    an ordinary player. Admins can still start a vote  *
 *    immediately using bbmap_rockthevote in console.    *
 *                                                       *
 *  - Fixed minor bug in reseting sync would reset to    *
 *    value of DEFAULT_ECHO, not DEFAULT_SYNC. This isnt *
 *    a big issue as both are defined as 1 anyway.       *
 *                                                       *
 * Version 3.2:                                          *
 *                                                       *
 *  - French translation updated                         *
 *                                                       *
 * Version 3.1:                                          *
 *                                                       *
 *  - Listmaps now stops at 200 to prevent overflows     *
 *    You can change this limit by editing the constant  *
 *    below.                                             *
 *  - Freeze time modification and bury removed and      *
 *    replaced with setting sv_maxspeed to 0 (thanks     *
 *    Cavey)                                             *
 *  - Drops weapons on map change                        *
 *  - Improved the error messages on bad adminmod config *
 *  - Prevented a possible inoccuous division by zero    *
 *    error on the last player disconnecting             *
 *  - Numerous changes to the german translation         *
 *    by [POPL]Kamikatze. I trust they are fixes ;)      *
 *                                                       *
 *                                                       *
 *                                                       *
 * Version 2.6:                                          *
 *                                                       *
 *  -  Reversed order of listmaps.                       *
 *  -  Put handlers in for when critical timers fail to  *
 *     start.                                            *
 *  -  Fixed bug in bbmap_duration wins and wins lead    *
 *     not working due to debug code being left enabled  *
 *  -  Added colour and timing settings for all          *
 *     messageex class on 2.51.xx alpha builds.          *
 *  -  Modified bbmap_rockthevote to support more modes  *
 *  -  Plugin now refuses to start it path to maps.ini   *
 *     is absolute rather than relative .                *
 *  -  BUG FIX: In version 2.5, nextmap/timeleft after   *
 *     a vote has taken place would no report that the   *
 *     vote was still to happen.                         *
 *  -  Freeze time is now reset before the map ends to   *
 *     prevent a 1 minute freeze at the start of the     *
 *     next map, if you don't set it in server.cfg       *
 *  -  Kills main timer on rockthevote / admin map       *
 *     / admin vote map                                  *
 *     prevent a 1 minute freeze at the start of the     *
 *  -  Handles an admin using admin_map during a vote    *
 *  -  BUG FIX: Reports next map correctly in cycle mode *
 *                                                       *
 * Version 2.5:                                          *
 *                                                       *
 *  -  Move prominent error messages in log              *
 *  -  Fixed bug in handling too many nominations - it   *
 *     continually replaced the last one, even if there  *
 *     were multiple nominations from one player         *
 *  -  Audio announcement of admin_map                   *
 *  -  Audio announcements no use speakto instead of     *
 *     exec client                                       *
 *                                                       *
 * Version 2.4:                                          *
 *                                                       *
 *  -  Updated for adminmod 2.50.50                      *
 *  -  Uses bbdir vault setting for file locations, then *
 *     modfolder/addons/adminmod/config then mod folder. *
 *  -  Increased stack space just in case - the plugin   *
 *     ran close to the 8K limit                         *
 *  -  Changing time limit has immediate effect now - no *
 *     need to wait for the next map                     *
 *  -  Error handling in plugin_init revised             *
 *  -  French & German translation strings updated       *
 *  -  Works around bug in calling admin_command bb,,,   *
 *     from in a .cfg file saved in DOS format           *
 *  -  New acces rules for rockthevote - its level 1 if  *
 *     admins are playing. Level 2 if there are          *
 *  -  Admin_vote_map is now round synchronised and      *
 *     plugin_antiadminkick compatible                   *
 *  -  Admin_map now accepts short map names - e.g.      *
 *     "admin_map dust"                                  *
 *  -  listmaps now accepts a search string              *
 *  -  Supports admin_abort_vote any vote that has a     *
 *     stay on map feature.                              *
 *  -  Allows you to edit maps.ini whilst server is      *
 *     running without overwriting your changes          *
 *  -  BUGFIX: MaxExtends has been broken since 2.3      *
 *  -  Listmaps now shows available and banned maps      *
 *     in seperate lists                                 *
 *  -  Maximum value for banlast increased.              *
 *  -  Implemented admin_listmaps.                       *
 *  -  BUGFIX: admin_command bbmap_rockthevote with no   *
 *     players connected would break voting for the      *
 *     rest of the map                                   *
 *  -  BUGFIX: If standardmaps.ini was used, custom maps *
 *     were not banned when they won the vote.           *
 *  -  Now everyone sees the error message when you      *
 *     nominate a banned map. History file said this was *
 *     added in 2.2,but I kinda forgot ;)                *
 *  -  Lots of new speach added.                         *
 *  -  Improved prevention of votes during weapon buying *
 *     period.                                           *
 *  -  Everyone sees the results of "nominations"        *
 *  -  BUGFIX: Location of list of nominated maps was    *
 *     governed by FLAG_NOMIANTIONS  when it should have *
 *     been FLAG_NOMINATED_MAPS                          *
 *  -  Map will only be delayed for weapons buying once  *
 *     this allows map vote to take place on tiny maps   *
 *     with 10 second rounds!                            *
 *                                                       *
 * Version 2.3:                                          *
 *                                                       *
 *  -  Allow multiple conditions for map change          *
 *  -  Announces reason for map change                   *
 *  -  Enable/Disable command echo                       *
 *  -  Enable/Disable round synchonisation so it works   *
 *     with logd when the game mod doesn't have rounds   *
 *  -  Checks values of server.cfg vars and reads name   *
 *     of maps.ini                                       *
 *  -  Compatible with plugin_antiadminkick from !2SX!   *
 *  -  Improved parsing of maps.ini and mapcycle.txt     *
 *  -  Reports non-standard maps                         *
 *  -  Reports map download URLs                         *
 *                                                       *
 * Version 2.2:                                          *
 *                                                       *
 *  - replaces statsme /timeleft and also supports       *
 *    /nextmap and /currentmap                           *
 *  - bbmap_duration replaces mp_timelimit and allows    *
 *    more modes                                         *
 *  - map cannot be nominated error message is displayed *
 *    to all users, not just nominator                   *
 *  - bbmap_nomination on/off added                      *
 *  - admin_vote_map reverts to old behaviour when it is *
 *    issued by a play with ACCESS_MAP access.           *
 *    Acts as a nomination for other players             *
 *  - logd detected messages aren't logged anymore       *
 *                                                       *
 * Version 2.1:                                          *
 *                                                       *
 *  - multi-lingual!                                     *
 *  - centered messages are now all green and don't      *
 *    display for as long :( - Side effect of            *
 *    multi-lingual                                      *
 *  - does not include extend on the vote menu if        *
 *    no-one is playing.                                 *
 *  - Updated for adminmod 2.50.37                       *
 *                                                       *
 * Version 2.0:                                          *
 *                                                       *
 *  - listmaps wording changed to be less confusing      *
 *  - Default mesage location changed                    *
 *  - "bbmap_mode now" replaced with bbmap_rockthevote   *
 *    with no arguments.                                 *
 *  - rockthevote time restrictions are no longer        *
 *    enforced if issued by a player with ADMIN_MAP      *
 *    access.                                            *
 *                                                       *
 *                                                       *
 * Version 1.9:                                          *
 *                                                       *
 *  - Copes with as few as two maps in maps.ini          *
 *  - Fixed a bug in that if no extension would be       *
 *    available at the end of the round and the vote     *
 *    was rocked, the wrong winner was picked from the   *
 *    rocked vote.                                       *
 *  - Plugin is now bot compatible.                      *
 *  - Report can be turned on and off now                *
 *  - Fails gracefully if maps.ini or mapcycle.txt is    *
 *    too big.                                           *
 *  - Copes with \minplayers and \maxplayers in          *
 *    mapcycle.txt for all mods - not just TFC           *
 *                                                       *
 * Version 1.8:                                          *
 *                                                       *
 *  - Name changed from map voting plugin.               *
 *  - Names of admin commands and vault settings changed *
 *    to fit new conventions. Names of associated global *
 *    variables and functions also updated.              *
 *  - Cycle mode added to provide improved cycling       *
 *    without enabling voting.                           *
 *  - bbmap_report command added.                        *
 *  - Support functions moved from sma file to include   *
 *    files.                                             *
 *  - votemap in the console now functions as nominiate  *
 *  - admin_vote_map in the console now functions as a   *
 *    nominate followed by rockthevote if the nomination *
 *    was sucessfull                                     *
 *  - admin_map is now implemented in the plugin so that *
 *    adminpass is exec'd, and the maps.ini is updated   *
 *  - only maps in mapcycle.txt are used when filling    *
 *    blank spaces in the voting menu (Thanks to camper  *
 *    for these changes)                                 *
 *  - Welcome message removed - it was unncessary and    *
 *    obscured important info from my other plugins      *
 *                                                       *
 * Version 1.7:                                          *
 *                                                       *
 *  - Improved nextmap message after voting when logd    *
 *    is present                                         *
 *                                                       *
 *  - Increase showscores and safety margin times        *
 *                                                       *
 *                                                       *
 * Version 1.6:                                          *
 *                                                       *
 *  - Plays 'choose now' sound as voting starts.         *
 *  - 'rockthevote' and 'bb_mapvote now' will both now   *
 *    use the 20 second countdown timer before poping    *
 *    up the menu.                                       *
 *  - Using 'rockthevote' or 'bb_mapvote now' during     *
 *    the countdown no longer works.                     *
 *  - Logd intergration to prevent voting at the start   *
 *    of a round                                         *
 *  - bb_maps command returned PLUGIN_CONTINUE instead   *
 *    of plugin handled, which resulted in a needless    *
 *    error appearing in the console after the map list  *
 *                                                       *
 * Version 1.5:                                          *
 *                                                       *
 *  - Prevents nominating the current map, however it    *
 *    was selected.  Also prevents the current map being *
 *    used to fill the voting menu                       *
 *  - scores are now shown at map change reliably        *
 *  - bb_maps function implemented to list all maps      *
 *  - Intergated !2SX!'s antiadminkick plugin - enable   *
 *    it with bb_mapexecadminpass on                     *
 *                                                       *
 * Version 1.4:                                          *
 *                                                       *
 *  - Fixed bug in failing to increase mp_timelimit      *
 *    when the map was extended.  Introduced in 1.3.     *
 *  - Added verbosity & message location control.        *
 *  - Added reset command.                               *
 *  - Made all admin commands report back the config     *
 *  - Map names / say commands are case insensitive -    *
 *    nominating 'dust' and 'Dust' won't result in two   *
 *    nominations.                                       *
 *  - Prevents duplication menu entires if you nominate  *
 *    a map that would be used to fill empty menus       *
 *  - Added support for 'rockthevote'                    *
 *  - Handles errors if another plugin is using the      *
 *    menu system. postpones the vote for 1 minute       *
 *                                                       *
 *                                                       *
 * Version 1.3:                                          *
 *                                                       *
 *  - Fixed bug in setting mp_timelimit 60 times too     *
 *    high.                                              *
 *                                                       *
 * Version 1.2:                                          *
 *                                                       *
 *  - Fixed bug in saving to maps.ini                    *
 *                                                       *
 * Version 1.0 - Initial Release                         *
 *********************************************************
 */
 
#pragma dynamic 8192
 
#include <core>
#include <console>
#include <string>
#include <admin>
#include <adminlib>
#include <plugin>
 
#include "clientio"
#include "settings"
#include "language"
#include "stringx"
#include "speech"
 
new g_Version[] = "4.0";
 
/*
 *********************************************************
 *         CONSTANTS THAT YOU MAY NEED TO CHANGE         *
 *********************************************************
*/
 
/* Maximum number of maps that will be included in rotation */
#define MAX_MAPS 200
 
/* Maximum number of teams in any team-play mod - can be too big*/
#define MAX_TEAMS 4
 
/* Increase this to list more maps. Decrease it if your players
 * are getting overflow errors then kicked when they use listmaps */
#define MAX_MAPLISTLENGTH 200
 
/*
 *********************************************************
 *          CONSTANTS THAT YOU SHOULDN'T TOUCH           *
 *********************************************************
*/
/* Longest allowed map filename */
#define MAX_MAP_LENGTH 32
/* Time after vote finishes before map change */
#define MAP_CHANGE_DELAY 20
/* Time after vote finishes when URL to download custom map is announced */
#define MAP_CUSTOM_DELAY 5
 
/* Default Number of maps in the nominations menu */
#define DEFAULT_NOMINATIONS 4
/* Maximum Number of maps in the nominations menu */
#define MIN_NOMINATIONS 2
#define MAX_NOMINATIONS 8
 
/* Amount of time to extend the map if that wins the vote (minutes) */
#define DEFAULT_EXTEND_TIME 10
/* Maximum allowed extend time */
#define MIN_EXTEND_TIME 1
#define MAX_EXTEND_TIME 60
 
/* Default number of previous maps that can't be nominated */
#define DEFAULT_BAN_LAST 3
/* Maximum number of previous maps that can't be nominated */
#define MIN_BAN_LAST 0
#define MAX_BAN_LAST 100
 
/* Default maximum number of times the map can be extended */
#define DEFAULT_MAX_EXTENDS 3
/* Maxmimum maximum number of times the map can be extended */
#define MIN_MAX_EXTENDS 0
#define MAX_MAX_EXTENDS 1000
 
 
#define DEFAULT_ROCKTHEVOTE 10
#define MIN_ROCKTHEVOTE 0
#define MAX_ROCKTHEVOTE 1000
 
#define DEFAULT_ROCKSNEEDED 1
#define MIN_ROCKSNEEDED 1
#define MAX_ROCKSNEEDED 32
 
#define DEFAULT_ROCKSPERCENT 60
#define MIN_ROCKSPERCENT 0
#define MAX_ROCKSPERCENT 100
 
 
#define DEFAULT_MESSAGEFREQUENCY 2
#define MIN_MESSAGEFREQUENCY 1
#define MAX_MESSAGEFREQUENCY 1000
 
#define DEFAULT_NOMINATION 1
#define MIN_NOMINATION 0
#define MAX_NOMINATION 1
 
#define DEFAULT_REPORT 1
#define MIN_REPORT 0
#define MAX_REPORT 1
 
#define DEFAULT_ECHO 1
#define MIN_ECHO 0
#define MAX_ECHO 1
 
#define DEFAULT_SYNC 1
#define MIN_SYNC 0
#define MAX_SYNC 1
 
 
#define DEFAULT_DURATIONMINS 30
#define DEFAULT_DURATIONROUNDS 0
#define DEFAULT_DURATIONWINS 0
#define DEFAULT_DURATIONWINSLEAD 0
#define DEFAULT_DURATIONFRAGS 0
#define DEFAULT_DURATIONFRAGSLEAD 0
 
#define MIN_DURATION 0
#define MAX_DURATION 1000
 
 
#define MIN_DURATIONMINS 0
#define MAX_DURATIONMINS 1000
 
 
 
/* Default verbosity is all on */
#define DEFAULT_VERBOSITY 1023
#define MIN_VERBOSITY 0
#define MAX_VERBOSITY 1023
 
/* Default message location is all centered except for nominations, nominated maps and winner of game */
#define DEFAULT_MESSAGELOCATION 755
#define MIN_MESSAGELOCATION 0
#define MAX_MESSAGELOCATION 1023
 
#define FLAG_WELCOMEMESSAGE 1
#define FLAG_AVAILABLEMAPS 2
#define FLAG_NOMINATEDMAPS 4
#define FLAG_NOMINATION 8
#define FLAG_COUNTDOWNMESSAGE 16
#define FLAG_COUNTDOWNTALK 32
#define FLAG_VOTINGINPROGRESS 64
#define FLAG_WINNEROFVOTE 128
#define FLAG_WINNEROFGAME 256
#define FLAG_CUSTOMMAP 512
 
#define SAFETY_MARGIN 600
 
#define ANNOUNCE_RED 40
#define ANNOUNCE_GREEN 120
#define ANNOUNCE_BLUE 255
 
#define ERROR_RED 255
#define ERROR_GREEN 10
#define ERROR_BLUE 10
 
/*
 *********************************************************
 *          CONSTANTS FOR MULTI-LINGUAL MESSAGES         *
 *********************************************************
*/
 
new MSG_VOTINGDISABLED[]="Map voting is currently disabled on this server.|Le vote de map est actuellement desactive sur ce serveur.|Das Map-Voting auf diesem Server ist momentan deaktiviert.";
new MSG_USAGE[]="Usage: %1s|Traitement: %1s|Verwendung: %1s";
new MSG_UNKNOWNMAPNAME[]="Unknown map name: %1s|Nom de map inconnu: %1s|Unbekannter Map-Name: %1s";
new MSG_REPORTTITLE[]="Bugblatter map voting configuration:|Configuration de vote de map Bugblatter:|Bugblatter Map-Voting Konfiguration:";
new MSG_RPT_DISABLED[]="Map management is currently disabled (bbmap_mode)|Le management de map est actuellement desactive. (bbmap_mode)|Map-Management ist momentan deaktiviert (bbmap_mode)";
new MSG_RPT_VOTING[]="Map management is currently in voting mode (bbmap_mode)|Le management de map est actuellement en phase de vote. (bbmap_mode)|Map-Management ist momentan im Voting-Mode (bbmap_mode)";
new MSG_RPT_CYCLE[]="Map management is currently in cycle mode (bbmap_mode)|Le management de map est actuellement en phase de cycle. (bbmap_mode)|Map-Management ist momentan im Cycle-Mode (bbmap_mode)";
 
new MSG_RPT_DURATION[]="Map vote/cycle will automatically occur based on the following (bbmap_duration)|Le vote et l'enchainement des maps suit les regles suivantes (bbmap_duration)|Der Map vote/cycle wird automatisch wie folgt geschehen (bbmap_duration)";
new MSG_RPT_DURATIONMINUTES_ON[]=" - After %1i minutes of play on this map| - Apres %1i minutes de jeu sur cette map| - Nach %1i Minuten Spielzeit auf dieser Map";
new MSG_RPT_DURATIONMINUTES_OFF[]=" - Time played on map is ignored| - Le temps passe sur la map est ignore| - Gespielte Zeit auf der Map wird ignoriert";
new MSG_RPT_DURATIONROUNDS_ON[]=" - After %1i rounds has been played on this map| - Apres %1i rounds joues sur cette map| - Nach %1i gespielten Runden auf dieser Map";
new MSG_RPT_DURATIONROUNDS_OFF[]=" - Total number of rounds played is ignored| - Le nombre de rounds joues sur la map est ignore| - Gespielte Runden auf der Map werden ignoriert";
new MSG_RPT_DURATIONWINS_ON[]=" - After %1i rounds have been won by one team| - Apres %1i rounds remportes par une equipe| - Nach %1i von einem Team gewonnenen Runden";
new MSG_RPT_DURATIONWINS_OFF[]=" - Number of rounds won by one team is ignored| - Le nombre de rounds remportes par une equipe est ignore| - Von einem Team gewonnene Runden werden ignoriert";
new MSG_RPT_DURATIONWINSLEAD_ON[]=" - After one team has a lead of %1i rounds| - Lorsqu'une equipe mene le score avec %1i points d'avance| - Nachdem ein Team %1i gewonnene Runden Vorsprung hat";
new MSG_RPT_DURATIONWINSLEAD_OFF[]=" - Size of the winning teams lead is ignored| - Les points d'ecart entre les equipes sont ignores| - Vorsprung der gewonnenen Runden eines Teams werden ignoriert";
new MSG_RPT_DURATIONFRAGS_ON[]=" - After %1i frags have been made by one player| - Quand un joueur a fait %1i frags| - Nachdem %1i Frags durch einen Spieler gemacht wurden";
new MSG_RPT_DURATIONFRAGS_OFF[]=" - Number of frags by each player is ignored| - Les nombres de frags des joueurs sont ignores| - Les points d'ecarts entre les meilleurs fraggers est ignore| - Anzahl der Frags eines Spielers wird ignoriert";
new MSG_RPT_DURATIONFRAGSLEAD_ON[]=" - After one player has a lead of %1i frags over the nearest opponent| - Quand le meilleur fragger a %1i frags de plus que le suivant| - Nachdem ein Spieler mit %1i Frags in Bezug auf den besten Gegner fuehrt";
new MSG_RPT_DURATIONFRAGSLEAD_OFF[]=" - Size of wining player's frags lead is ignored| - Les points d'ecarts entre les meilleurs fraggers est ignore| - Fragvorsprung des besten Spielers wird ignoriert";
 
new MSG_RPT_MENUSIZE[]="Players may nominate %1i different maps, effective after this vote. (bbmap_menusize)|Les joueurs peuvent nominer %1i maps differentes, en vigueur apres ce vote. (bbmap_menusize)|Es koennen %1i verschiedene Maps nominiert werden, wirksam ab naechstem Vote. (bbmap_menusize)";
new MSG_RPT_EXTEND[]="Players may vote to extend the map %1i times before the option is removed. (bbmap_extends)|Les joueurs peuvent voter %1i fois pour prolonger la map, avant que cette option soit enlevee. (bbmap_extends)|Maps koennen maximal %1i mal verlaengert werden. (bbmap_extends)";
new MSG_RPT_EXTENDTIME[]="Maps extensions will last for %1i minutes. (bbmap_extendtime)|Les extensions de maps vont durer %1i minutes. (bbmap_extendtime)|Map-Verlaengerungen dauern %1i Minuten. (bbmap_extendtime)";
new MSG_RPT_BANLAST[]="Players are be unable to nominate the last %1i maps played. (bbmap_banlast)|Les joueurs ne peuvent pas nominer les %1i dernieres maps jouees. (bbmap_banlast)|Die %1i zuletzt gespielten Maps koennen nicht nominiert werden. (bbmap_banlast)";
new MSG_RPT_VERBOSITY[]="Message verbosity is set to %1i - see manual for more details. (bbmap_verbosity)|La verbosite des messages est reglee a %1i - Consulter le manuel pour plus de details. (bbmap_verbosity)|Mitteilungsgrad ist auf %1i gesetzt - fuer Details siehe Anleitung. (bbmap_verbosity)";
new MSG_RPT_LOCATION[]="Message location is set to %1i - see manual for more details. (bbmap_msglocation)|L'emplacement des messages est reglee a %1i - Consulter le manuel pour plus de details. (bbmap_msglocation)|Mittleilungsort ist auf %1i gesetzt - fuer Details siehe Anleitung. (bbmap_msglocation)";
new MSG_RPT_FREQUENCY[]="Announcement messages are displayed every %1i miuntes. (bbmap_msgfrequency)|Les messages d'annonce sont affiches toutes les %1i minutes. (bbmap_msgfrequency)|Ankuendigungen werden alle %1i Minuten angezeigt. (bbmap_msgfrequency)";
new MSG_RPT_ROCKTHEVOTE[]="Players can rock the vote after %1i minutes. (bbmap_rockthevote)|Les joueurs  peuvent forcer le vote apres %1i minutes. (bbmap_rockthevote)|*Rockthevote* ist nach %1i Minuten moeglich. (bbmap_rockthevote)";
new MSG_RPT_ROCKSNEEDED[]="%1i players must rock to start a vote. (bbmap_rockthevote)|%1i joueurs doivent rockthevote afin de demarrer un vote. (bbmap_rockthevote)|%1i Spieler muessen *rockthevote* benutzen um ein Voting zu starten. (bbmap_rockthevote)";
new MSG_RPT_ROCKSPERCENT[]="%1i percent of players must rock to start a vote. (bbmap_rockthevote)|%1i pourcent de joueurs doivent rockthevote afin de demarrer un vote. (bbmap_rockthevote)|%1i Prozent der Spieler muessen *rockthevote* benutzen um ein Voting zu starten. (bbmap_rockthevote)";
new MSG_RPT_NOMINATION_ON[]="Players can nominate maps. (bbmap_nomination)|Les joueurs peuvent nominer des maps. (bbmap_nomination)|Spieler koennen Maps nominieren. (bbmap_nomination)";
new MSG_RPT_NOMINATION_OFF[]="Players can not nominate maps. (bbmap_nomination)|Les joueurs ne peuvent pas nominer de map. (bbmap_nomination)|Spieler koennen keine Maps nominieren. (bbmap_nomination) ";
new MSG_RPT_ECHO_ON[]="Players see commands others have typed in chat. (bbmap_echo)|Les joueurs voient les commandes que les autres tapent en chat. (bbmap_echo)|Spieler koennen die Chat-Eingaben anderer Mitspieler sehen. (bbmap_echo)";
new MSG_RPT_ECHO_OFF[]="Players do not see commands others have typed in chat. (bbmap_echo)|Les joueurs ne voient pas les commandes que les autres tapent en chat. (bbmap_echo)|Spieler koennen die Chat-Eingaben anderer Mitspieler nicht sehen. (bbmap_echo)";
new MSG_RPT_SYNC_ON[]="Map changes are synchronised to round changes. (bbmap_sync)|Les changements de maps sont synchronises avec les fins de rounds. (bbmap_sync)|Mapwechsel werden mit dem Rundenende synchronisiert. (bbmap_sync)";
new MSG_RPT_SYNC_OFF[]="Map changes are not synchronised to round changes. (bbmap_sync)|Les changements de maps ne sont pas synchronises avec les fins de rounds. (bbmap_sync)|Mapwechsel werden nicht mit dem Rundenende synchronisiert. (bbmap_sync)";
new MSG_RPT_DOWNLOAD[]="URL for custom map downloads is :%1s (bbmap_mapsurl)|URL de telechargement des maps : %1s (bbmap_mapsurl)|URL zum downloaden der Custom-Maps: %1s (bbmap_mapsurl)";
 
 
new MSG_MAPLISTINCONSOLE[]="A Full Map list has been displayed in your console - press ~ or `|Une liste complete des maps a ete affichee dans votre console|Die Liste aller Maps wurde auf der Konsole ausgegeben - druecke ^^";
new MSG_RESULTSINCONSOLE[]="Search results displayed in your console - press ~ or `|Resultats de recherche montres dans votre console|Suchresultate werden in Ihrer Konsole gezeigt - druecke ^^";
new MSG_TYPEVERSION[]="Type 'admin_version' in the console for version information.|Tapez 'Admin_version' dans votre console pour connaitre la version.|Fuer Versionsinformationen 'admin_version' in der Konsole eingeben.";
new MSG_VOTINGINPROGRESSNOMINATE[]="Voting is in progress. You cannot nominate at this time.|Le vote est en progression. Vous ne pouvez pas nominer en ce moment.|Das Voting laeuft gerade. Nominierungen sind deswegen momentan nicht moeglich.";
new MSG_VOTEEXTEND[]="'vote extend' is not necessary on this server.|Le 'vote d'extension' n'est pas necessaire sur ce serveur.|'vote extend' ist auf diesem Server nicht notwendig.";
new MSG_MAPNOTFOUND[]="Map %1s was not found on this server.|La map %1s n'a pas ete trouvee sur ce serveur.|%1s wurde auf diesem Server nicht gefunden.";
new MSG_MAPALREADYNOMINATED[]="Map %1s has already been nominated.|La map %1s a deja ete nominee.|%1s wurde bereits nominiert !";
new MSG_MAPBANNED[]="You cannot nominate map %1s because it was the last map played. Type listmaps for more information.|Vous ne pouvez pas nominer la map %1s car c'est la derniere map jouee. dactylographiez les listmaps.|%1s wurde gerade gespielt und kann daher nicht nominiert werden. Sage *listmaps*.";
new MSG_MAPBANNED2[]="You cannot nominate map %1s because it was one of the last %2i maps played. Type listmaps for more information.|Vous ne pouvez pas nominer la carte %1s car c'est l'une des %2i dernieres map jouees. dactylographiez les listmaps.|Map %1s ist eine der %2i zuletzt gespielten Maps und kann daher nicht nominiert werden. Sage *listmaps*";
new MSG_NOMINATIONSFULL[]="Sorry - The maximum number of nominations has already been reached.|Desole - Le nombre maximum de nominations a deja ete atteint.|Die maximale Anzahl an Nominierungen ist leider schon erreicht.";
new MSG_NOMINATED[]="%1s has nominated %2s. To nominate a map, say: ^"nominate MAPNAME^"|%1s a nomine %2s. Afin de nominer, say: ^"nominate MAPNAME^"|%1s hat %2s fuer das Voting nominiert. Sage einfach: vote ^"MAPNAME^"";
new MSG_MAPLISTTRUNCATED[]="%1i more maps not listed to prevent overflow - use: listmaps <search string>.|Encore %1i map non listees afin d'empecher le depassement - utilisez listmaps <nom map>.|%1i Maps werden aus Platzmangel nicht angezeigt. Benutze *listmaps <Sucheingabe>*";
new MSG_SEARCHFAILED[]="No maps match search string ^"%1s^"|Aucune corde ^"%1s^" de recherche d'allumette de cartes |Keine Map entspricht der Suche nach ^"%1s^"";
new MSG_VOTINGALREADYINPROGRESS[]="Voting already in progress.|Le vote est toujours en progression.|Das Voting laeuft bereits...";
new MSG_ROCKBANNED[]="Administrator has not enabled 'vote rocking' on this server|L'administrateur a autorise le 'vote rocking' sur ce serveur.|Der Administrator hat 'vote rocking' auf diesem Server nicht aktiviert.";
new MSG_ROCKSREQUIRED[]="%1i more players must rockthevote to start a vote.|%1i joueurs supplementaires soivent rockthevote afin de demarrer un vote.|Noch %1i Spieler muessen rockthevote benutzen um ein Voting zu starten.";
/*new MSG_ROCKADMINPRESENT[]="You cannot rock the vote when an administrator is connected.|Vous ne pouvez pas rockthevote quand un administrateur est connecte.|Du kannst rockthevote nicht benutzen wenn ein Administrator auf dem Server ist!";*/
new MSG_ROCKEDALREADY[]="You have already requested to rock the vote. %1i more needed.|Vous avez deja demande le rockthevote. %1i demande supplemantaire est requise.|Du hast *rockthevote* schon benutzt! Es werden noch %1i Stimmen benoetigt.";
new MSG_ROCKNOTALLOWED[]="Another vote is in progress, or has recently finished. Try again later.|Un autre vote est en progression, ou a recemment finit. Recommencez plus tard.|Voting laeuft bereits oder wurde gerade beendet. Versuche es spaeter nochmal.";
new MSG_ROCKIN1MINUTE[]="Vote cannot be rocked for another 1 minute|Le vote ne peut etre force avant 1 minute.|*rockthevote* noch eine Minute gesperrt.";
new MSG_ROCKINNMINUTES[]="Vote cannot be rocked for another %1i minutes|Le vote ne peut etre force avant %1i minutes.|*rockthevote* noch %1i Minuten gesperrt.";
new MSG_VOTINGABOUTTOBEGIN[]="Map voting is about to begin|Le vote de map est sur le point de commencer.|Das Map-Voting beginnt gleich !";
new MSG_VOTINGPAUSED[]="Map voting paused while weapons are purchased|Le vote de map est suspendu pendant l'achat d'armes.|Map-Voting bis nach der Kaufphase unterbrochen...";
new MSG_VOTINGINPROGRESS[]="Map voting in progress...|Vote de map en progression...|Map-Voting wurde gestartet...";
new MSG_DURATIONUSAGE[]="Usage: bbmap_duration <n> minutes or <n> rounds or <n> wins or <n> lead|Erreur: bbmap_duration <n> minutes ou <n> rounds ou <n> victoires ou <n> defaites|Verwendung: bbmap_duration <n> Minuten oder <n> Runden oder <n> Siege oder <n> Siege Vorsprung.";
new MSG_CUSTOMMAP[]="WARNING: %1s is a non-standard map. You may need to download it.|WARNING: %1s n'est pas une map standard. Vous devrez peut-etre la telecharger.|WARNUNG: %1s ist keine Standard-Map. Du wirst sie vielleicht downloaden muessen";
new MSG_CUSTOMMAPURL[]="WARNING: %1s is a non-standard map. You can download it from: %2s|WARNING: %1s n'est pas une map standard. Pouvez la telecharger ici : %2s|WARNUNG: %1s ist keine Standard-Map. Du kannst sie hier downloaden: %2s";
/* Can't do a multi-lingual vote menu yet
new MSG_WHATMAPNEXT[]="What map would you like to play next?|A quel map voulez-vous maintenant jouer?|Welche Map willst Du als naechstes spielen?";
new MSG_STAYONMAP[]="Stay on this map|Rester sur cette map|Auf dieser Map bleiben";
*/
new MSG_ROCKFAILED[]="Attempt to rock the vote failed.|l'essai de vote force a echoue.|Der Versuch ein Voting zu starten ist gescheitert";
new MSG_ERRORSTARTINGVOTE[]="Error starting vote. Will try again in 1 minute.|Erreur au demarrage du vote. Nouvel essai dans 1 minute.|Fehler beim Starten des Votings. Neuer Versuch in einer Minute";
new MSG_ADMINVOTEMAP_NO[]="Remaining on this map won with %1i of %2i votes.|On reste sur cette map avec %1i votes sur %2i.|Die aktuelle Map weiterspielen hat mit %1i von %2i Stimmen gewonnen.";
new MSG_ADMINVOTEMAP_YES[]="Map change failed, %1i votes required - only got %2i.|Map le changement echoue - %1i votes requises, seulement %2i obtenus |Map-Voting fehlgeschlagen - %1i Stimmen wurden benoetigt, aber nur %2i wurden erreicht.";
 
new MSG_REMAININGWON[]="Remaining on this map won with %1i of %2i votes. Nominations will be kept for next vote.|On reste sur cette map avec %1i votes sur %2i. Les nominations sont gardees pour le prochain vote.|Die aktuelle Map weiterspielen hat mit %1i von %2i Stimmen gewonnen. Nominierungen bleiben bestehen.";
new MSG_MAPNOTAVAILABLE[]="Unfortunately the chosen map is no longer available.|Malheureusement la carte choisie n'est plus disponible.|Die gewaehlte Map ist leider nicht mehr verfuegbar.";
new MSG_REPORTWINNER[]="Voting complete. %1s won with %2i of %3i votes|Le vote est fini. La carte %1s l'a remporte avec %2i votes sur %3i.|Map-Voting wurde beendet. %1s hat mit %2i von %3i Stimmen gewonnen.";
new MSG_REPORTWINNER_LOGD[]="Voting complete. %1s won with %2i of %3i votes. Map will change at the end of this round.|Le vote est fini. La carte %1s l'a remporte avec %2i votes sur %3i. Le chagement de map est effectue a la fin de ce round.|Map-Voting wurde beendet. %1s hat mit %2i von %3i Stimmen gewonnen. Map wird AM ENDE dieser Runde gewechselt !";
new MSG_CHANGENOW[]="Map is changing to %1s.|Changement de map pour %1s.|Map wird zu %1s gewechselt...";
new MSG_CHANGETOENDOFROUND[]="Map will change to %1s at the end of this round.|Le chagement de map est effectue a la fin de ce round pour %1s.|Map wird nach dieser Runde zu %1s gewechselt.";
new MSG_CHANGETONOW[]="Map is about to change to %1s.|Changement de map imminent pour %1s.|Map wechselt gleich zu %1s.";
new MSG_SAYCURRENT[]="The current map is: %1s.|La map actuelle est : %1s.|Die derzeitige Map ist: %1s.";
 
new MSG_SAYDURATIONTIME1[]="Time remaining on map %1s: 1 minute.|Temps restant sur la map %1s: 1 minute.|Verbleibende Zeit auf %1s: 1 Minute.";
new MSG_SAYDURATIONTIMEN[]="Time remaining on map %1s: %2i minutes.|Temps restant sur la map %1s: %2i minutes.|Verbleibende Zeit auf %1s: %2i Minuten.";
 
new MSG_SAYNEXT[]="The next map will be: %1s.|La prochaine map sera : %1s.|Die naechste Map wird %1s sein.";
new MSG_SAYVOTEWINNER[]="Players have voted for %1s as the next map.|Les joueurs ont vote pour %1s comme prochaine map.|Es wurde %1s als naechste Map gewaehlt.";
new MSG_SAYENDOFROUND[]="The map will change at the end of this round|Le chagement de map est effectue a la fin de ce round.|Die Map wird am Ende dieser Runde gewechselt.";
new MSG_SAYABOUTTOCHANGE[]="The map is about to change.|Changement de map imminent.|Die Map wechselt gleich.";
new MSG_SAYVOTINGNOW[]="Voting is in progress for the next map. Result will be announced soon.|Le vote est en progression pour la prochaine map. Les resultats seront annonces bientot.|Das Voting fuer die naechste Map laeuft. Ergebnis wird gleich bekanntgegeben.";
new MSG_SAYWILLVOTE[]="The next map will be determined by a vote.|La prochaine map sera determinee par un vote.|Die naechste Map wird per Voting bestimmt.";
new MSG_SAYCYCLENEXT[]="The next map will be %1s. Voting is disabled.|La proochaine map sera %1s. Le vote est suspendu.|Die naechste Map ist %1s. Voting ist deaktiviert.";
new MSG_NONOMINATIONS[]="No maps have been nominated for the next vote.|Aucune map n'a ere nominee pour le prochain vote.|Fuer das naechste Voting sind noch keine Maps nominiert. Dein Vorschlag mit *vote MAPNAME*";
new MSG_PROMPTNOMINATIONS[]="Maps nominated for the next vote: %1s. To nominate a map, say ^"nominate MAPNAME^"|Maps nominees pour le prochain vote: %1s. Pour nominer une map, say ^"nominate MAPNAME^"|%1s wurden schon nominiert. Um auch eine Map zu nominieren, sage ^"vote MAPNAME^"";
new MSG_AREAVAILABLE[]="Maps %1s are available. Say listmaps for a full list|Maps %1s sont disponibles. Say listmaps pour la liste complete.|Die Maps %1s koennen gevotet werden. Sage *listmaps* fuer komplette Liste.";
new MSG_AVAILABLEMAPS[]="The following maps are available on this server:|Les maps suivantes sont disponibles sur ce serveur:|Folgende Maps sind momentan auf diesem Server verfuegbar:";
new MSG_TONOMINATE[]="To nominate a map, say ^"nominate MAPNAME^" in chat, or ^"votemap MAPNAME^" in the console.|Pour nominer une map, say ^"nominate MAPNAME^" dans le chat, ou ^"votemap MAPNAME^" dans la console.|Um eine Map zu nominieren, sage ^"vote MAPNAME^" oder gib ^"votemap MAPNAME^" in der Konsole ein.";
 
 
new MSG_REASONT_ROUNDS[]="%1i round limit reached - terrorist team has won the game|Limite de %1i rounds atteinte - l'equipe de terroristes remporte le jeu|%1i Runden Limit erreicht - Das Terroristen Team hat das Spiel gewonnen";
new MSG_REASONCT_ROUNDS[]="%1i round limit reached - counter terrorist team has won the game|Limite de %1i rounds atteinte - l'equipe de contre-terroristes remporte le jeu|%1i Runden Limit erreicht - Das Counter-Terroristen Team hat das Spiel gewonnen";
new MSG_REASONTIE_ROUNDS[]="%1i round limit reached - game is tied|Limite de %1i rounds atteinte - egalite des scores|%1i Runden Limit erreicht - Das Spiel geht mit einem Unentschieden zu Ende";
 
new MSG_REASONT_WINS[]="Terrorist team has won the game by winning %1i rounds|L'equipe de terroristes remporte le jeu avec %1i points|Das Terroristen Team siegt mit %1i gewonnenen Runden";
new MSG_REASONCT_WINS[]="Counter Terrorist team has won the game by winning %1i rounds|L'equipe de contre-terroristes remporte le jeu avec %1i points|Das Counter-Terroristen Team siegt mit %1i gewonnenen Runden";
new MSG_REASONT_WINSLEAD[]="Terrorist team has won the game by winning %1i rounds more than the Counter Terrorists|L'equipe de terroristes remporte le jeu avec %1i points de plus que les contre-terroristes|Das Terroristen Team gewinnt mit %1i Siegen Vorsprung vor den Counter-Terroristen";
new MSG_REASONCT_WINSLEAD[]="Counter Terrorist team has won the game by winning %1i rounds more than the Terrorists|L'equipe de contre-terroristes remporte le jeu avec %1i points de plus que les terroristes|Das Counter-Terroristen Team gewinnt mit %1i Siegen Vorsprung vor den Terroristen";
new MSG_REASONPLAYER_FRAGS[]="Player %1s has won the game by scoring %2i|Le joueur %1s remporte le jeu avec %2i frags|Le joueur %1s remporte le jeu avec %2i frags de plus que le second meilleur fragger|Spieler %1s gewann das Spiel mit %2i Frags";
new MSG_REASONPLAYER_FRAGSLEAD[]="Player %1s has won the game by scoring %2i more than the closest opponent|Ce serveur ne vous autorise a changer votre nom que %1i fois en cours de jeu.|Spieler %1s gewann das Spiel mit %2i Frags Vorsprung vor dem naechstbesten Spieler";
 
new MSG_VOTECANCELED[]="Map vote cancelled by an admin.|Vote du map a ete decommandee par un admin.|Das Map Voting wurde durch einen Administrator beendet";
new MSG_VOTEWILLBECANCELED[]="Map vote will be cancelled.|Vote du map sera decommandee|Das Map Voting wird beendet...";
new MSG_CANTCANCELVOTE[]="This vote cannot be cancelled.|Cette vote ne peut pas etre decommandee.|Dieses Voting kann nicht beendet werden";
new MSG_BANNEDMAPS[]="The following maps are unavailable, because they have been played recently:|Les maps suivantes sont indisponibles, car elles ont ete jouees recemment :|Die folgenden Maps sind nicht waehlbar, da sie erst vor kurzem gespielt wurden:";
 
 
 
/*
 *********************************************************
 *                     GLOBAL VARIABLES                  *
 *********************************************************
*/
 
/* Map list variables */
new g_AllMaps[MAX_MAPS][MAX_MAP_LENGTH];                    /* Array contains map names */
new g_AllMapsShort[MAX_MAPS][MAX_MAP_LENGTH];               /* Names with the underscore bit gone */
new g_AllMapsSize=0;                                        /* Number of slots filled in g_AllMaps */
new g_Mapcycle[MAX_MAPS][MAX_MAP_LENGTH];                   /* Array contains map names in mapcycle.txt */
new g_AllMapsDisk[MAX_MAPS][MAX_MAP_LENGTH];            /* Untouched copy of g_AllMaps to allow maps.ini editing */
new g_MapcycleSize=0;                                       /* Number of slots filled in g_Mapcycle */
new g_NominatedCaptions[MAX_NOMINATIONS][MAX_MAP_LENGTH+20];/* Array containing nominations as displayed in vote menu */
new g_NominatedMaps[MAX_NOMINATIONS][MAX_MAP_LENGTH];       /* Array containing nominations */
new g_Nominators[MAX_NOMINATIONS+2];                        /* Array contain user IDs of nominators */
 
/* Configuration variables */
new g_Report=DEFAULT_REPORT;                                /* Automatically show report after command? */
new g_NextMenuSize=DEFAULT_NOMINATIONS;                     /* Number of maps in the next nominations menu */
new g_InfoCount=DEFAULT_MESSAGEFREQUENCY;                   /* Number of minutes until next announcement */
new g_Extends=DEFAULT_MAX_EXTENDS;                          /* Number of times the current map *can* be extended */
new g_ExtendTime=DEFAULT_EXTEND_TIME;                       /* Number of minutes a map extension adds to the clock */
new g_BanLast=DEFAULT_BAN_LAST;                             /* Number of previous maps that can't be nominated */
new g_Verbosity=DEFAULT_VERBOSITY;                          /* Series of flags saying what to announce */
new g_MsgLocation=DEFAULT_MESSAGELOCATION;                  /* Flags saying where to display message */
new g_MsgFrequency=DEFAULT_MESSAGEFREQUENCY;                /* Time between announcements */
new g_RockTheVote=DEFAULT_ROCKTHEVOTE;                      /* Time before rock the vote is allowed */
new g_DurationMins=DEFAULT_DURATIONMINS;                    /* Length of game in minutes */
new g_DurationRounds=DEFAULT_DURATIONROUNDS;                /* Length of game in rounds played */
new g_DurationWins=DEFAULT_DURATIONWINS;                    /* Length of game in rounds won */
new g_DurationWinsLead=DEFAULT_DURATIONWINSLEAD;            /* Length of game in rounds won lead */
new g_DurationFrags=DEFAULT_DURATIONFRAGS;                  /* Length of game in player frags */
new g_DurationFragsLead=DEFAULT_DURATIONFRAGSLEAD;          /* Length of game in player frags */
new g_Echo=DEFAULT_ECHO;                                    /* True if commands are to be echoed to all users */
new g_Nomination=DEFAULT_NOMINATION;                        /* True if nominations are enabled */
new g_Sync=DEFAULT_SYNC;                                    /* True if map changes are synchronised to round changes */
new g_MapsURL[MAX_DATA_LENGTH]="";                          /* URL to download maps */
new g_RocksNeeded=DEFAULT_ROCKSNEEDED;                      /* Number of 'rockthevotes' from non-admins needed to start a vote */
new g_RocksPercent=DEFAULT_ROCKSPERCENT;                    /* Percent of 'rockthevotes' from non-admins needed to start a vote */
new g_MaxMapListLength=MAX_MAPLISTLENGTH;                   /* Maximum number of maps listed by listmaps */
 
/* Status variables */
new g_ThisMenuSize = DEFAULT_NOMINATIONS;                   /* Number of maps in the current nominations menu */
new g_VoteActive=0;                                         /* 1 = Voting process has begun / 0 = Nomiations allowed */
new g_CountDownActive=0;                                    /* 1 = Voting countdown in progress */
new g_NextMap[MAX_MAP_LENGTH];                              /* Name of map that won the vote, if extend didn't win */
new g_InfoToggle=0;                                         /* 1 = Display nominations next / 0 =  Display available maps next */
new g_InfoPosition=0;                                       /* Index in g_AllMaps to read next available maps list from */
new g_NumExtends=0;                                         /* Number of times the current map has been extended */
new g_RoundTime=1200;                                       /* Time between votes. Overridden by mp_timelimit in server.cfg */
new g_Mode=1;                                               /* 1 = Voting enabled / 0 = No voting allowed - use normal map rotation */
new g_Rocked=0;                                             /* True if this rock was rocked */
new g_BuyTimer=0;                                           /* Timer to prevent vote menu appearing while buying */
new g_LogdDetected=0;                                       /* True if logd is installed */
new g_LastVoteAllowedExtend = 0;                            /* Was extend option displayed on the last vote menu */
new g_TeamScore[MAX_TEAMS];                                 /* Score of each team */
new g_DebugLevel = 0;                                       /* Reads admin_debug cvar for logging of debug info */
new g_VoteAborted = 0;                                      /* True if a vote has been cancelled */
new g_VoteAbortable = 0;                                    /* True if actual vote() function is running */
new g_BuySupressed = 0;                                     /* True if buying weapons is supressed */
new g_DelaySupressed = 0;                                   /* True if vote can't be delayed - data only valid during countdown period */
new g_MainTimer =0;                                         /* Timer index for the main timer */
new g_HasRockedTheVote[MAX_PLAYERS];                        /* 1 if the player has rocked the vote */
new g_MaxSpeed=0;                                           /* Server's max speed */
 
/* Function declarations */
forward RegisterPlugin();
forward CheckTest(HLCommand,HLData,HLUserName,UserIndex);
forward plugin_disconnect(HLUserName, UserIndex);
forward AdminVoteMap(HLCommand,HLData,HLUserName,UserIndex);
forward HandleAdminVoteMap(WinningOption,HLParam,VoteCount,UserCount);
forward OriginalVoteMap(HLCommand,HLData,HLUserName,UserIndex);
forward AdminAbortVote(HLCommand,HLData,HLUserName,UserIndex);
forward AdminMap(HLCommand,HLData,HLUserName,UserIndex);
forward plugin_command(HLCommand,HLData,HLUserName,UserIndex);
forward BBMapMode(HLCommand,HLData,HLUserName,UserIndex);
forward BBMapMenuSize(HLCommand,HLData,HLUserName,UserIndex);
forward BBMapExtends(HLCommand,HLData,HLUserName,UserIndex);
forward BBMapDuration(HLCommand,HLData,HLUserName,UserIndex);
forward BBMapMapsURL(HLCommand,HLData,HLUserName,UserIndex);
forward BBMapExtendTime(HLCommand,HLData,HLUserName,UserIndex);
forward BBMapBanLast(HLCommand,HLData,HLUserName,UserIndex);
forward BBMapVerbosity(HLCommand,HLData,HLUserName,UserIndex);
forward BBMapNomination(HLCommand,HLData,HLUserName,UserIndex);
forward BBMapMsgLocation(HLCommand,HLData,HLUserName,UserIndex);
forward BBMapMsgFrequency(HLCommand,HLData,HLUserName,UserIndex);
forward BBMapRockTheVote(HLCommand,HLData,HLUserName,UserIndex);
forward BBMapList(HLCommand,HLData,HLUserName,UserIndex);
forward BBMapReset(HLCommand,HLData,HLUserName,UserIndex);
forward BBMapReport(HLCommand,HLData,HLUserName,UserIndex);
forward BBMapEcho(HLCommand,HLData,HLUserName,UserIndex);
forward BBMapSync(HLCommand,HLData,HLUserName,UserIndex);
forward ShowConfig(UserIndex,Force);
forward CheckIfMapShouldChange();
forward BBLogdWorldAction(HLCommand,HLData,HLUserName,UserIndex);
forward Event_RoundStart(event);
forward Event_RoundEnd(event);
forward BuyTimeEnd();
forward BBLogdTeamAction(HLCommand,HLData,HLUserName,UserIndex);
forward BBLogdPlayerAction(HLCommand,HLData,HLUserName,UserIndex);
forward Death(event);
forward SayCommand(HLCommand,HLData,HLUserName,UserIndex);
forward Nominate(MapName[],UserName[],UserIndex,ShowErrors,LogToConsole,&EchoCommand);
forward SayCurrentMap();
forward SayTimeleft();
forward SayNextMap();
forward SayMapInfo();
forward ShowTimeLeft(Seconds);
forward StartMainTimer();
forward StopMainTimer();
forward Timer(Dummy);
forward GetRocksNeeded();
forward RockTheVote(UserIndex,UserName[],LogToConsole,AdminUse);
forward ClearRockedTheVote();
forward DoInformVote30();
forward DoInformVote10();
forward DoInformVote5();
forward DoInformVote0();
forward DoStartVote();
forward VoteComplete(Winner,HLData,VoteCount,UserCount);
forward DoVoteComplete(Winner,VoteCount);
forward AbortVote();
forward CycleMaps();
forward InitiateMapChange(NextMap[],ChangeNow);
forward AntiAdminKick();
forward AnnounceCustomMap(Timer,Repeat,HLUserName,HLParam);
forward DoAnnounceCustomMap(MapName[]);
forward DoChangeMap(Dummy);
forward DoChangeMap2();
forward DoChangeMap3();
forward ReadMapList();
forward CompareMapList();
forward ReadMapcycle();
forward ReadMapLine(MapFile[],i,Line[]);
forward GetShortName(fullname[],shortname[]);
forward CheckMenuSize();
forward SaveMapList();
forward MarkCustomMaps();
forward ReportNominations();
forward ReportMapsAvailable(UserName[]);
forward ShowMapListChat(UserIndex,Search[],Skip);
forward ShowMapList(UserIndex,Search[],Chat);
forward LookupFreeSlot(UserIndex);
forward LookupMap(MapName[]);
forward IsNominated(MapName[]);
forward IsBanned(MapName[]);
forward IsInMapcycle(MapName[]);
forward MoveMapToEnd(MapName[],Offset);
forward SetTimeLimit();
forward SetMapTime(mins);
forward print_type:getlocation(flag);
forward GetNextMapInCycle();
forward CanStayOnMap(fAnnounce);
forward DebugHeapInit();
forward DebugHeapTimer();
forward DebugHeap(context[]);
 
/*
 *********************************************************
 *                  PLUGIN INITIALISATION                *
 *********************************************************
*/
 
 
public plugin_init() {
 
  plugin_registerinfo("Bugblatter's Map Management Plugin","Controls map voting and cycling on your server",g_Version);
 
  new msg[MAX_TEXT_LENGTH];
  snprintf(msg,MAX_TEXT_LENGTH,"Initialising Plugin - Version %s",g_Version);  
  plugin_message(msg);
 
  /*DebugHeapInit();*/
 
  /* Check server is configured properly */
  new fOK = checkFileAccessRead();
  fOK = checkFileAccessWrite() && fOK;
  fOK = checkAllowClientExec() && fOK;
  fOK = checkAdminVoteAutostart() && fOK;
  fOK = checkVaultOK() && fOK;
  fOK = checkMapsOK() && fOK;
  if (fOK == 0) {
    return PLUGIN_CONTINUE;
  }
  /* No need to abort if this one fails */
  checkLanguage();
 
  /* g_Nominators array has 2 unused extra slots to
   * simplify the map listing function. This sets them
   * to 0, where they will stay */
  g_Nominators[MAX_NOMINATIONS]=0;
  g_Nominators[MAX_NOMINATIONS+1]=0;
 
  plugin_message("Loading configuration settings...");
 
  /* Read saved configuration from vault */
  readvaultnum("bbmap_mode",g_Mode,1,0,2);
  readvaultnum("bbmap_report",g_Report,DEFAULT_REPORT,MIN_REPORT,MAX_REPORT);
  readvaultnum("bbmap_menusize", g_NextMenuSize,DEFAULT_NOMINATIONS,MIN_NOMINATIONS,MAX_NOMINATIONS);
  readvaultnum("bbmap_nomination", g_Nomination,DEFAULT_NOMINATION,MIN_NOMINATION,MAX_NOMINATION);
  readvaultnum("bbmap_durationmins", g_DurationMins,DEFAULT_DURATIONMINS,MIN_DURATION,MAX_DURATION);
  readvaultnum("bbmap_durationrounds", g_DurationRounds,DEFAULT_DURATIONROUNDS,MIN_DURATION,MAX_DURATION);
  readvaultnum("bbmap_durationwins", g_DurationWins,DEFAULT_DURATIONWINS,MIN_DURATION,MAX_DURATION);
  readvaultnum("bbmap_durationwinslead", g_DurationWinsLead,DEFAULT_DURATIONWINSLEAD,MIN_DURATION,MAX_DURATION);
  readvaultnum("bbmap_durationfrags", g_DurationFrags,DEFAULT_DURATIONFRAGS,MIN_DURATION,MAX_DURATION);
  readvaultnum("bbmap_durationfragslead", g_DurationFragsLead,DEFAULT_DURATIONFRAGSLEAD,MIN_DURATION,MAX_DURATION);
 
  readvaultnum("bbmap_extends",g_Extends,DEFAULT_MAX_EXTENDS,MIN_MAX_EXTENDS,MAX_MAX_EXTENDS);
  readvaultnum("bbmap_extendtime",g_ExtendTime,DEFAULT_EXTEND_TIME,MIN_EXTEND_TIME,MAX_EXTEND_TIME);
  readvaultnum("bbmap_banlast",g_BanLast, DEFAULT_BAN_LAST,MIN_BAN_LAST,MAX_BAN_LAST);
  readvaultnum("bbmap_verbosity",g_Verbosity, DEFAULT_VERBOSITY,MIN_VERBOSITY,MAX_VERBOSITY);
  readvaultnum("bbmap_msglocation",g_MsgLocation, DEFAULT_MESSAGELOCATION,MIN_MESSAGELOCATION,MAX_MESSAGELOCATION);
  readvaultnum("bbmap_msgfrequency",g_MsgFrequency, DEFAULT_MESSAGEFREQUENCY,MIN_MESSAGEFREQUENCY,MAX_MESSAGEFREQUENCY);
  readvaultnum("bbmap_rockthevote",g_RockTheVote, DEFAULT_ROCKTHEVOTE,MIN_ROCKTHEVOTE,MAX_ROCKTHEVOTE);
  readvaultnum("bbmap_rocksneeded",g_RocksNeeded, DEFAULT_ROCKSNEEDED,MIN_ROCKSNEEDED,MAX_ROCKSNEEDED);
  readvaultnum("bbmap_rockspercent",g_RocksPercent, DEFAULT_ROCKSPERCENT,MIN_ROCKSPERCENT,MAX_ROCKSPERCENT);
  readvaultnum("bbmap_echo",g_Echo,DEFAULT_ECHO,MIN_ECHO,MAX_ECHO);
  readvaultnum("bbmap_sync",g_Sync,DEFAULT_SYNC,MIN_SYNC,MAX_SYNC);
  get_vaultdata("bbmap_mapsurl",g_MapsURL,MAX_DATA_LENGTH);
 
  plugin_message("Loading maps file...");
  if (ReadMapList()==0) {
    configError("Aborting due to error loading maps.ini file");
    return PLUGIN_CONTINUE;
  }
 
  plugin_message("Loading mapcycle file...");
  if (ReadMapcycle() ==0) {
    configError("Aborting due to error loading mapcycle.txt file");
    return PLUGIN_CONTINUE;
  }
 
  /* Move the first 2 maps in the list to bb_mapbanlast places from the end
   * This prevents the same maps being used to pad the menu over and over if
   * there aren't enough nominations */
 
 
  plugin_message("Updating maps file...");
 
  new mapname[MAX_MAP_LENGTH];
  strcpy(mapname,g_AllMaps[0],MAX_MAP_LENGTH);
  MoveMapToEnd(mapname,g_BanLast+3);
  strcpy(mapname,g_AllMaps[0],MAX_MAP_LENGTH);
  MoveMapToEnd(mapname,g_BanLast);
  SaveMapList();
 
 
  plugin_message("Initialising voting system...");
 
  /* Prime the voting menu */
  new i;
  for(i=0;i<MAX_NOMINATIONS;i++) {
    g_Nominators[i]=0;
    g_NominatedMaps[i]="<None>";
  }
  g_ThisMenuSize = g_NextMenuSize;
 
  for(i=0;i<MAX_TEAMS;i++) {
    g_TeamScore[i]=0;
  }
 
  SetTimeLimit();
  ClearRockedTheVote();
 
 
  plugin_message("Registering commands...");
 
  /* Not allowed to return errors from this point on... */
  language_init();
  /* Register with logd if its installed. If its not this will
     cause an error in your server logs, but will have no other
     detremental effects
 
   * NOTE: No access level numerically coded because its missing
           from include/admin.inc and I don't want to cause a
           name clash with future releases of include files
  */
 
/*
#if !defined(ADMINMOD_VERSION)
  #define ADMINMOD_VERSION 25050
#endif
 
#if ADMINMOD_VERSION >= 25100
  plugin_reg_event( "cstrike_roundstart", "Event_RoundStart" );
  plugin_reg_event( "cstrike_roundend", "Event_RoundEnd" );
  plugin_reg_event( "death", "Death" );
#else
*/
  plugin_registercmd("bbmap_logdwa","BBLogdWorldAction",131072,"");
  exec("logd_reg 62 admin_command bbmap_logdwa",0);
  plugin_registercmd("bbmap_logdta","BBLogdTeamAction",131072,"");
  exec("logd_reg 61 admin_command bbmap_logdta",0);
  plugin_registercmd("bbmap_logdpa","BBLogdPlayerAction",131072,"");
  exec("logd_reg 60 admin_command bbmap_logdpa",0);
/*#endif*/
 
  /* Start the announcement / vote start timer */
  if (g_Mode>0) {
    StartMainTimer();
  }
 
 
  RegisterPlugin();
 
  plugin_message("Plugin initialisation complete.");
 
 
  return PLUGIN_CONTINUE;
}
 
RegisterPlugin() {
  /* Register callbacks with admin mod */
  plugin_registercmd("say","SayCommand",ACCESS_ALL);
  plugin_registercmd("say_team","SayCommand",ACCESS_ALL);
 
  plugin_registercmd("admin_vote_map","AdminVoteMap",ACCESS_VOTE_MAP,
         "admin_vote_map <map>: Starts a vote to change the map.");
  plugin_registercmd("admin_map","AdminMap",ACCESS_MAP,
         "admin_map <map> [^"now^"]: Changes map. Happens immediately if ^"now^" is specified, otherwise at end of round/after a delay");
  plugin_registercmd("votemap","OriginalVoteMap",ACCESS_ALL,
                     "votemap <map>: Nominates a map for the next vote");
  plugin_registercmd("listmaps","BBMapList",ACCESS_ALL,
         "listmaps  <search>: Lists maps available for voting");
  plugin_registercmd("admin_abort_vote","AdminAbortVote",ACCESS_ABORT_VOTE,
           "admin_abort_vote: Aborts a vote in progress.");
  plugin_registercmd("admin_listmaps","BBMapList",ACCESS_ALL,
         "listmaps  <search>: Lists maps available for voting");
 
  plugin_registercmd("bbmap_mode","BBMapMode",ACCESS_MAP,
                     "bbmap_mode <^"vote^" | ^"cycle^" | ^"off^">: Switch between map voting and cycling");
  plugin_registercmd("bbmap_duration","BBMapDuration",ACCESS_MAP,
                     "bbmap_duration <n> < minutes | rounds | wins | frags > [lead]: Set duration");
  plugin_registercmd("bbmap_nomination","BBMapNomination",ACCESS_MAP,
                     "bbmap_nomination on | off: Enable or disable nominating maps");
  plugin_registercmd("bbmap_menusize","BBMapMenuSize",ACCESS_MAP,
                     "bbmap_menusize <n>: Set number of maps in menu to n");
  plugin_registercmd("bbmap_echo","BBMapEcho",ACCESS_MAP,
                     "bbmap_echo ^"on^" | ^"off^": Sets whether commands in say are displayed to other users");
  plugin_registercmd("bbmap_extends","BBMapExtends",ACCESS_MAP,
                     "bbmap_extends <n>: Set maximum number of times you can for to stay on same map to n");
  plugin_registercmd("bbmap_extendtime","BBMapExtendTime",ACCESS_MAP,
                     "bbmap_extendtime <n>: Set number of minutes an extension lasts");
  plugin_registercmd("bbmap_banlast","BBMapBanLast",ACCESS_MAP,
                     "bbmap_banlast <n>: Set number of recently played maps to ban");
  plugin_registercmd("bbmap_verbosity","BBMapVerbosity",ACCESS_MAP,
                     "bbmap_verbosity <n>: See bugblatter plugins manual for value of n");
  plugin_registercmd("bbmap_msglocation","BBMapMsgLocation",ACCESS_MAP,
                     "bbmap_msglocation <n>: see bugblatter plugins manual for value of n");
  plugin_registercmd("bbmap_msgfrequency","BBMapMsgFrequency",ACCESS_MAP,
                     "bbmap_msgfrequency <n>: number of minutes between announcements");
  plugin_registercmd("bbmap_report","BBMapReport",ACCESS_MAP,
                     "bbmap_report [ ^"on^" | ^"off^" ]: shows the map management config, and enables/disable report after every command");
  plugin_registercmd("bbmap_reset","BBMapReset",ACCESS_MAP,
                     "bbmap_reset: sets all map voting settings to defaults");
  plugin_registercmd("bbmap_rockthevote","BBMapRockTheVote",ACCESS_MAP,
                     "bbmap_rockthevote [ <n> ] [ ^"minutes^" | ^"votes^" | ^"percent^" ] : Configures rockthevote");
  plugin_registercmd("bbmap_sync","BBMapSync",ACCESS_MAP,
                     "bbmap_sync [ ^"on^" | ^"off^" ]: synchronises map changes to round changes");
  plugin_registercmd("bbmap_mapsurl","BBMapMapsURL",ACCESS_MAP,
                     "bbmap_mapsurl <url>: sets location to download maps");
}
 
public plugin_disconnect(HLUserName, UserIndex) {
  g_HasRockedTheVote[UserIndex]=0;
  new rocktime = (g_RockTheVote*60) - maptime(0,0);
  if (rocktime < 0) {
    if (GetRocksNeeded()==0) {
      g_Rocked = 1;
      if (g_Mode==1) {
        log("Starting voting process due to players leaving causing rockthevote threshold to be reached.");
        DoInformVote30();
      }
    }
  }
}
 
 
 
 
 
/*
 *********************************************************
 *        ADMINISTRATOR COMMAND HANDLER FUNCTIONS        *
 *********************************************************
*/
 
public AdminVoteMap(HLCommand,HLData,HLUserName,UserIndex) {
  new Data[MAX_DATA_LENGTH];
  new Text[MAX_TEXT_LENGTH];
  new UserName[MAX_NAME_LENGTH];
 
  safe_convert(HLData,Data,MAX_DATA_LENGTH);
  safe_convert(HLUserName,UserName,MAX_NAME_LENGTH);
 
  /* If issued by a player with ACCESS_MAP, use normal behaviour */
  if (access(ACCESS_MAP,"")) {
    if (vote_allowed()!=1) {
      language_say(UserIndex,MSG_ROCKNOTALLOWED,print_type:print_console);
      return PLUGIN_HANDLED;
    }
    new MapIndex = LookupMap(Data);
    if (MapIndex >= 0) {
      snprintf(Text, MAX_TEXT_LENGTH, "Change map to %s?", g_AllMaps[MapIndex]);
      log(Text);
      vote(Text,"Yes","No","HandleAdminVoteMap",g_AllMaps[MapIndex]);
      StopMainTimer();
      g_VoteActive = 1;
      g_LastVoteAllowedExtend = 1;
      g_VoteAbortable = 1;
      g_VoteAborted = 0;
    } else {
      language_sayf(UserIndex,MSG_MAPNOTFOUND,print_type:print_console,0,0,0,0,Data);
    }
    return PLUGIN_HANDLED;
  }
 
  /* If issued by a player without ACCESS_MAP, treat as nominate followed by rock */
  if ((g_Mode == 0) || (g_Mode == 2)) {
    language_say(UserIndex,MSG_VOTINGDISABLED,print_type:print_console);
  }
  else {
    new Dummy;
    if (Nominate(Data,UserName,UserIndex,1,1,Dummy)) {
      RockTheVote(UserIndex,UserName,1,0);
    }
  }
 
  return PLUGIN_HANDLED;
}
 
/* Handle a map vote's results. */
public HandleAdminVoteMap(WinningOption,HLParam,VoteCount,UserCount) {
  new MapName[MAX_DATA_LENGTH];
  safe_convert(HLParam,MapName,MAX_DATA_LENGTH);
 
  if (strlen(g_NextMap)>0) {
    /* An admin must have used admin_map while the vote was running */
    return PLUGIN_HANDLED;
  }
 
  g_VoteActive = 0;
  g_VoteAbortable = 0;
  if (g_VoteAborted) {
    language_sayall(MSG_VOTECANCELED,getlocation(FLAG_WINNEROFVOTE),15,ANNOUNCE_RED,ANNOUNCE_GREEN,ANNOUNCE_BLUE);
    StartMainTimer();
  }
  else {
    if (WinningOption == 1) {
      new Ratio = getvar("map_ratio");
      if (VoteCount >= Ratio*UserCount/100) {
        InitiateMapChange(MapName,0);
      } else {
        language_sayallf(MSG_ADMINVOTEMAP_YES,getlocation(FLAG_WINNEROFVOTE),15,ANNOUNCE_RED,ANNOUNCE_GREEN,ANNOUNCE_BLUE,Ratio*UserCount/100,VoteCount);
        StartMainTimer();
      }
    } else {
      language_sayallf(MSG_ADMINVOTEMAP_NO,getlocation(FLAG_WINNEROFVOTE),15,ANNOUNCE_RED,ANNOUNCE_GREEN,ANNOUNCE_BLUE,VoteCount,UserCount);
      StartMainTimer();
    }
  }
 
  return PLUGIN_HANDLED;
}
 
 
public OriginalVoteMap(HLCommand,HLData,HLUserName,UserIndex) {
  if ((g_Mode == 0) || (g_Mode == 2)) {
    language_say(UserIndex,MSG_VOTINGDISABLED,print_type:print_console);
  }
  else {
    new Data[MAX_DATA_LENGTH];
    new UserName[MAX_NAME_LENGTH];
    safe_convert(HLData,Data,MAX_DATA_LENGTH);
    safe_convert(HLUserName,UserName,MAX_NAME_LENGTH);
    new Dummy;
    if (Nominate(Data,UserName,UserIndex,1,1,Dummy)==0) {
      ShowMapList(UserIndex,"",0);
    }
  }
  return PLUGIN_HANDLED;
}
 
public AdminAbortVote(HLCommand,HLData,HLUserName,UserIndex) {
  if (g_VoteAbortable) {
    if (g_LastVoteAllowedExtend) {
      g_VoteAborted = 1;
      language_say(UserIndex,MSG_VOTEWILLBECANCELED,print_type:print_console);
    }
    else {
      language_say(UserIndex,MSG_CANTCANCELVOTE,print_type:print_console);
    }
    return PLUGIN_HANDLED;
  }
  return PLUGIN_CONTINUE;
}
 
 
public AdminMap(HLCommand,HLData,HLUserName,UserIndex) {
  new Command[MAX_COMMAND_LENGTH];
  new Data[MAX_DATA_LENGTH];
  new User[MAX_NAME_LENGTH];
  new MapName[MAX_DATA_LENGTH];
  new MapNow[MAX_DATA_LENGTH];
 
  if (g_Mode == 0) {
    return PLUGIN_CONTINUE;
  }
 
  safe_convert(HLCommand,Command,MAX_COMMAND_LENGTH);
  safe_convert(HLData,Data,MAX_DATA_LENGTH);
  safe_convert(HLUserName,User,MAX_NAME_LENGTH);
 
  new args=strsplit(Data," ",MapName,MAX_DATA_LENGTH,MapNow,MAX_DATA_LENGTH);
 
  if (((args==2) && (streq(MapNow,"now")==0)) || (args==0)) {
    language_sayf(UserIndex,MSG_USAGE, print_type:print_console,0,0,0,0,"admin_map <mapname> [^"now^"]");
    return PLUGIN_HANDLED;
  }
 
  new MapIndex = LookupMap(MapName);
  if (MapIndex >= 0) {
    say_command(User,Command,MapName);
    if ((g_Verbosity & FLAG_COUNTDOWNTALK) > 0) {
      SpeakAll("administration(e48) elevator(s55) chamber(e40) engage(s65,e92) loading(s45) mass(e40) cap(s50)");
    }
 
    InitiateMapChange(g_AllMaps[MapIndex],streq(MapNow,"now"));
  } else {
    language_sayf(UserIndex,MSG_UNKNOWNMAPNAME,print_type:print_console,0,0,0,0,MapName);
  }
  return PLUGIN_HANDLED;
}
 
public plugin_command(HLCommand,HLData,HLUserName,UserIndex) {
  if (g_BuySupressed==0) {
    return PLUGIN_CONTINUE;
  }
 
  new Command[MAX_COMMAND_LENGTH];
  safe_convert(HLCommand,Command,MAX_COMMAND_LENGTH);
 
  if (strmatch(Command,"say",3)==1) {
    return PLUGIN_CONTINUE;
  }
  if (streq(Command,"drop")) {
    return PLUGIN_CONTINUE;
  }
 
  return PLUGIN_HANDLED;
}
 
 
/*
 *********************************************************
 *               MAP COMMAND HANDLER FUNCTIONS           *
 *********************************************************
*/
 
public BBMapMode(HLCommand,HLData,HLUserName,UserIndex) {
  new Data[MAX_DATA_LENGTH];
  safe_convert(HLData,Data,MAX_DATA_LENGTH);
 
  if (strcasecmp(Data,"vote")==0) {
    g_Mode = 1;
    StartMainTimer();
  }
  else if(strcasecmp(Data,"off")==0) {
    g_Mode = 0;
    SetMapTime(g_RoundTime);
  } else if(strcasecmp(Data,"cycle")==0) {
    g_Mode = 2;
    StartMainTimer();
  } else {
    language_sayf(UserIndex,MSG_USAGE,print_type:print_console,0,0,0,0,"bbmap_mode vote | cycle | off");
  }
 
  writevaultnum("bbmap_mode",g_Mode);
  return ShowConfig(UserIndex,0);
 
}
 
public BBMapMenuSize(HLCommand,HLData,HLUserName,UserIndex) {
  if (readHLnum(HLData,g_NextMenuSize,5,2,MAX_NOMINATIONS)) {
  writevaultnum("bbmap_menusize",g_NextMenuSize);
  }
  if (CheckMenuSize()) {
    return ShowConfig(UserIndex,0);
  }
  return PLUGIN_HANDLED;
}
 
public BBMapExtends(HLCommand,HLData,HLUserName,UserIndex) {
  if (readHLnum(HLData,g_Extends,DEFAULT_MAX_EXTENDS,MIN_MAX_EXTENDS,MAX_MAX_EXTENDS)) {
    writevaultnum("bbmap_extends",g_Extends);
  }
  return ShowConfig(UserIndex,0);
}
 
public BBMapDuration(HLCommand,HLData,HLUserName,UserIndex) {
  new strData[MAX_DATA_LENGTH];
  new strDuration[MAX_DATA_LENGTH];
  new strDurationType[MAX_DATA_LENGTH];
  new strDurationLead[MAX_DATA_LENGTH];
  new nDuration=0;
  new fLead=0;
 
  safe_convert(HLData,strData,MAX_DATA_LENGTH);
  new args=strsplit(strData," ",strDuration,MAX_DATA_LENGTH,strDurationType,MAX_DATA_LENGTH,strDurationLead,MAX_DATA_LENGTH);
 
  if ((args==3) && (strcasecmp(strDurationLead,"lead")==0)) {
    fLead=1;
  }
  else if (args!=2) {
    language_say(UserIndex,MSG_DURATIONUSAGE,print_type:print_console);
    return PLUGIN_HANDLED;
  }
 
  nDuration = strtonum(strDuration);
  if ( (nDuration < MIN_DURATION) || (nDuration > MAX_DURATION)) {
    language_say(UserIndex,MSG_DURATIONUSAGE,print_type:print_console);
    return PLUGIN_HANDLED;
  }
 
  if (strcasecmp(strDurationType,"minutes") == 0) {
    g_DurationMins = nDuration;
    writevaultnum("bbmap_durationmins",g_DurationMins);
    SetTimeLimit();
  }
  else if (strcasecmp(strDurationType,"rounds") == 0) {
    g_DurationRounds = nDuration;
    writevaultnum("bbmap_durationrounds",g_DurationRounds);
  }
  else if (strcasecmp(strDurationType,"wins") == 0) {
    if (fLead) {
      g_DurationWinsLead = nDuration;
      writevaultnum("bbmap_durationwinslead",g_DurationWinsLead);
    }
    else {
      g_DurationWins = nDuration;
      writevaultnum("bbmap_durationwins",g_DurationWins);
    }
  }
  else if (strcasecmp(strDurationType,"frags") == 0) {
    if (fLead) {
      g_DurationFragsLead = nDuration;
      writevaultnum("bbmap_durationfragslead",g_DurationFragsLead);
    }
    else {
      g_DurationFrags = nDuration;
      writevaultnum("bbmap_durationfrags",g_DurationFrags);
    }
  }
  else {
    language_say(UserIndex,MSG_DURATIONUSAGE,print_type:print_console);
    return PLUGIN_HANDLED;
  }
 
  return ShowConfig(UserIndex,0);
}
 
public BBMapMapsURL(HLCommand,HLData,HLUserName,UserIndex) {
  safe_convert(HLData,g_MapsURL,MAX_DATA_LENGTH);
  strtrim(g_MapsURL," ^t");
  set_vaultdata("bbmap_mapsurl",g_MapsURL);
  return ShowConfig(UserIndex,0);
}
 
 
public BBMapExtendTime(HLCommand,HLData,HLUserName,UserIndex) {
  if (readHLnum(HLData,g_ExtendTime,DEFAULT_EXTEND_TIME,MIN_EXTEND_TIME,MAX_EXTEND_TIME)) {
    writevaultnum("bbmap_extendtime",g_ExtendTime);
  }
  return ShowConfig(UserIndex,0);
}
 
public BBMapBanLast(HLCommand,HLData,HLUserName,UserIndex) {
  if (readHLnum(HLData,g_BanLast,DEFAULT_BAN_LAST,MIN_BAN_LAST,MAX_BAN_LAST)) {
    writevaultnum("bbmap_banlast",g_BanLast);
  }
  return ShowConfig(UserIndex,0);
}
 
public BBMapVerbosity(HLCommand,HLData,HLUserName,UserIndex) {
  if (readHLnum(HLData,g_Verbosity,DEFAULT_VERBOSITY,MIN_VERBOSITY,MAX_VERBOSITY)) {
    writevaultnum("bbmap_verbosity",g_Verbosity);
  }
  return ShowConfig(UserIndex,0);
}
 
public BBMapNomination(HLCommand,HLData,HLUserName,UserIndex) {
  if (readHLonoff(HLData,g_Nomination,DEFAULT_NOMINATION,MIN_NOMINATION,MAX_NOMINATION)) {
    writevaultnum("bbmap_nomination",g_Nomination);
  }
  return ShowConfig(UserIndex,0);
}
 
public BBMapMsgLocation(HLCommand,HLData,HLUserName,UserIndex) {
  if (readHLnum(HLData,g_MsgLocation,DEFAULT_MESSAGELOCATION,MIN_MESSAGELOCATION,MAX_MESSAGELOCATION)) {
    writevaultnum("bbmap_msglocation",g_MsgLocation);
  }
  return ShowConfig(UserIndex,0);
}
 
public BBMapMsgFrequency(HLCommand,HLData,HLUserName,UserIndex) {
  if (readHLnum(HLData,g_MsgFrequency,DEFAULT_MESSAGEFREQUENCY,MIN_MESSAGEFREQUENCY,MAX_MESSAGEFREQUENCY)) {
    writevaultnum("bbmap_msgfrequency",g_MsgFrequency);
    if (g_InfoCount+1>g_MsgFrequency) {
      g_InfoCount = g_MsgFrequency-1;
    }
  }
  return ShowConfig(UserIndex,0);
}
 
public BBMapRockTheVote(HLCommand,HLData,HLUserName,UserIndex) {
  new strData[MAX_DATA_LENGTH];
  new strValue[MAX_DATA_LENGTH];
  new strType[MAX_DATA_LENGTH];
  new nValue=0;
 
  safe_convert(HLData,strData,MAX_DATA_LENGTH);
  new args=strsplit(strData," ",strValue,MAX_DATA_LENGTH,strType,MAX_DATA_LENGTH);
 
  nValue = strtonum(strValue);
 
 
  if ((args==2) && (strcasecmp(strType,"votes")==0)) {
    if ( (nValue < MIN_ROCKSNEEDED) || (nValue > MAX_ROCKSNEEDED)) {
      g_RocksNeeded = DEFAULT_ROCKSNEEDED;
    }
    else {
      g_RocksNeeded = nValue;
    }
    writevaultnum("bbmap_rocksneeded",g_RocksNeeded);
    ClearRockedTheVote();
    ShowConfig(UserIndex,0);
  }
  else if ((args==2) && (strcasecmp(strType,"percent")==0)) {
    if ( (nValue < MIN_ROCKSPERCENT) || (nValue > MAX_ROCKSPERCENT)) {
      g_RocksPercent = DEFAULT_ROCKSPERCENT;
    }
    else {
      g_RocksPercent = nValue;
    }
    writevaultnum("bbmap_rockspercent",g_RocksPercent);
    ClearRockedTheVote();
    ShowConfig(UserIndex,0);
  }
  else if (((args==2) && (strcasecmp(strType,"minutes")==0)) || (args==1)) {
    if ( (nValue < MIN_ROCKTHEVOTE) || (nValue > MAX_ROCKTHEVOTE)) {
      g_RockTheVote = DEFAULT_ROCKTHEVOTE;
    }
    else {
      g_RockTheVote = nValue;
    }
    writevaultnum("bbmap_rockthevote",g_RockTheVote);
    ShowConfig(UserIndex,0);
  }
  else {
    new UserName[MAX_NAME_LENGTH];
    safe_convert(HLUserName,UserName,MAX_NAME_LENGTH);
    RockTheVote(UserIndex,UserName,1,1);
  }
  return PLUGIN_HANDLED;
}
 
public BBMapList(HLCommand,HLData,HLUserName,UserIndex) {
  new Data[MAX_DATA_LENGTH];
  safe_convert(HLData,Data,MAX_DATA_LENGTH);
  ShowMapList(UserIndex,Data,0);
  return PLUGIN_HANDLED;
}
 
public BBMapReset(HLCommand,HLData,HLUserName,UserIndex) {
  g_InfoCount=DEFAULT_MESSAGEFREQUENCY;
  g_Extends=DEFAULT_MAX_EXTENDS;
  g_ExtendTime=DEFAULT_EXTEND_TIME;
  g_BanLast=DEFAULT_BAN_LAST;
  g_Mode=1;
  g_Verbosity=DEFAULT_VERBOSITY;
  g_MsgLocation=DEFAULT_MESSAGELOCATION;
  g_MsgFrequency=DEFAULT_MESSAGEFREQUENCY;
  g_RockTheVote = DEFAULT_ROCKTHEVOTE;
  g_RocksNeeded = DEFAULT_ROCKSNEEDED;
  g_RocksPercent = DEFAULT_ROCKSPERCENT;
  g_NextMenuSize = DEFAULT_NOMINATIONS;
  g_DurationMins=DEFAULT_DURATIONMINS;
  g_DurationRounds=DEFAULT_DURATIONROUNDS;
  g_DurationWins=DEFAULT_DURATIONWINS;
  g_DurationWinsLead=DEFAULT_DURATIONWINSLEAD;
  g_DurationFrags=DEFAULT_DURATIONFRAGS;
  g_DurationFragsLead=DEFAULT_DURATIONFRAGSLEAD;
  g_Nomination=DEFAULT_NOMINATION;
  g_Echo=DEFAULT_ECHO;
  g_Sync=DEFAULT_SYNC;
  g_MapsURL[0]=0;
 
  writevaultnum("bbmap_vote",g_Mode);
  writevaultnum("bbmap_menusize",g_NextMenuSize);
  writevaultnum("bbmap_extends",g_Extends);
  writevaultnum("bbmap_extendtime",g_ExtendTime);
  writevaultnum("bbmap_banlast",g_BanLast);
  writevaultnum("bbmap_verbosity",g_Verbosity);
  writevaultnum("bbmap_msglocation",g_MsgLocation);
  writevaultnum("bbmap_msgfrequency",g_MsgFrequency);
  writevaultnum("bbmap_rockthevote",g_RockTheVote);
  writevaultnum("bbmap_rocksneeded",g_RocksNeeded);
  writevaultnum("bbmap_rockspercent",g_RocksPercent);
  writevaultnum("bbmap_durationmins",g_DurationMins);
  writevaultnum("bbmap_durationrounds",g_DurationRounds);
  writevaultnum("bbmap_durationwins",g_DurationWins);
  writevaultnum("bbmap_durationwinslead",g_DurationWinsLead);
  writevaultnum("bbmap_durationfrags",g_DurationFrags);
  writevaultnum("bbmap_durationfragslead",g_DurationFragsLead);
  writevaultnum("bbmap_nomination",g_Nomination);
  writevaultnum("bbmap_echo",g_Echo);
  writevaultnum("bbmap_sync",g_Echo);
  set_vaultdata("bbmap_mapsurl",g_MapsURL);
 
  return ShowConfig(UserIndex,0);
}
 
public BBMapReport(HLCommand,HLData,HLUserName,UserIndex) {
  if (readHLonoff(HLData,g_Report,DEFAULT_REPORT,MIN_REPORT,MAX_REPORT)) {
    writevaultnum("bbmap_report",g_Report);
    ShowConfig(UserIndex,0);
  }
  else {
    ShowConfig(UserIndex,1);
  }
  return PLUGIN_HANDLED;
}
 
public BBMapEcho(HLCommand,HLData,HLUserName,UserIndex) {
  if (readHLonoff(HLData,g_Echo,DEFAULT_ECHO,MIN_ECHO,MAX_ECHO)) {
    writevaultnum("bbmap_report",g_Echo);
    ShowConfig(UserIndex,0);
  }
  return PLUGIN_HANDLED;
}
 
public BBMapSync(HLCommand,HLData,HLUserName,UserIndex) {
  if (readHLonoff(HLData,g_Sync,DEFAULT_SYNC,MIN_SYNC,MAX_SYNC)) {
    writevaultnum("bbmap_sync",g_Sync);
    ShowConfig(UserIndex,0);
  }
  return PLUGIN_HANDLED;
}
 
ShowConfig(UserIndex,Force) {
  if ((Force==0) && (g_Report==0)) {
    return PLUGIN_HANDLED;
  }
 
  language_say(UserIndex,MSG_REPORTTITLE,print_type:print_console);
  if (g_Mode ==0) {
    language_say(UserIndex,MSG_RPT_DISABLED,print_type:print_console);
  }
  else if (g_Mode == 1) {
    language_say(UserIndex,MSG_RPT_VOTING,print_type:print_console);
  }
  else if (g_Mode == 2) {
    language_say(UserIndex,MSG_RPT_CYCLE,print_type:print_console);
  }
 
 
 
  language_sayf(UserIndex,MSG_RPT_DURATION,print_type:print_console,0,0,0,0);
  if (g_DurationMins>0) {
    language_sayf(UserIndex,MSG_RPT_DURATIONMINUTES_ON,print_type:print_console,0,0,0,0,g_DurationMins);
  }
  else {
    language_say(UserIndex,MSG_RPT_DURATIONMINUTES_OFF,print_type:print_console);
  }
  if (g_DurationRounds>0) {
    language_sayf(UserIndex,MSG_RPT_DURATIONROUNDS_ON,print_type:print_console,0,0,0,0,g_DurationRounds);
  }
  else {
    language_say(UserIndex,MSG_RPT_DURATIONROUNDS_OFF,print_type:print_console);
  }
  if (g_DurationWins>0) {
    language_sayf(UserIndex,MSG_RPT_DURATIONWINS_ON,print_type:print_console,0,0,0,0,g_DurationWins);
  }
  else {
    language_say(UserIndex,MSG_RPT_DURATIONWINS_OFF,print_type:print_console);
  }
  if (g_DurationWinsLead>0) {
    language_sayf(UserIndex,MSG_RPT_DURATIONWINSLEAD_ON,print_type:print_console,0,0,0,0,g_DurationWinsLead);
  }
  else {
    language_say(UserIndex,MSG_RPT_DURATIONWINSLEAD_OFF,print_type:print_console);
  }
  if (g_DurationFrags>0) {
    language_sayf(UserIndex,MSG_RPT_DURATIONFRAGS_ON,print_type:print_console,0,0,0,0,g_DurationFrags);
  }
  else {
    language_say(UserIndex,MSG_RPT_DURATIONFRAGS_OFF,print_type:print_console);
  }
  if (g_DurationFragsLead>0) {
    language_sayf(UserIndex,MSG_RPT_DURATIONFRAGSLEAD_ON,print_type:print_console,0,0,0,0,g_DurationFragsLead);
  }
  else {
    language_say(UserIndex,MSG_RPT_DURATIONFRAGSLEAD_OFF,print_type:print_console);
  }
 
 
  language_sayf(UserIndex,MSG_RPT_MENUSIZE,print_type:print_console,0,0,0,0,g_NextMenuSize);
  language_sayf(UserIndex,MSG_RPT_EXTEND,print_type:print_console,0,0,0,0,g_Extends);
  language_sayf(UserIndex,MSG_RPT_EXTENDTIME,print_type:print_console,0,0,0,0,g_ExtendTime);
  language_sayf(UserIndex,MSG_RPT_BANLAST,print_type:print_console,0,0,0,0,g_BanLast);
  language_sayf(UserIndex,MSG_RPT_VERBOSITY,print_type:print_console,0,0,0,0,g_Verbosity);
  language_sayf(UserIndex,MSG_RPT_LOCATION,print_type:print_console,0,0,0,0,g_MsgLocation);
  language_sayf(UserIndex,MSG_RPT_FREQUENCY,print_type:print_console,0,0,0,0,g_MsgFrequency);
  language_sayf(UserIndex,MSG_RPT_ROCKTHEVOTE,print_type:print_console,0,0,0,0,g_RockTheVote);
  language_sayf(UserIndex,MSG_RPT_ROCKSNEEDED,print_type:print_console,0,0,0,0,g_RocksNeeded);
  language_sayf(UserIndex,MSG_RPT_ROCKSPERCENT,print_type:print_console,0,0,0,0,g_RocksPercent);
  if (g_Nomination) {
    language_say(UserIndex,MSG_RPT_NOMINATION_ON,print_type:print_console,0,0,0,0);
  }
  else {
    language_say(UserIndex,MSG_RPT_NOMINATION_OFF,print_type:print_console,0,0,0,0);
  }
  if (g_Echo) {
    language_say(UserIndex,MSG_RPT_ECHO_ON,print_type:print_console,0,0,0,0);
  }
  else {
    language_say(UserIndex,MSG_RPT_ECHO_OFF,print_type:print_console,0,0,0,0);
  }
  if (g_Sync) {
    language_say(UserIndex,MSG_RPT_SYNC_ON,print_type:print_console,0,0,0,0);
  }
  else {
    language_say(UserIndex,MSG_RPT_SYNC_OFF,print_type:print_console,0,0,0,0);
  }
  language_sayf(UserIndex,MSG_RPT_DOWNLOAD,print_type:print_console,0,0,0,0,g_MapsURL);
 
  return PLUGIN_HANDLED;
}
 
 
 
 
 
 
 
 
 
 
 
 
/*
 *********************************************************
 *                 LOGD CALLBACK HANDLERS                *
 *********************************************************
*/
 
CheckIfMapShouldChange() {
  new stay = CanStayOnMap(1);
/*
  new msg[MAX_TEXT_LENGTH];
  snprintf(msg,MAX_TEXT_LENGTH,"CT %i, T %i, Stay result %i",g_TeamScore[1],g_TeamScore[2],stay);
  log(msg);*/
 
  if (stay > 0) {
    if (g_Mode==1) {
      DoInformVote30();
    }
    if (g_Mode==2) {
      CycleMaps();
    }
  }
}
 
public BBLogdWorldAction(HLCommand,HLData,HLUserName,UserIndex) {
 
  new Data[MAX_DATA_LENGTH];
  safe_convert(HLData,Data,MAX_DATA_LENGTH);
 
  g_LogdDetected =1;
 
  if (strmatch(Data,"Round_Start",11)==1) {
    Event_RoundStart(0);
  }
  else if (strmatch(Data,"Round_End",9)==1) {
    Event_RoundEnd(0);
  }
 
  return PLUGIN_HANDLED;
}
 
public Event_RoundStart( event ) {
  /* OK, logd hasn't been detected, but behave as if it had! */
  g_LogdDetected =1;
 
  if (g_BuyTimer != 0) {
    kill_timer(g_BuyTimer);
  }
  g_BuyTimer = set_timer("BuyTimeEnd",10,1,"");
}
 
public Event_RoundEnd( event ) {
    if (event !=0) {
      /* Real RoundEnd event from adminmod, rather than logd
       *
       * Therefore there is no teamaction events, so we have
       * to check team scores here.
       */
#if ADMINMOD_VERSION >= 25100
      g_BuyTimer = set_timer("BuyTimeEnd",5,1,"");
 
      new nType = event_getkey_int( event, "side");
      if (nType==0) {
        /* CT win */
        g_TeamScore[2] = g_TeamScore[2]+1;
      }
      else if (nType==1) {
        /* T win */
        g_TeamScore[1] = g_TeamScore[1]+1;
      }
      CheckIfMapShouldChange();
#endif
    }
 
    /* Common code for roundend from logd or adminmod */
    if (strlen(g_NextMap)>0) {
       if (set_timer("DoChangeMap",5,1,"")==0) {
         DoChangeMap(0);
       }
    }
}
 
 
public BuyTimeEnd() {
  g_BuyTimer = 0;
}
 
 
public BBLogdTeamAction(HLCommand,HLData,HLUserName,UserIndex) {
 
  new strData[MAX_DATA_LENGTH];
  safe_convert(HLData,strData,MAX_DATA_LENGTH);
 
  g_LogdDetected =1;
  g_BuyTimer = set_timer("BuyTimeEnd",5,1,"");
 
  new strEvent[MAX_DATA_LENGTH];
  new strT[MAX_DATA_LENGTH];
  new strCT[MAX_DATA_LENGTH];
 
  new args=strsplit(strData,"#",strEvent,MAX_DATA_LENGTH,strCT,MAX_DATA_LENGTH,strT,MAX_DATA_LENGTH);
 
  if (args==3) {
 
    /* A team has won a game, evaluate duration based on
     * rounds, team wins and team wins lead now */
    g_TeamScore[2]=strtonum(strCT);
    g_TeamScore[1]=strtonum(strT);
    CheckIfMapShouldChange();
  }
 
  return PLUGIN_HANDLED;
}
 
public BBLogdPlayerAction(HLCommand,HLData,HLUserName,UserIndex) {
  new strData[MAX_DATA_LENGTH];
  safe_convert(HLData,strData,MAX_DATA_LENGTH);
 
  if ((g_DurationFrags>0) || (g_DurationFragsLead>0)) {
    CheckIfMapShouldChange();
  }
  return PLUGIN_HANDLED;
}
 
public Death( event )
{
    CheckIfMapShouldChange();
}
 
 
 
 
 
 
 
 
 
 
/*
 *********************************************************
 *                 CHAT COMMAND HANDLERS                 *
 *********************************************************
*/
 
public SayCommand(HLCommand,HLData,HLUserName,UserIndex) {
  new Data[MAX_DATA_LENGTH];
  new UserName[MAX_NAME_LENGTH];
  new EchoCommand;
  new i;
  new lenData;
 
  safe_convert(HLData,Data,MAX_DATA_LENGTH);
  safe_convert(HLUserName,UserName,MAX_NAME_LENGTH);
  strstripquotes(Data);
 
  new retVal = PLUGIN_HANDLED;
  if (g_Echo) {
    retVal = PLUGIN_CONTINUE;
  }
 
   /* Swallow attempts to spam chat with numbers during voting */
  if (g_VoteActive) {
     lenData=strlen(Data);
     new fOk=0;
     for (i=0;i<lenData;i++) {
       if ( ((Data[i]>64)&&(Data[i]<91)) || ((Data[i]>96)&&(Data[i]<123))) {
         fOk=1;
         break;
       }
     }
     if (fOk==0) {
       return PLUGIN_HANDLED;
     }
  }
 
  if ((g_Mode == 1) && (g_Nomination == 1)) {
 
    if (strmatch(Data,"nominations",11)==1) {
      ReportNominations();
      return retVal;
    }
 
    if (strmatch(Data,"nominate ",9)==1) {
 
      lenData = strlen(Data);
      for(i=9;i<=lenData;i++) {
        Data[i-9] = Data[i];
      }
      Data[i-9] = NULL_CHAR;
      Nominate(Data,UserName,UserIndex,1,0,EchoCommand);
      return retVal;
    }
 
    if (strmatch(Data,"vote ",5)==1) {
      lenData = strlen(Data);
      for(i=5;i<=lenData;i++) {
        Data[i-5] = Data[i];
      }
      Data[i-5] = NULL_CHAR;
      Nominate(Data,UserName,UserIndex,1,0,EchoCommand);
      return retVal;
    }
 
    if (strmatch(Data, "listmaps",8)==1) {
      ShowMapListChat(UserIndex,Data,9);
      return retVal;
    }
    if (strmatch(Data, "list maps",9)==1) {
      ShowMapListChat(UserIndex,Data,10);
      return retVal;
    }
 
    if ((strcasecmp(Data, "rockthevote")==0) || (strcasecmp(Data, "rock the vote")==0)) {
      RockTheVote(UserIndex,UserName,0,0);
      return retVal;
    }
  }
 
  /* Take over admin_chat's role */
  if ((strcasecmp(Data, "timeleft")==0) || (strcasecmp(Data, "time left")==0) || (strcasecmp(Data, "/timeleft")==0)) {
    if (strcasecmp(Data, "/timeleft")==0) {
      /* Need to always supress echo for /timeleft otherwise statsme
       * will also display the time, and it will be wrong */
      retVal=PLUGIN_HANDLED;
    }
    SayTimeleft();
    return retVal;
  } else if (strcasecmp(Data, "version")==0) {
    language_say(UserIndex,MSG_TYPEVERSION,print_type:print_chat);
    return retVal;
  } else if ((strcasecmp(Data, "nextmap")==0) || (strcasecmp(Data, "next map")==0) || (strcasecmp(Data, "/nextmap")==0)) {
    SayNextMap();
    return retVal;
  } else if ((strcasecmp(Data, "currentmap")==0) || (strcasecmp(Data, "current map")==0) || (strcasecmp(Data, "/currentmap")==0)) {
    SayCurrentMap();
    return retVal;
  }
 
  Nominate(Data,UserName,UserIndex,0,0,EchoCommand);
 
  /*language_output(UserName,Data,print_type:print_console);*/
  if (EchoCommand || g_Echo) {
    return PLUGIN_CONTINUE;
  }
  return PLUGIN_HANDLED;
 
}
 
/* Handles the "nominate" command - invoked from SayCommand */
Nominate(MapName[],UserName[],UserIndex,ShowErrors,LogToConsole,&EchoCommand) {
 
  EchoCommand = 1;
  if (g_Nomination == 0) {
    return 0;
  }
 
  new MapIndex = LookupMap(MapName);
  new print_type:loglocation=print_type:print_pretty;
  if (LogToConsole) { loglocation=print_type:print_console; }
  new print_type:msglocation=print_type:print_chat;
  if ((g_MsgLocation & FLAG_NOMINATION)>0) {
    msglocation=print_type:print_chat;
  }
 
  if ((g_Mode == 0) || (g_Mode == 2)) {
    return 0;
  }
 
  if (g_VoteActive == 1) {
    if (ShowErrors) {
      EchoCommand = 0;
      language_say(UserIndex,MSG_VOTINGINPROGRESSNOMINATE,loglocation,6,ERROR_RED,ERROR_GREEN,ERROR_BLUE);
    }
    return 0;
  }
 
  if (MapIndex == -1) {
    if (ShowErrors) {
      EchoCommand = 0;
      if (strcasecmp(MapName,"extend")==0) {
        language_say(UserIndex,MSG_VOTEEXTEND,loglocation,6,ERROR_RED,ERROR_GREEN,ERROR_BLUE);
      }
      else {
        language_sayf(UserIndex,MSG_MAPNOTFOUND,loglocation,6,ERROR_RED,ERROR_GREEN,ERROR_BLUE,MapName);
      }
    }
    return 0;
  }
 
  if (IsNominated(MapName)==1) {
    EchoCommand = 0;
    language_sayf(UserIndex,MSG_MAPALREADYNOMINATED,loglocation,6,ERROR_RED,ERROR_GREEN,ERROR_BLUE,MapName);
    return 1;
  }
 
  if (IsBanned(MapName)==1) {
    if (g_BanLast == 1) {
      language_sayallf(MSG_MAPBANNED,msglocation,6,ERROR_RED,ERROR_GREEN,ERROR_BLUE,MapName);
    }
    else {
      language_sayallf(MSG_MAPBANNED2,msglocation,6,ERROR_RED,ERROR_GREEN,ERROR_BLUE,MapName,g_BanLast);
    }
    return 0;
  }
 
  EchoCommand = 0;
 
  new NominationIndex = LookupFreeSlot(UserIndex);
  if (NominationIndex ==-1) {
    language_say(UserIndex,MSG_NOMINATIONSFULL,loglocation,6,ERROR_RED,ERROR_GREEN,ERROR_BLUE);
    return 0;
  }
 
  strcpy(g_NominatedMaps[NominationIndex],MapName,MAX_MAP_LENGTH);
  g_Nominators[NominationIndex]=UserIndex;
  language_sayallf(MSG_NOMINATED,msglocation,6,ANNOUNCE_RED,ANNOUNCE_GREEN,ANNOUNCE_BLUE,UserName,MapName);
 
  return 1;
 
}
 
SayCurrentMap() {
  if (g_Mode>0) {
    return SayMapInfo();
  }
 
  new CurrentMap[MAX_NAME_LENGTH];
 
  currentmap(CurrentMap,MAX_NAME_LENGTH);
  language_sayallf(MSG_SAYCURRENT,print_type:print_chat,0,0,0,0,CurrentMap);
  return 0;
}
 
SayTimeleft() {
  if (g_Mode>0) {
    return SayMapInfo();
  }
 
  new MapName[MAX_MAP_LENGTH];
  new Seconds = timeleft(0);
  Seconds /= 60;
  if (Seconds < 1) Seconds =1;
 
  currentmap(MapName,MAX_MAP_LENGTH);
 
  if (Seconds == 1) {
    language_sayallf(MSG_SAYDURATIONTIME1,print_type:print_chat,0,0,0,0,MapName,Seconds);
  }
  else {
    language_sayallf(MSG_SAYDURATIONTIMEN,print_type:print_chat,0,0,0,0,MapName,Seconds);
  }
 
  return 0;
}
 
 
SayNextMap() {
  if (g_Mode>0) {
    return SayMapInfo();
  }
 
 
  new MapName[MAX_MAP_LENGTH];
 
  nextmap(MapName,MAX_MAP_LENGTH);
  language_sayallf(MSG_SAYNEXT,print_type:print_chat,0,0,0,0, MapName);
  return 0;
}
 
SayMapInfo() {
 
 
  new Seconds = g_RoundTime - maptime(0,0);
  Seconds /= 60;
  Seconds += 2;
  if (Seconds < 1) Seconds =1;
 
  if (g_Mode == 1) {
    if (g_VoteActive ==1) {
      if (strlen(g_NextMap)>0) {
        language_sayallf(MSG_SAYVOTEWINNER,print_type:print_chat,0,0,0,0,g_NextMap);
 
        if ((g_LogdDetected == 1) && (g_Sync==1)) {
          language_sayall(MSG_SAYENDOFROUND,print_type:print_chat);
        }
        else {
          language_sayall(MSG_SAYABOUTTOCHANGE,print_type:print_chat);
        }
      }
      else {
        language_sayall(MSG_SAYVOTINGNOW,print_type:print_chat);
      }
    }
    else {
      ShowTimeLeft(Seconds);
      language_sayall(MSG_SAYWILLVOTE,print_type:print_chat);
    }
  }
  else {
    /* Mode must be 2 - cycle */
    if (strlen(g_NextMap)>0) {
      language_sayallf(MSG_SAYCYCLENEXT,print_type:print_chat,0,0,0,0,g_NextMap);
 
      if ((g_LogdDetected == 1) && (g_Sync==1)) {
        language_sayall(MSG_SAYENDOFROUND,print_type:print_chat);
      }
      else {
        language_sayall(MSG_SAYABOUTTOCHANGE,print_type:print_chat);
      }
    } else {
      ShowTimeLeft(Seconds);
      language_sayallf(MSG_SAYCYCLENEXT,print_type:print_chat,0,0,0,0,g_AllMaps[GetNextMapInCycle()]);
    }
  }
  return 0;
}
 
ShowTimeLeft(Seconds) {
  new CurrentMapName[MAX_MAP_LENGTH];
  currentmap(CurrentMapName,MAX_MAP_LENGTH);
  if (g_DurationMins > 0) {
    if (Seconds ==1) {
      language_sayallf(MSG_SAYDURATIONTIME1,print_type:print_chat, 0,0,0,0,CurrentMapName,Seconds);
    }
    else {
      language_sayallf(MSG_SAYDURATIONTIMEN,print_type:print_chat, 0,0,0,0,CurrentMapName,Seconds);
    }
  }
  else {
    /* Need to examine each other win condition and state the nearest */
  }
}
 
 
 
 
 
 
 
 
 
 
 
 
/*
 *********************************************************
 *                     TIMED MESSAGES                    *
 *********************************************************
*/
 
StartMainTimer() {
  if (g_MainTimer == 0) {
    g_MainTimer=set_timer("Timer",60,1,"");
    if (g_MainTimer==0) {
      plugin_message("ERROR: 1 minute timer would not start. Automatic vote may not happen at the end of this game.");
    }
  }
}
 
StopMainTimer() {
  if (g_MainTimer !=0) {
    kill_timer(g_MainTimer);
    g_MainTimer =0;
  }
}
public Timer(Dummy) {
  g_MainTimer=0;
 
  if (g_VoteActive == 1) {
    /* Timer shouldn't be running if a vote is active, but
     * check anyway for safety */
    plugin_message("ERROR: 1 minute timer was running when it shouldn't be");
    StartMainTimer();
    return 0;
  }
 
  if (g_Mode==1) {
   /* Find out how much time is left */
    if (maptime(0,0) >= g_RoundTime) {
      g_Rocked=0;
      if (DoInformVote30()==0) {
        StartMainTimer();
      }
    }
    else {
      if (g_InfoCount > 0) {
        g_InfoCount--;
      }
      else {
        g_InfoCount = g_MsgFrequency-1;
        g_InfoToggle = 1 - g_InfoToggle;
        if (g_Nomination) {
          if ((g_InfoToggle==0) || ((g_Verbosity & FLAG_AVAILABLEMAPS)==0)) {
          /* Report to the users on the current nomination status */
            if ((g_Verbosity & FLAG_NOMINATEDMAPS)>0) {
              ReportNominations();
            }
          }
          else {
            ReportMapsAvailable("");
          }
        }
      }
      StartMainTimer();
    }
  }
 
  if (g_Mode==2) {
    if (maptime(0,0) >= g_RoundTime) {
      CycleMaps();
    }
    else {
      StartMainTimer();
    }
  }
  return 0;
}
 
 
 
 
 
 
 
 
 
 
 
 
/*
 *********************************************************
 *                    STARTING A VOTE                    *
 *********************************************************
*/
 
GetRocksNeeded() {
  new i=0;
  new nRocked=0;
  for (i=1;i<MAX_PLAYERS;i++) {
    if (g_HasRockedTheVote[i]) {
      nRocked++;
    }
  }
 
  new nBots=0;
  new nPlayers= realplayercount(nBots);
  if (nPlayers == 0) {
    /* If there are no players connected, then
       don't rock */
    return 1;
  }
 
  new nNeeded = nRocked;
  while ((nNeeded * 100) / nPlayers < g_RocksPercent) {
    nNeeded++;
  }
  if (nNeeded < g_RocksNeeded) {
    nNeeded = g_RocksNeeded;
  }
  if (nNeeded > nPlayers) {
    nNeeded = nPlayers;
  }
 
/*
  new s[100];
  snprintf(s,100,"Checking for rock the vote:");
  log(s);
  snprintf(s,100,"Configured votes needed: %i",g_RocksNeeded);
  log(s);
  snprintf(s,100,"Configured percent needed: %i",g_RocksPercent);
  log(s);
  snprintf(s,100,"Actual votes: %i",nRocked);
  log(s);
  snprintf(s,100,"Number of players: %i",nPlayers);
  log(s);
  snprintf(s,100,"Additional needed: %i",nNeeded-nRocked);
  log(s);
 
*/
  if (nNeeded <= nRocked) {
    return 0;
  }
  return nNeeded - nRocked;
}
 
public RockTheVote(UserIndex,UserName[],LogToConsole,AdminUse) {
 
  new print_type:loglocation=print_type:print_chat;
  new msg[MAX_TEXT_LENGTH];
  snprintf(msg,MAX_TEXT_LENGTH,"User %s has attempted to the rockthevote.",UserName);
  log(msg);
 
  if (g_Mode != 1) {
    return 0;
  }
 
  if(LogToConsole) { loglocation=print_type:print_console; }
 
  /* Rock the vote can be used by admins with ACCESS_VOTE_MAP at any time,
   * but only by those with no access when no admins are present
   * in the game */
 
  /* Removed this restriction as it can be used to detect admins
  if (!access(ACCESS_VOTE_MAP,"") && adminpresent(ACCESS_MAP)) {
    language_say(UserIndex,MSG_ROCKADMINPRESENT,loglocation,6,ERROR_RED,ERROR_GREEN,ERROR_BLUE);
    return 0;
  }
  */
 
  /* Rock the vote can not be used when voting in progress */
  if (g_VoteActive) {
    language_say(UserIndex,MSG_VOTINGALREADYINPROGRESS,loglocation,6,ERROR_RED,ERROR_GREEN,ERROR_BLUE);
    return 0;
  }
 
  new rocktime = (g_RockTheVote*60) - maptime(0,0);
 
  if (AdminUse==0) {
    /* Rock the vote can not be used if the rock time is set larger than the round time
     * except by admins.
     */
    if (g_RockTheVote >= g_RoundTime) {
      language_say(UserIndex,MSG_ROCKBANNED,loglocation,6,ERROR_RED,ERROR_GREEN,ERROR_BLUE);
      return 0;
    }
 
    /* Rock the vote can not be used more than once by the same player */
    if (g_HasRockedTheVote[UserIndex]==1) {
      new nRocksNeeded= GetRocksNeeded();
      language_sayf(UserIndex,MSG_ROCKEDALREADY,loglocation,6,ERROR_RED,ERROR_GREEN,ERROR_BLUE,nRocksNeeded);
      return 0;
    }
 
    /* Check the time limit */
    if (rocktime > 0) {
      rocktime /= 60;
      rocktime += 2;
      if (rocktime < 1) rocktime =1;
      if (rocktime ==1) {
        language_sayf(UserIndex,MSG_ROCKIN1MINUTE,loglocation,6,ERROR_RED,ERROR_GREEN,ERROR_BLUE,rocktime);
      }
      else {
        language_sayf(UserIndex,MSG_ROCKINNMINUTES,loglocation,6,ERROR_RED,ERROR_GREEN,ERROR_BLUE,rocktime);
      }
      return 1;
    }
 
    if (vote_allowed()==0) {
      language_say(UserIndex,MSG_ROCKNOTALLOWED,loglocation,6,ERROR_RED,ERROR_GREEN,ERROR_BLUE);
      return 0;
    }
 
    g_HasRockedTheVote[UserIndex]=1;
    new nRocksNeeded= GetRocksNeeded();
    if (nRocksNeeded>0) {
      language_sayallf(MSG_ROCKSREQUIRED,loglocation,6,ERROR_RED,ERROR_GREEN,ERROR_BLUE,nRocksNeeded);
      return 0;
    }
 
  }
 
  g_Rocked = 1;
  snprintf(msg,MAX_TEXT_LENGTH,"Starting voting process due to user %s rocking the vote.",UserName);
  log(msg);
 
  DoInformVote30();
  return 1;
 
 
}
 
ClearRockedTheVote() {
  new i;
  for (i=1;i<MAX_PLAYERS;i++) {
    g_HasRockedTheVote[i]=0;
  }
}
 
 
public DoInformVote30() {
  if (strlen(g_NextMap)>0) {
    /* An admin must have used admin_map while the vote was running */
    return 0;
  }
 
  /* prevent two countdowns going at the same time */
  if ((g_CountDownActive == 1) || (g_VoteActive == 1)) {
    return 0;
  }
 
  if (vote_allowed()==0) {
    if (set_timer("DoInformVote30",5,1,"")) {
      return 1;
    }
    else {
      return 0;
    }
  }
 
  g_CountDownActive = 1;
  g_DelaySupressed = 0;
  StopMainTimer();
 
  /* Determine if extend option is available. It is if
   * the vote is due to timeout and max extends isn't reached,
   * or if this was cause by rockthevote. */
  new nReason = CanStayOnMap(0);
  g_LastVoteAllowedExtend = ((g_NumExtends<g_Extends) || (g_Rocked>0)) && (nReason==0);
  g_VoteAbortable = 1;
  g_VoteAborted=0;
 
  if ((g_Verbosity & FLAG_COUNTDOWNTALK) > 0) {
    SpeakAll("nominal(e55) eight(s8) the mass(e40) cap(s50) you want(e90) to(e60) plant(e30) away(s40)");
  }
  if ((g_Verbosity & FLAG_COUNTDOWNMESSAGE) > 0) {
    language_sayall(MSG_VOTINGABOUTTOBEGIN,getlocation(FLAG_COUNTDOWNMESSAGE),8,ANNOUNCE_RED,ANNOUNCE_GREEN,ANNOUNCE_BLUE);
  }
 
  if (set_timer("DoInformVote10",25,1,"")==0) {
    /* If this fails, timers are buggered, so start the vote now */
    DoStartVote();
  }
  return 1;
}
 
 
public DoInformVote10() {
  if (strlen(g_NextMap)>0) {
    /* An admin must have used admin_map while the vote was running */
    return 0;
  }
 
  if (g_VoteAborted) {
    if (AbortVote()) {
      return 0;
    }
  }
 
  /* prevent voting starting at the start of a round
   * when everyone is pressing number keys to buy */
  if ((g_BuyTimer != 0) && (g_DelaySupressed == 0)) {
    g_DelaySupressed = 1;
    set_timer("DoInformVote10",17,1,"");
    return 0;
  }
 
  if ((g_Verbosity & FLAG_COUNTDOWNTALK) > 0) {
    SpeakAll("get red(e80) thirty(s60) to voltage(e35) test(e20) for relay(s60) mass(e40) cap(s50)");
 
  }
  if ((g_Verbosity & FLAG_COUNTDOWNMESSAGE) > 0) {
    language_sayall(MSG_VOTINGABOUTTOBEGIN,getlocation(FLAG_COUNTDOWNMESSAGE),10,ANNOUNCE_RED,ANNOUNCE_GREEN,ANNOUNCE_BLUE);
  }
  set_timer("DoInformVote5",5,1,"");
  return 0;
}
 
public DoInformVote5() {
  if (strlen(g_NextMap)>0) {
    /* An admin must have used admin_map while the vote was running */
    return 0;
  }
 
  if (g_VoteAborted) {
    if (AbortVote()) {
      return 0;
    }
  }
 
  /* prevent voting starting at the start of a round
   * when everyone is pressing number keys to buy */
  if ((g_BuyTimer != 0) && (g_DelaySupressed == 0)) {
    if ((g_Verbosity & FLAG_COUNTDOWNTALK) > 0) {
      SpeakAll("voltage(e35) test(e20) detain(e20) laser(e45) breached(s66) to bypass(e40) weapon son(e20)");
    }
    language_sayall(MSG_VOTINGPAUSED,getlocation(FLAG_COUNTDOWNMESSAGE),5,ANNOUNCE_RED,ANNOUNCE_GREEN,ANNOUNCE_BLUE);
    g_DelaySupressed = 1;
    set_timer("DoInformVote5",17,1,"");
    return 0;
  }
 
 
  if ((g_Verbosity & FLAG_COUNTDOWNTALK) > 0) {
    SpeakAll("holo/tr_holo_commencing(s28)");
  }
  if ((g_Verbosity & FLAG_COUNTDOWNMESSAGE) > 0) {
    language_sayall(MSG_VOTINGABOUTTOBEGIN,getlocation(FLAG_COUNTDOWNMESSAGE),5,ANNOUNCE_RED,ANNOUNCE_GREEN,ANNOUNCE_BLUE);
  }
  set_timer("DoInformVote0",5,1,"");
  return 0;
}
 
public DoInformVote0() {
  if (strlen(g_NextMap)>0) {
    /* An admin must have used admin_map while the vote was running */
    return 0;
  }
 
  if (g_VoteAborted) {
    if (AbortVote()) {
      return 0;
    }
  }
 
  /* prevent voting starting at the start of a round
   * when everyone is pressing number keys to buy */
  if ((g_BuyTimer != 0) && (g_DelaySupressed == 0)) {
    if ((g_Verbosity & FLAG_COUNTDOWNTALK) > 0) {
      SpeakAll("voltage(e35) test(e20) detain(e20) laser(e45) breached(s66) to bypass(e40) weapon son(e20)");
    }
    language_sayall(MSG_VOTINGPAUSED,getlocation(FLAG_COUNTDOWNMESSAGE),5,ANNOUNCE_RED,ANNOUNCE_GREEN,ANNOUNCE_BLUE);
    g_DelaySupressed = 1;
    set_timer("DoInformVote5",22,1,"");
    return 0;
  }
 
  if ((g_Verbosity & FLAG_COUNTDOWNTALK) > 0) {
    SpeakAll("gman/gman_choose1");
  }
  DoStartVote();
  return 0;
}
 
 
 
DoStartVote() {
  new i=0;
  new j=0;
 
  /*Time to start a vote */
  g_CountDownActive=0;
  g_VoteActive=1;
  g_VoteAborted=0;
 
  new Current[MAX_MAP_LENGTH];
  currentmap(Current,MAX_MAP_LENGTH);
 
 
  /* Clear any maps from a previous menu that were introduced as padding, not nominations */
  for(i=0;i<g_ThisMenuSize;i++) {
    if (g_Nominators[i]==0) {
      g_NominatedMaps[i][0]=0;
    }
  }
 
  /*Pad any free slots with top maps */
  new fAllowAll=0;
  for(i=0;i<g_ThisMenuSize;i++) {
    if (g_Nominators[i] ==0) {
      while (
       (
        (IsNominated(g_AllMaps[j])==1) ||
        (
         (
          (strcasecmp(g_AllMaps[j],Current)==0) ||
          (IsInMapcycle(g_AllMaps[j])==0)
         ) &&
         (fAllowAll==0)
        )
       ) &&
       (j<g_AllMapsSize)
      ) {
        j++;
      }
      if (j<g_AllMapsSize) {
        g_NominatedMaps[i] = g_AllMaps[j];
        j++;
      }
      else {
        /* Run out of maps, allow all maps in maps.ini to be nominated
         * even if they are the current map, or not in mapcycle.txt */
        fAllowAll++;
        j=0;
        if (fAllowAll == 1) {
          /* If allowall > 1 then something has gone wrong. Prevent *
           * an infinite loop by not decrementing */
          i--;
        }
      }
    }
  }
 
  /* Generate captions */
 
  for(i=0;i<g_ThisMenuSize;i++) {
    strcpy(g_NominatedCaptions[i],g_NominatedMaps[i],MAX_MAP_LENGTH+20);
  }
  MarkCustomMaps();
 
  /* No point in voting if no-one connected */
  new dummy;
  if (realplayercount(dummy) == 0) {
    g_VoteAbortable = 0;
    return DoVoteComplete(1,0);
  }
 
 
  new fOK;
  if (g_LastVoteAllowedExtend) {
    switch(g_ThisMenuSize) {
    case 2:
      fOK=vote("What map would you like to play next?","Stay on this map",
         g_NominatedCaptions[0],g_NominatedCaptions[1],
         "VoteComplete","");
    case 3:
      fOK=vote("What map would you like to play next?","Stay on this map",
         g_NominatedCaptions[0],g_NominatedCaptions[1],g_NominatedCaptions[2],
         "VoteComplete","");
    case 4:
      fOK=vote("What map would you like to play next?","Stay on this map",
         g_NominatedCaptions[0],g_NominatedCaptions[1],g_NominatedCaptions[2],g_NominatedCaptions[3],
         "VoteComplete","");
    case 5:
      fOK=vote("What map would you like to play next?","Stay on this map",
         g_NominatedCaptions[0],g_NominatedCaptions[1],g_NominatedCaptions[2],g_NominatedCaptions[3],
         g_NominatedCaptions[4],
         "VoteComplete","");
    case 6:
      fOK=vote("What map would you like to play next?","Stay on this map",
         g_NominatedCaptions[0],g_NominatedCaptions[1],g_NominatedCaptions[2],g_NominatedCaptions[3],
         g_NominatedCaptions[4],g_NominatedCaptions[5],
         "VoteComplete","");
    case 7:
      fOK=vote("What map would you like to play next?","Stay on this map",
         g_NominatedCaptions[0],g_NominatedCaptions[1],g_NominatedCaptions[2],g_NominatedCaptions[3],
         g_NominatedCaptions[4],g_NominatedCaptions[5],g_NominatedCaptions[6],
         "VoteComplete","");
    case 8:
      fOK=vote("What map would you like to play next?","Stay on this map",
         g_NominatedCaptions[0],g_NominatedCaptions[1],g_NominatedCaptions[2],g_NominatedCaptions[3],
         g_NominatedCaptions[4],g_NominatedCaptions[5],g_NominatedCaptions[6],g_NominatedCaptions[7],
         "VoteComplete","");
    }
  }
  else {
    g_LastVoteAllowedExtend = 0;
    switch(g_ThisMenuSize) {
    case 2:
      fOK=vote("What map would you like to play next?",
         g_NominatedCaptions[0],g_NominatedCaptions[1],
         "VoteComplete","");
    case 3:
      fOK=vote("What map would you like to play next?",
         g_NominatedCaptions[0],g_NominatedCaptions[1],g_NominatedCaptions[2],
         "VoteComplete","");
    case 4:
      fOK=vote("What map would you like to play next?",
         g_NominatedCaptions[0],g_NominatedCaptions[1],g_NominatedCaptions[2],g_NominatedCaptions[3],
         "VoteComplete","");
    case 5:
      fOK=vote("What map would you like to play next?",
         g_NominatedCaptions[0],g_NominatedCaptions[1],g_NominatedCaptions[2],g_NominatedCaptions[3],
         g_NominatedCaptions[4],
         "VoteComplete","");
    case 6:
      fOK=vote("What map would you like to play next?",
         g_NominatedCaptions[0],g_NominatedCaptions[1],g_NominatedCaptions[2],g_NominatedCaptions[3],
         g_NominatedCaptions[4],g_NominatedCaptions[5],
         "VoteComplete","");
    case 7:
      fOK=vote("What map would you like to play next?",
         g_NominatedCaptions[0],g_NominatedCaptions[1],g_NominatedCaptions[2],g_NominatedCaptions[3],
         g_NominatedCaptions[4],g_NominatedCaptions[5],g_NominatedCaptions[6],
         "VoteComplete","");
    case 8:
      fOK=vote("What map would you like to play next?",
         g_NominatedCaptions[0],g_NominatedCaptions[1],g_NominatedCaptions[2],g_NominatedCaptions[3],
         g_NominatedCaptions[4],g_NominatedCaptions[5],g_NominatedCaptions[6],g_NominatedCaptions[7],
         "VoteComplete","");
    }
  }
 
 
  if (fOK) {
    if ((FLAG_VOTINGINPROGRESS & g_Verbosity)>0) {
      language_sayall(MSG_VOTINGINPROGRESS,getlocation(FLAG_VOTINGINPROGRESS),15,ANNOUNCE_RED,ANNOUNCE_GREEN,ANNOUNCE_BLUE);
    }
  }
  else {
    plugin_message("ERROR: The vote() function failed to run");
    g_VoteActive=0;
    g_VoteAbortable = 0;
    if (g_Rocked) {
      language_sayall(MSG_ROCKFAILED,print_type:print_chat);
    }
    else {
      if ((FLAG_VOTINGINPROGRESS & g_Verbosity)>0) {
        language_sayall(MSG_ERRORSTARTINGVOTE,getlocation(FLAG_VOTINGINPROGRESS),5,ERROR_RED,ERROR_GREEN,ERROR_BLUE);
      }
    }
    StartMainTimer();
  }
 
 
  return 0;
}
 
/* Invoked by adminmod when a menu driven vote is complete */
/* es vote results and changes map */
public VoteComplete(Winner,HLData,VoteCount,UserCount) {
  if (strlen(g_NextMap)>0) {
    /* An admin must have used admin_map while the vote was running */
    return PLUGIN_HANDLED;
  }
  g_VoteAbortable = 0;
  g_CountDownActive = 0;
  ClearRockedTheVote();
  return DoVoteComplete(Winner,VoteCount);
}
 
DoVoteComplete(Winner,VoteCount) {
 
  new dummy;
  new pc = realplayercount(dummy);
 
  if ((g_Mode == 0) || (g_Mode == 2)) {
    plugin_message("WARNING: Map vote results ignore because bbmap_mode changed while vote was running.");
    return 0;
  }
 
 
 
  if (((Winner < 2) && (pc>0) && (g_LastVoteAllowedExtend)) || g_VoteAborted) {
    if (g_VoteAborted) {
     language_sayall(MSG_VOTECANCELED,getlocation(FLAG_WINNEROFVOTE),15,ANNOUNCE_RED,ANNOUNCE_GREEN,ANNOUNCE_BLUE);
    }
    else {
      if ((FLAG_WINNEROFVOTE & g_Verbosity) >0) {
        language_sayallf(MSG_REMAININGWON,getlocation(FLAG_WINNEROFVOTE),15,ANNOUNCE_RED,ANNOUNCE_GREEN,ANNOUNCE_BLUE,VoteCount,pc);
      }
    }
 
    if (g_Rocked == 0) {
      g_NumExtends++;
      g_RoundTime = g_RoundTime + (g_ExtendTime *60);
      SetMapTime(g_RoundTime + SAFETY_MARGIN);
    }
    g_VoteActive=0;
    if ((g_Verbosity & FLAG_COUNTDOWNTALK) > 0) {
      SpeakAll("you have(s25) voltage(e35) detected(s60) to remaining(e64) son(s25) this mass(e40) cap(s50)");
    }
 
    StartMainTimer();
  }
  else {
    new MapIndex = Winner-1;
    if (g_LastVoteAllowedExtend) {
      MapIndex = Winner-2;
    }
    /*Guard for admin_command bbmap_rockthevote with no players connected! */
    if (MapIndex<0) {
      MapIndex=0;
    }
 
    new fCustomMap = (strcmp(g_NominatedMaps[MapIndex],g_NominatedCaptions[MapIndex])!=0);
    g_NumExtends=0;
    if (valid_map(g_NominatedMaps[MapIndex]) !=1) {
      if ((FLAG_WINNEROFVOTE & g_Verbosity) >0) {
        language_sayall(MSG_MAPNOTAVAILABLE,getlocation(FLAG_WINNEROFVOTE),15,ERROR_RED,ERROR_GREEN,ERROR_BLUE);
      }
      plugin_message("ERROR: The map the won the vote is not a valid map name! Check your map list files.");
      g_VoteActive=0;
      StartMainTimer();
    }
    else {
      if ((g_LogdDetected==1) && (g_Sync==1)) {
        if ((FLAG_WINNEROFVOTE & g_Verbosity) >0) {
          language_sayallf(MSG_REPORTWINNER_LOGD,getlocation(FLAG_WINNEROFVOTE),15,ANNOUNCE_RED,ANNOUNCE_GREEN,ANNOUNCE_BLUE,g_NominatedMaps[MapIndex],VoteCount,pc);
        }
      }
      else {
        if ((FLAG_WINNEROFVOTE & g_Verbosity) >0) {
          language_sayallf(MSG_REPORTWINNER,getlocation(FLAG_WINNEROFVOTE),15,ANNOUNCE_RED,ANNOUNCE_GREEN,ANNOUNCE_BLUE,g_NominatedMaps[MapIndex],VoteCount,pc);
        }
        if (set_timer("DoChangeMap",MAP_CHANGE_DELAY,1)==0) {
          DoChangeMap(0);
        }
      }
 
      if ((g_Verbosity & FLAG_COUNTDOWNTALK) > 0) {
        SpeakAll("you have(s25) voltage(e35) detected(s60) to chamber(e40) engage(s65) mass(e40) cap(s50)");
      }
 
      /* Check if new map is custom */
      if (fCustomMap) {
        /* Yes, custom map announcement timer will set g_NextMap to
         * prevent the map change happening if the next round starts
         * before the announcement */
        if (set_timer("AnnounceCustomMap",MAP_CUSTOM_DELAY,4,g_NominatedMaps[MapIndex])==0) {
          DoAnnounceCustomMap(g_NominatedMaps[MapIndex]);
        }
      }
      else {
        /* No, set g_NextMap now so map changes at end of this round */
        strcpy(g_NextMap,g_NominatedMaps[MapIndex],MAX_MAP_LENGTH);
      }
    }
  }
 
  return 0;
}
 
AbortVote() {
  if (g_Rocked == 0) {
    plugin_message("Administrator has aborted an end of game map vote");
    g_NumExtends++;
    g_RoundTime = g_RoundTime + (g_ExtendTime *60);
    SetMapTime(g_RoundTime + SAFETY_MARGIN);
  }
  else {
    plugin_message("Administrator has aborted a rocked map vote");
  }
 
  g_VoteActive = 0;
  g_VoteAbortable = 0;
  g_CountDownActive = 0;
  ClearRockedTheVote();
  language_sayall(MSG_VOTECANCELED,getlocation(FLAG_WINNEROFVOTE),15,ANNOUNCE_RED,ANNOUNCE_GREEN,ANNOUNCE_BLUE);
 
  if ((g_Verbosity & FLAG_COUNTDOWNTALK) > 0) {
    SpeakAll("voltage(e35) test(e20) approach(e15) before(e10) before(s50,e80) detected(s60)");
  }
 
  StartMainTimer();
  return 1;
}
 
 
 
 
 
 
 
 
 
 
/*
 *********************************************************
 *              PERFORMING A MAP CHANGE                  *
 *********************************************************
*/
 
CycleMaps() {
  new i=0;
  while ((IsInMapcycle(g_AllMaps[i])==0) && (i<MAX_MAPS)) {
    i++;
  }
  if (i < MAX_MAPS) {
    if ((g_LogdDetected ==1) && (g_Sync==1)){
      strcpy(g_NextMap,g_AllMaps[i],MAX_MAP_LENGTH);
    }
    else {
      InitiateMapChange(g_AllMaps[i],1);
    }
 
  }
  else {
    configError("No maps in maps.ini also appear in mapcycle.txt - Unable to cycle the map properly");
  }
}
 
InitiateMapChange(NextMap[],ChangeNow) {
  plugin_message("Plugin is initiating map change process");
  strcpy(g_NextMap,NextMap,MAX_MAP_LENGTH);
  g_VoteActive=1;
  StopMainTimer();
 
  if (ChangeNow) {
    DoChangeMap(0);
  }
  else if ((g_LogdDetected ==1) && (g_Sync==1)){
    if ((FLAG_WINNEROFVOTE & g_Verbosity) >0) {
      language_sayallf(MSG_CHANGETOENDOFROUND,getlocation(FLAG_WINNEROFVOTE),15,ANNOUNCE_RED,ANNOUNCE_GREEN,ANNOUNCE_BLUE,g_NextMap);
    }
  }
  else {
    if ((FLAG_WINNEROFVOTE & g_Verbosity) >0) {
      language_sayallf(MSG_CHANGETONOW,getlocation(FLAG_WINNEROFVOTE),15,ANNOUNCE_RED,ANNOUNCE_GREEN,ANNOUNCE_BLUE,g_NextMap);
    }
    if (set_timer("DoChangeMap",MAP_CHANGE_DELAY,1)==0) {
      DoChangeMap(0);
    }
  }
 
}
 
/* If !2SX!'s antiadminkick plugin is installed, invoke its
 * reauth funciton before a map change */
AntiAdminKick() {
  if (plugin_checkcommand("admin_reauth")>0) {
    plugin_exec("admin_reauth","");
  }
}
 
/* Invoked on a shorter timer than DoChangeMap when the map
 * is custom so the download URL can be announced */
 
public AnnounceCustomMap(Timer,Repeat,HLUserName,HLParam) {
  new MapName[MAX_MAP_LENGTH];
  safe_convert(HLParam,MapName,MAX_MAP_LENGTH);
  DoAnnounceCustomMap(MapName);
}
 
DoAnnounceCustomMap(MapName[]) {
  if ((FLAG_CUSTOMMAP & g_Verbosity) >0) {
    if (strlen(g_MapsURL)>0) {
      language_sayallf(MSG_CUSTOMMAPURL,getlocation(FLAG_CUSTOMMAP),15,ANNOUNCE_RED,ANNOUNCE_GREEN,ANNOUNCE_BLUE,MapName,g_MapsURL);
    }
    else {
      language_sayallf(MSG_CUSTOMMAP,getlocation(FLAG_CUSTOMMAP),15,ANNOUNCE_RED,ANNOUNCE_GREEN,ANNOUNCE_BLUE,MapName);
    }
  }
 
  /* Not setting this until this point prevents a map change
     until the URL has been announced when logd is used*/
  strcpy(g_NextMap,MapName,MAX_MAP_LENGTH);
}
 
/* Invoked by a timer from VoteComplete or InitiateMapChange,
 * or if logd is present, is called when the round end is detected.
 * Actually performs the map change after the users
 * have a chance to read the message that it is
 * changing */
public DoChangeMap(Dummy)
{
  if (g_Mode ==0) {
    return 0;
  }
  plugin_message("Map change in progress - step 1 - begin");
 
  MoveMapToEnd(g_NextMap,0);
  SaveMapList();
 
 
  g_BuySupressed=1;
  safe_execall("drop");
  new ms;
  ms = getvar("sv_maxspeed");
  if (ms > 0) {
    g_MaxSpeed = ms;
    exec("sv_maxspeed 0");
  }
 
 
  AntiAdminKick();
  if ((FLAG_WINNEROFVOTE & g_Verbosity) >0) {
    language_sayallf(MSG_CHANGENOW,getlocation(FLAG_WINNEROFVOTE),15,ANNOUNCE_RED,ANNOUNCE_GREEN,ANNOUNCE_BLUE,g_NextMap);
  }
  safe_execall("+showscores");
  if (set_timer("DoChangeMap2",2,1,"")==0) {
    DoChangeMap2();
  }
  return 0;
}
 
public DoChangeMap2()
{
  plugin_message("Map change in progress - step 2 - announcements");
  safe_execall("drop");
  if ((g_Verbosity & FLAG_COUNTDOWNTALK) > 0) {
    if (streq(g_NextMap,"de_dust")) {
      SpeakAll("open(e30) no, not duct(e35) storage(e17) fourth(e60) the thousand(e90) thirty(e20) time");
    }
    else {
      SpeakAll("dadeda attention, loading(e75) mass(e40) cap(s50) on(e75) to(e75) your(e75) computer");
    }
  }
  if ((FLAG_WINNEROFVOTE & g_Verbosity) >0) {
    language_sayallf(MSG_CHANGENOW,getlocation(FLAG_WINNEROFVOTE),15,ANNOUNCE_RED,ANNOUNCE_GREEN,ANNOUNCE_BLUE,g_NextMap);
  }
  safe_execall("+showscores");
  new chattime = getvar("mp_chattime")-2;
  if (chattime<4) { chattime=4; }
 
  if (set_timer("DoChangeMap3",chattime,1,"")==0) {
    DoChangeMap3();
  }
  return 0;
}
 
new g_MapChangeFailed=0;
new g_MapChangeRetryTimer=0;
 
public DoChangeMap3()
{
  if ((g_MapChangeRetryTimer!=0) && (g_MapChangeFailed==0)) {
    g_MapChangeRetryTimer=0;
    return 0;
  }
 
  plugin_message("Map change in progress - step 3 - actual change");
  g_MapChangeFailed=1;
  g_MapChangeRetryTimer =set_timer("DoChangeMap3",1,1,"");
  changelevel(g_NextMap);
  g_MapChangeFailed=0;
  kill_timer(g_MapChangeRetryTimer);
  g_MapChangeRetryTimer=0;
  plugin_message("Plugin has completed the map change process");
 
  new cmd[MAX_DATA_LENGTH];
  snprintf(cmd,MAX_DATA_LENGTH,"sv_maxspeed %i",g_MaxSpeed);
  exec(cmd);
 
  return 0;
}
 
 
 
 
 
/*
 *********************************************************
 *                    READ MAPS FILES                    *
 *********************************************************
*/
 
ReadMapList() {
 
  g_AllMapsSize=0;
  new MapFile[MAX_MAP_LENGTH];
  new Line[MAX_DATA_LENGTH];
  getstrvar("maps_file",MapFile,MAX_MAP_LENGTH);
 
  if (fileexists(MapFile)==0) {
    MapFile="mapcycle.txt";
  }
 
  new ReadCount=filesize(MapFile,fsize_unit:lines)+1;
  if (ReadCount >= MAX_MAPS) {
    new msg[MAX_TEXT_LENGTH];
    snprintf(msg,MAX_TEXT_LENGTH,"You have more than %i maps in %s",MAX_MAPS,MapFile);
    configError(msg);
    log("You must recompile plugin_blatt_map with a higher value for MAX_MAPS");
    return 0;
  }
 
  new i;
  for(i=0;i<ReadCount;i++) {
    /* File line numbers are indexed from 1, not 0, hence the +1 */
    ReadMapLine(MapFile,i,Line);
 
    if (strlen(Line)>0) {
      strcpy(g_AllMaps[g_AllMapsSize],Line,MAX_MAP_LENGTH);
      strcpy(g_AllMapsDisk[g_AllMapsSize],Line,MAX_MAP_LENGTH);
      GetShortName(g_AllMaps[g_AllMapsSize],g_AllMapsShort[g_AllMapsSize]);
      g_AllMapsSize++;
    }
  }
  CheckMenuSize();
  return 1;
}
 
CompareMapList() {
 
  new MapFile[MAX_MAP_LENGTH];
  new Line[MAX_DATA_LENGTH];
  getstrvar("maps_file",MapFile,MAX_MAP_LENGTH);
 
  if (fileexists(MapFile)==0) {
    MapFile="mapcycle.txt";
  }
 
  new ReadCount=filesize(MapFile,fsize_unit:lines)+1;
  if (ReadCount >= MAX_MAPS) {
    return 0;
  }
 
  new i;
  new j=0;
  for(i=0;i<ReadCount;i++) {
    /* File line numbers are indexed from 1, not 0, hence the +1 */
    ReadMapLine(MapFile,i,Line);
 
    if (strlen(Line)>0) {
      if (streq(g_AllMapsDisk[j],Line)==0) {
        return 0;
      }
      j++;
    }
  }
 
  return (j == g_AllMapsSize);
}
 
 
/* Called during initialisation. Only these maps will be auto-nominated. */
/* Reads mapcycle.txt into an g_Mapcycle */
ReadMapcycle() {
 
  g_MapcycleSize=0;
  new MapFile[MAX_MAP_LENGTH]="mapcycle.txt";
  new Line[MAX_DATA_LENGTH];
 
  new ReadCount=filesize(MapFile,fsize_unit:lines)+1;
  if (ReadCount >= MAX_MAPS) {
    new msg[MAX_TEXT_LENGTH];
    snprintf(msg,MAX_TEXT_LENGTH,"You have more than %i maps in mapcycle.txt",MAX_MAPS);
    configError(msg);
    log("You must recompile plugin_blatt_map with a higher value for MAX_MAPS");
    return 0;
  }
 
  new i;
  for(i=0;i<ReadCount;i++) {
    ReadMapLine(MapFile,i,Line);
    if (strlen(Line)>0) {
      strcpy(g_Mapcycle[g_MapcycleSize],Line,MAX_MAP_LENGTH);
      g_MapcycleSize++;
    }
  }
 
  return 1;
}
 
ReadMapLine(MapFile[],i,Line[]) {
  /* File line numbers are indexed from 1, not 0, hence the +1 */
  readfile(MapFile,Line,i,MAX_DATA_LENGTH);
 
  /* Remove trailing comment or nullify the string if its nothing but a comment */
  new pos = strstr(Line, "//");
  if (pos >= 0) {
    Line[pos] = NULL_CHAR;
  }
 
  /* Remove spaces and tabs */
  strtrim(Line," ^t",2);
 
  /* Ignore an options after the map name (tfc allows this) */
  pos=index(Line,' ');
  if (pos>0) {
      Line[pos]=NULL_CHAR;
  }
 
  pos=index(Line,'"');
  if (pos>0) {
    Line[pos]=NULL_CHAR;
  }
  pos=index(Line,'\');
  if (pos>0) {
    Line[pos]=NULL_CHAR;
  }
}
 
 
 
GetShortName(fullname[],shortname[]) {
  new pos = strchr(fullname,'_');
  if (pos>0) {
    pos = pos +1;
  }
  else {
    pos = 0;
  }
 
  new dpos=0;
  while (fullname[pos] != NULL_CHAR) {
    shortname[dpos]=fullname[pos];
    pos++;
    dpos++;
  }
  shortname[dpos] = NULL_CHAR;
}
 
CheckMenuSize() {
  if (g_NextMenuSize > g_AllMapsSize) {
    log("WARNING: bbmaps_menusize is larger than the number of maps in your map list file (maps.ini/mapcycle.txt)");
    g_NextMenuSize = g_AllMapsSize;
    g_ThisMenuSize = g_AllMapsSize;
    return 0;
  }
  return 1;
}
 
SaveMapList() {
  /* If maps file on disk has been changed by some
   * other program while we've had it in memory,
   * don't save it. */
  if (CompareMapList() == 0) {
    return 0;
  }
 
  new i=0;
  new MapFile[MAX_MAP_LENGTH];
  getstrvar("maps_file",MapFile,MAX_MAP_LENGTH);
 
  /* Re-Write maps.ini with the new map order */
  if (fileexists(MapFile)==0) {
    MapFile = "mapcycle.txt";
  }
  resetfile(MapFile);
 
 
  for(i=0;i<g_AllMapsSize;i++) {
    writefile(MapFile,g_AllMaps[i]);
    strcpy(g_AllMapsDisk[i],g_AllMaps[i],MAX_MAP_LENGTH);
  }
 
  return 1;
}
 
 
/* Shows in the vote menu which maps are custom */
MarkCustomMaps() {
 
  g_MapcycleSize=0;
  new MapFile[MAX_TEXT_LENGTH];
  if (getfilelocation(MapFile,"standardmaps.ini") == 0) {
    /* If no standardmaps.ini exists, assume the server admin isn't
     * interested in this feature and say all maps are standard */
    return 0;
  }
 
  new Line[MAX_DATA_LENGTH];
  new Standard[MAX_NOMINATIONS];
  new ReadCount=filesize(MapFile,fsize_unit:lines)+1;
  new i;
  new j;
 
  for (i=0;i<MAX_NOMINATIONS;i++) {
    Standard[i]= 0;
  }
 
  for(i=0;i<ReadCount;i++) {
    ReadMapLine(MapFile,i,Line);
    for(j=0;j<g_ThisMenuSize;j++) {
      if (strcasecmp(g_NominatedMaps[j],Line)==0) {
        Standard[j] = 1;
        break;
      }
    }
  }
 
  for (i=0;i<MAX_NOMINATIONS;i++) {
    if (Standard[i]==0) {
      strcat(g_NominatedCaptions[i]," (Custom)",MAX_MAP_LENGTH+20);
    }
  }
 
  return 1;
}
 
 
/*
 *********************************************************
 *                    SUPPORT FUNCTIONS                  *
 *********************************************************
*/
 
 
ReportNominations() {
  new i=0;
  new msg[MAX_TEXT_LENGTH]="";
 
  /*Stop the timer repeating this report too early */
  if (g_Nominators[0] == 0) {
    if ((g_Verbosity & FLAG_NOMINATEDMAPS) > 0) {
      language_sayall(MSG_NONOMINATIONS,getlocation(FLAG_NOMINATEDMAPS),8,ANNOUNCE_RED,ANNOUNCE_GREEN,ANNOUNCE_BLUE);
 
    }
    return 0;
  }
 
  msg[0]=NULL_CHAR;
  i=0;
  if (g_Nominators[1] != 0) {
    while (g_Nominators[i+2] >0) {
      strcat(msg,g_NominatedMaps[i],MAX_TEXT_LENGTH);
      strcat(msg,", ",MAX_TEXT_LENGTH);
      i++;
    }
    strcat(msg,g_NominatedMaps[i],MAX_TEXT_LENGTH);
    i++;
    strcat(msg," and ",MAX_TEXT_LENGTH);
  }
  strcat(msg,g_NominatedMaps[i],MAX_TEXT_LENGTH);
  strcat(msg,".",MAX_TEXT_LENGTH);
 
 
  if ((g_Verbosity & FLAG_NOMINATEDMAPS) > 0) {
    language_sayallf(MSG_PROMPTNOMINATIONS,getlocation(FLAG_NOMINATEDMAPS),8,ANNOUNCE_RED,ANNOUNCE_GREEN,ANNOUNCE_BLUE,msg);
  }
 
  return 0;
}
 
 
ReportMapsAvailable(UserName[]) {
  new i=0;
  new msg[MAX_TEXT_LENGTH]="";
  new listlen=3;
 
  /*Stop the timer repeating this report too early */
 
  for (i=0;i<listlen;i++) {
    if (i<listlen-2) {
      strcat(msg,g_AllMaps[g_InfoPosition],MAX_TEXT_LENGTH);
      strcat(msg,", ",MAX_TEXT_LENGTH);
    }
    else {
      if (i==listlen-2) {
        strcat(msg,g_AllMaps[g_InfoPosition],MAX_TEXT_LENGTH);
        strcat(msg," and ",MAX_TEXT_LENGTH);
      }
      else {
        strcat(msg,g_AllMaps[g_InfoPosition],MAX_TEXT_LENGTH);
      }
    }
 
    g_InfoPosition++;
    if (g_InfoPosition == g_AllMapsSize) {
      g_InfoPosition = 0;
    }
  }
 
  if (strlen(UserName)) {
    language_saybynamef(UserName,MSG_AREAVAILABLE,getlocation(FLAG_AVAILABLEMAPS),15,ANNOUNCE_RED,ANNOUNCE_GREEN,ANNOUNCE_BLUE,msg);
  }
  else {
    if ((g_Verbosity & FLAG_AVAILABLEMAPS)>0) {
      language_sayallf(MSG_AREAVAILABLE,getlocation(FLAG_AVAILABLEMAPS),8,ANNOUNCE_RED,ANNOUNCE_GREEN,ANNOUNCE_BLUE,msg);
    }
  }
  return 0;
}
 
ShowMapListChat(UserIndex,Search[],Skip) {
  strstrip(Search,Skip);
  if (ShowMapList(UserIndex,Search,1) > 0) {
    if (Search[0] != NULL_CHAR) {
      language_say(UserIndex,MSG_RESULTSINCONSOLE,print_type:print_chat);
    }
    else {
      language_say(UserIndex,MSG_MAPLISTINCONSOLE,print_type:print_chat);
    }
  }
}
 
ShowMapList(UserIndex,Search[],Chat) {
  new i=g_AllMapsSize-1;
  new msg[MAX_TEXT_LENGTH];
  new fDisplay;
  new fSearch = Search[0]!=NULL_CHAR;
  new fShowBannedBanner=0;
  new msgmaps=0;
  new displayed=0;
  new fBannerDisplayed=0;
 
  strcpy(msg,"  ",MAX_TEXT_LENGTH);
  while (i>=0 && (displayed<g_MaxMapListLength)) {
    fDisplay=1;
    if (fSearch) {
      if (strstr(g_AllMaps[i],Search) < 0) {
        fDisplay=0;
      }
    }
    fShowBannedBanner = (i<g_AllMapsSize-g_BanLast) && (g_Mode == 1) && (fBannerDisplayed==0);
 
    if (fDisplay) {
      if ((msgmaps==5) || fShowBannedBanner) {
        selfmessage(msg);
        msg[2]=NULL_CHAR;
        msgmaps=0;
      }
 
      if (msg[2]!=NULL_CHAR) {
        strcat(msg,", ",MAX_TEXT_LENGTH);
      }
      strcat(msg,g_AllMaps[i],MAX_TEXT_LENGTH);
      msgmaps++;
 
      if ((displayed == 0) && (fShowBannedBanner==0) && (fBannerDisplayed==0)) {
        language_say(UserIndex,MSG_BANNEDMAPS,print_type:print_console);
 
      }
      if (fShowBannedBanner) {
        selfmessage(" ");
        language_say(UserIndex,MSG_AVAILABLEMAPS,print_type:print_console);
        fBannerDisplayed=1;
      }
      displayed++;
    }
    i--;
  }
 
  if (msgmaps> 0) {
    selfmessage(msg);
  }
 
  if (i>0) {
    language_sayf(UserIndex,MSG_MAPLISTTRUNCATED,print_type:print_console,0,0,0,0,i);
  }
 
  if ((displayed == 0) && fSearch) {
    if (Chat) {
      language_sayf(UserIndex,MSG_SEARCHFAILED,print_type:print_chat,0,0,0,0,Search);
    }
    else {
      language_sayf(UserIndex,MSG_SEARCHFAILED,print_type:print_console,0,0,0,0,Search);
    }
  }
  if (displayed > 0) {
    selfmessage(" ");
    language_say(UserIndex,MSG_TONOMINATE,print_type:print_console);
  }
 
  return displayed>0;
}
 
 
/* Finds a slot in the nominations menu for a particular users nomination */
LookupFreeSlot(UserIndex) {
  new i;
  new j;
 
  /* First attempt to find an unused nomination slot */
  for(i=0;i<g_ThisMenuSize;i++) {
    if (g_Nominators[i] == 0) {
      return i;
    }
  }
 
  /* No free slots, is there a previous nomination from the user to replace? */
  for(i=g_ThisMenuSize-1;i>=0;i--) {
    if (g_Nominators[i] == UserIndex) {
      return i;
    }
  }
 
  /* OK, how about a previous user who has nominated more than one? */
  for(i=g_ThisMenuSize-1;i>=0;i--) {
    for(j=i+1;j<g_ThisMenuSize;j++) {
      if (g_Nominators[i] == g_Nominators[j]) {
        return j;
      }
    }
  }
 
  /* No free slots */
  return -1;
 
}
 
/* Checks if a name is in maps.ini */
LookupMap(MapName[]) {
 
  /* Remove an .bsp or other extension */
  new pos = strchr(MapName,'.');
  if (pos >0) {
    MapName[pos] = NULL_CHAR;
  }
 
  /* Check for an exact match */
  new i;
  for(i=0;i<g_AllMapsSize;i++) {
    if (strcasecmp(g_AllMaps[i],MapName)==0) {
      strcpy(MapName,g_AllMaps[i],MAX_MAP_LENGTH);
      return i;
    }
  }
 
  /* Check for a match ignoring characters before the _ */
  for(i=0;i<g_AllMapsSize;i++) {
    if (strcasecmp(g_AllMapsShort[i],MapName)==0) {
      strcpy(MapName,g_AllMaps[i],MAX_MAP_LENGTH);
      return i;
    }
  }
  return -1;
}
 
/* Checks is a aname is already in the nominations menu */
IsNominated(MapName[]) {
  new i;
  for(i=0;i<g_ThisMenuSize;i++) {
    if (strcasecmp(g_NominatedMaps[i],MapName)==0) {
      return 1;
    }
  }
  return 0;
}
 
/* Checks is a name is in the bottom BannedMap places of g_AllMaps
 * and therefore is unavailable for nomination */
IsBanned(MapName[]) {
 
  new Current[MAX_MAP_LENGTH];
  currentmap(Current,MAX_MAP_LENGTH);
 
  if (strcasecmp(MapName,Current)==0) {
    return 1;
  }
 
  new i=g_AllMapsSize-g_BanLast;
  if (i<0) { i=0; }
  while(i<g_AllMapsSize) {
    if (streq(g_AllMaps[i],MapName)==1) {
      return 1;
    }
    i++;
  }
  return 0;
}
 
/* Checks if name is in the mapcycle */
IsInMapcycle(MapName[]) {
  new i;
  for(i=0;i<g_MapcycleSize;i++) {
    if (strcasecmp(g_Mapcycle[i],MapName)==0) {
      return 1;
    }
  }
  return 0;
}
 
MoveMapToEnd(MapName[],Offset) {
  /* No point in moving maps if there are aren't enough *
   * to fill the menu anyway */
  if (g_AllMapsSize <= g_NextMenuSize) {
    return 1;
  }
 
  new i = LookupMap(MapName);
  /* Shouldn't be able to happen, but just in case */
  if (i==-1) {
    return 0;
  }
 
  /* Protect against a short map list */
  if (g_AllMapsSize-Offset-1<0) {
    Offset = g_AllMapsSize-1;
  }
 
  while (i<(g_AllMapsSize-Offset-1)) {
    strcpy(g_AllMaps[i],g_AllMaps[i+1],MAX_MAP_LENGTH);
    strcpy(g_AllMapsShort[i],g_AllMapsShort[i+1],MAX_MAP_LENGTH);
    i++;
  }
  strcpy(g_AllMaps[g_AllMapsSize-Offset-1],MapName,MAX_MAP_LENGTH);
  GetShortName(MapName,g_AllMapsShort[g_AllMapsSize-Offset-1]);
  return 0;
}
 
 
SetTimeLimit() {
  /* Prevent built-in map rotation getting in the way */
  if (g_Mode == 0) {
    g_RoundTime = getvar("mp_timelimit") * 60;
  }
  else {
    if (g_DurationMins > 0) {
      g_RoundTime = g_DurationMins * 60;
    }
    else {
      g_RoundTime = 60000;
    }
  }
  SetMapTime(g_RoundTime+SAFETY_MARGIN);
  return 0;
}
 
 
SetMapTime(mins) {
  new ExecCommand[MAX_COMMAND_LENGTH];
  snprintf(ExecCommand, MAX_DATA_LENGTH, "mp_timelimit %i", mins/60);
  exec(ExecCommand,0);
  return 0;
}
 
 
 
getlocation(flag) {
  if ((flag & g_MsgLocation) >0) {
    return print_type:print_pretty;
  }
  return print_type:print_chat;
}
 
 
GetNextMapInCycle() {
  new j=0;
 
  new Current[MAX_MAP_LENGTH];
  currentmap(Current,MAX_MAP_LENGTH);
 
  while (
          ((strcasecmp(g_AllMaps[j],Current)==0) ||
          (IsInMapcycle(g_AllMaps[j])==0))
          && (j<g_AllMapsSize)
      )
  {
        j++;
  }
 
  if (j<g_AllMapsSize) {
    return j;
  }
 
  while (
          (strcasecmp(g_AllMaps[j],Current)==0)
          && (j<g_AllMapsSize)
      )
  {
        j++;
  }
 
  return j;
}
 
 
 
 
/*
 *********************************************************
 *        CHECKING IF VICTORY CONDITIONS ARE MET         *
 *********************************************************
*/
 
 
/* Returns:
 * 0 = can stay on map
 * 1 = time limit exceeed and no extends (only checked when vote is active)
 * 2 = too many rounds played
 * 3 = too many rounds won by one team
 * 4 = one team has too many round wins lead over the others
 * 5 = too many frags be one player
 * 6 = one player has too many frags lead over the other
 */
 
/*BUG: Team win announcing is counterstrike specific at the moment. Mind you
 *     at the time of writting it is the only mod that supports team events anyway.
 *     DOD3 supports team scores, but doesn't log the proper events
 */
CanStayOnMap(fAnnounce) {
  new i;
  new rounds=0;
  new first=0;
  new second=0;
  new firstindex=0;
 
  if ((FLAG_WINNEROFGAME & g_Verbosity) == 0) {
    fAnnounce=0;
  }
 
  /* First check team scores exceeding allowed wins */
  for (i=0 ;i<MAX_TEAMS;i++) {
    rounds=rounds+g_TeamScore[i];
    if (g_TeamScore[i] > first) {
      second = first;
      first = g_TeamScore[i];
      firstindex=i;
    }
    else if (g_TeamScore[i] > second) {
      second = g_TeamScore[i];
    }
 
    if (g_DurationWins > 0) {
      if (g_TeamScore[i] >= g_DurationWins) {
        if (fAnnounce) {
          if ( i == 1) {
            language_sayallf(MSG_REASONT_WINS,print_type:getlocation(FLAG_WINNEROFGAME),15,ANNOUNCE_RED,ANNOUNCE_GREEN,ANNOUNCE_BLUE,g_DurationWins);
          }
          else {
            language_sayallf(MSG_REASONCT_WINS,print_type:getlocation(FLAG_WINNEROFGAME),15,ANNOUNCE_RED,ANNOUNCE_GREEN,ANNOUNCE_BLUE,g_DurationWins);
          }
        }
        return 3;
      }
    }
  }
 
  /* Now check maximum rounds or if one team has too big a lead */
  if ((g_DurationRounds > 0) && (rounds >= g_DurationRounds))  {
    if (fAnnounce) {
      if (g_TeamScore[1] == g_TeamScore[2]) {
        language_sayallf(MSG_REASONTIE_ROUNDS,print_type:getlocation(FLAG_WINNEROFGAME),15,ANNOUNCE_RED,ANNOUNCE_GREEN,ANNOUNCE_BLUE,g_DurationRounds);
      }
      else if  (g_TeamScore[1] > g_TeamScore[2]) {
        language_sayallf(MSG_REASONT_ROUNDS,print_type:getlocation(FLAG_WINNEROFGAME),15,ANNOUNCE_RED,ANNOUNCE_GREEN,ANNOUNCE_BLUE,g_DurationRounds);
      }
      else {
        language_sayallf(MSG_REASONCT_ROUNDS,print_type:getlocation(FLAG_WINNEROFGAME),15,ANNOUNCE_RED,ANNOUNCE_GREEN,ANNOUNCE_BLUE,g_DurationRounds);
      }
    }
    return 2;
  }
 
  if ((g_DurationWinsLead > 0) && (first-second >= g_DurationWinsLead)) {
    if (fAnnounce) {
      if (firstindex==1) {
        language_sayallf(MSG_REASONT_WINSLEAD,print_type:getlocation(FLAG_WINNEROFGAME),15,ANNOUNCE_RED,ANNOUNCE_GREEN,ANNOUNCE_BLUE,g_DurationWinsLead);
      }
      else {
        language_sayallf(MSG_REASONCT_WINSLEAD,print_type:getlocation(FLAG_WINNEROFGAME),15,ANNOUNCE_RED,ANNOUNCE_GREEN,ANNOUNCE_BLUE,g_DurationWinsLead);
 
      }
    }
    return 4;
  }
 
  /* Now check players frags */
  first=0;
  second=0;
  new Name[MAX_NAME_LENGTH];
  new FirstName[MAX_NAME_LENGTH];
  new frags=0;
  new x = maxplayercount();
  for(i=1; i<=x; i=i+1) {
    if(playerinfo(i, Name, MAX_NAME_LENGTH)==1) {
      if (get_userFrags(Name,frags) == 1) {
        if (frags > first) {
          second = first;
          first = frags;
          strcpy(FirstName,Name,MAX_NAME_LENGTH);
        }
        else if (frags > second) {
          second = frags;
        }
 
        if (g_DurationFrags > 0) {
          if (frags >= g_DurationFrags) {
            if (fAnnounce) {
              language_sayallf(MSG_REASONPLAYER_FRAGS,print_type:getlocation(FLAG_WINNEROFGAME),15,ANNOUNCE_RED,ANNOUNCE_GREEN,ANNOUNCE_BLUE,Name,g_DurationFrags);
            }
            return 5;
          }
        }
      }
    }
  }
 
  if ((g_DurationFragsLead > 0) && (first-second >= g_DurationFragsLead)) {
    if (fAnnounce) {
      language_sayallf(MSG_REASONPLAYER_FRAGSLEAD,print_type:getlocation(FLAG_WINNEROFGAME),15,ANNOUNCE_RED,ANNOUNCE_GREEN,ANNOUNCE_BLUE,FirstName,g_DurationFragsLead);
    }
    return 6;
  }
 
  /* Only test number of extends when in a vote and it hasn't been caused by rocking */
  if ((g_VoteActive) && (g_Rocked==0)) {
    if (g_NumExtends>=g_Extends) {
      return 1;
    }
  }
  return 0;
}
 
 
/*
 *********************************************************
 *                 HEAP DEBUGGING CODE                   *
 *********************************************************
*/
 
public DebugHeapInit() {
  g_DebugLevel = getvar("admin_debug");
  if (g_DebugLevel >= 2) {
    set_timer("DebugHeapTimer",2,99999);
  }
  return 0;
}
 
public DebugHeapTimer() {
  return DebugHeap("IDLE");
}
 
DebugHeap(context[]) {
  if (g_DebugLevel >= 2) {
    new heapsize[MAX_TEXT_LENGTH];
    snprintf(heapsize,MAX_TEXT_LENGTH,"[%s] %i free bytes in the plugin heap.",context,heapspace());
    plugin_message(heapsize);
  }
  return 0;
}