#
# Battlefield 3 Parser for BigBrotherBot(B3) (www.bigbrotherbot.net)
# Copyright (C) 2010 Thomas LEVEIL <courgette@bigbrotherbot.net>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
#
#
# versions that only reflect changes to AbstractParser :
# 1.0.1 -> 1.0.3
# 1.1.1 -> 1.1.6
# 1.2.1 -> 1.2.1
# 1.4.1, 1.6
#
# CHANGELOG
#
# 0.1
#  functional parser but BF3 admin protocol is not fully implemented on the BF3 side. See TODOs
# 1.0
#  update parser for BF3 R20
# 1.1
#  reflects changes in AbstractParser and refactor the class by moving some of the code to AbstractParser
# 1.2
#  reflects changes in AbstractParser due to BF3 server R21 release
# 1.3
#  BF3 server R24 changes
# 1.4
#  add available gamemodes by map
#  check minimum required BF3 server version on startup
#  fix issue from 1.3 that made impossible to use commands related to Close Quarter maps
# 1.5
#  add new maps and gamemode from DLC "Armored Kill"
# 1.7
#  add new maps and gamemode from DLC "Aftermath"
# 1.8
#  add GUNMASTER_WEAPONS_PRESET_BY_INDEX and GUNMASTER_WEAPONS_PRESET_BY_NAME constants
# 1.8.1
#  add new maps and gamemodes from DLC "End Game"
#  implement getPlayerPings
#
from b3.parsers.frostbite2.abstractParser import AbstractParser
from b3.parsers.frostbite2.util import PlayerInfoBlock
import b3
import b3.events
__author__  = 'Courgette'
__version__ = '1.8.1'
 
BF3_REQUIRED_VERSION = 1149977
 
SQUAD_NOSQUAD = 0
SQUAD_ALPHA = 1
SQUAD_BRAVO = 2
SQUAD_CHARLIE = 3
SQUAD_DELTA = 4
SQUAD_ECHO = 5
SQUAD_FOXTROT = 6
SQUAD_GOLF = 7
SQUAD_HOTEL = 8
SQUAD_INDIA = 9
SQUAD_JULIET = 10
SQUAD_KILO = 11
SQUAD_LIMA = 12
SQUAD_MIKE = 13
SQUAD_NOVEMBER = 14
SQUAD_OSCAR = 15
SQUAD_PAPA = 16
SQUAD_QUEBEC = 17
SQUAD_ROMEO = 18
SQUAD_SIERRA = 19
SQUAD_TANGO = 20
SQUAD_UNIFORM = 21
SQUAD_VICTOR = 22
SQUAD_WHISKEY = 23
SQUAD_XRAY = 24
SQUAD_YANKEE = 25
SQUAD_ZULU = 26
SQUAD_HAGGARD = 27
SQUAD_SWEETWATER = 28
SQUAD_PRESTON = 29
SQUAD_REDFORD = 30
SQUAD_FAITH = 31
SQUAD_CELESTE  = 32
 
SQUAD_NAMES = {
    SQUAD_ALPHA: "Alpha",
    SQUAD_BRAVO: "Bravo",
    SQUAD_CHARLIE: "Charlie",
    SQUAD_DELTA: "Delta",
    SQUAD_ECHO: "Echo",
    SQUAD_FOXTROT: "Foxtrot",
    SQUAD_GOLF: "Golf",
    SQUAD_HOTEL: "Hotel",
    SQUAD_INDIA: "India",
    SQUAD_JULIET: "Juliet",
    SQUAD_KILO: "Kilo",
    SQUAD_LIMA: "Lima",
    SQUAD_MIKE: "Mike",
    SQUAD_NOVEMBER: "November",
    SQUAD_OSCAR: "Oscar",
    SQUAD_PAPA: "Papa",
    SQUAD_QUEBEC: "Quebec",
    SQUAD_ROMEO: "Romeo",
    SQUAD_SIERRA: "Sierra",
    SQUAD_TANGO: "Tango",
    SQUAD_UNIFORM: "Uniform",
    SQUAD_VICTOR: "Victor",
    SQUAD_WHISKEY: "Whiskey",
    SQUAD_XRAY: "Xray",
    SQUAD_YANKEE: "Yankee",
    SQUAD_ZULU: "Zulu",
    SQUAD_HAGGARD: "Haggard",
    SQUAD_SWEETWATER: "Sweetwater",
    SQUAD_PRESTON: "Preston",
    SQUAD_REDFORD: "Redford",
    SQUAD_FAITH: "Faith",
    SQUAD_CELESTE: "Celeste"
}
 
GAME_MODES_NAMES = {
    "ConquestLarge0": "Conquest64",
    "ConquestSmall0": "Conquest",
    "ConquestAssaultLarge0": "Conquest Assault64",
    "ConquestAssaultSmall0": "Conquest Assault",
    "ConquestAssaultSmall1": "Conquest Assault alt.2",
    "RushLarge0": "Rush",
    "SquadRush0": "Squad Rush",
    "SquadDeathMatch0": "Squad Deathmatch",
    "TeamDeathMatch0": "Team Deathmatch",
    "Domination0": "Conquest Domination",
    "GunMaster0": "Gun master",
    "TeamDeathMatchC0": "TDM Close Quarters",
    "TankSuperiority0": "Tank Superiority",
    "Scavenger0": "Scavenger",
    "AirSuperiority0": "Air Superiority",
    "CaptureTheFlag0": "Capture the Flag",
}
 
GAMEMODES_IDS_BY_NAME = dict()
for _id, name in GAME_MODES_NAMES.items():
    GAMEMODES_IDS_BY_NAME[name.lower()] = _id
 
MAP_NAME_BY_ID = {
    'MP_001': 'Grand Bazaar',
    'MP_003': 'Tehran Highway',
    'MP_007': 'Caspian Border',
    'MP_011': 'Seine Crossing',
    'MP_012': 'Operation Firestorm',
    'MP_013': 'Damavand Peak',
    'MP_017': 'Noshahar Canals',
    'MP_018': 'Kharg Island',
    'MP_Subway': 'Operation Metro',
    'XP1_001': 'Strike At Karkand',
    'XP1_002': 'Gulf of Oman',
    'XP1_003': 'Sharqi Peninsula',
    'XP1_004': 'Wake Island',
    "XP2_Factory": "Scrapmetal",
    "XP2_Office": "Operation 925",
    "XP2_Palace": "Donya Fortress",
    "XP2_Skybar": "Ziba Tower",
    "XP3_Desert": "Bandar Desert",
    "XP3_Alborz": "Alborz Mountains",
    "XP3_Shield": "Armored Shield",
    "XP3_Valley": "Death Valley",
    "XP4_Quake": "Epicenter",
    "XP4_FD": "Markaz Monolith",
    "XP4_Parl": "Azadi Palace",
    "XP4_Rubble": "Talah market",
    "XP5_001": "Operation Riverside",
    "XP5_002": "Nebandan Flats",
    "XP5_003": "Kiasar Railroad",
    "XP5_004": "Sabalan Pipeline",
}
 
MAP_ID_BY_NAME = dict()
for _id, name in MAP_NAME_BY_ID.items():
    MAP_ID_BY_NAME[name.lower()] = _id
 
GAME_MODES_BY_MAP_ID = {
    "MP_001": ("ConquestLarge0", "ConquestSmall0", "RushLarge0", "SquadRush0", "SquadDeathMatch0", "TeamDeathMatch0"),
    "MP_003": (
        "ConquestLarge0", "ConquestSmall0", "RushLarge0", "SquadRush0", "SquadDeathMatch0", "TeamDeathMatch0"),
    "MP_007": (
        "ConquestLarge0", "ConquestSmall0", "RushLarge0", "SquadRush0", "SquadDeathMatch0", "TeamDeathMatch0"),
    "MP_011": (
        "ConquestLarge0", "ConquestSmall0", "RushLarge0", "SquadRush0", "SquadDeathMatch0", "TeamDeathMatch0"),
    "MP_012": (
        "ConquestLarge0", "ConquestSmall0", "RushLarge0", "SquadRush0", "SquadDeathMatch0", "TeamDeathMatch0"),
    "MP_013": (
        "ConquestLarge0", "ConquestSmall0", "RushLarge0", "SquadRush0", "SquadDeathMatch0", "TeamDeathMatch0"),
    "MP_017": (
        "ConquestLarge0", "ConquestSmall0", "RushLarge0", "SquadRush0", "SquadDeathMatch0", "TeamDeathMatch0"),
    "MP_018": (
        "ConquestLarge0", "ConquestSmall0", "RushLarge0", "SquadRush0", "SquadDeathMatch0", "TeamDeathMatch0"),
    "MP_Subway": (
        "ConquestLarge0", "ConquestSmall0", "RushLarge0", "SquadRush0", "SquadDeathMatch0", "TeamDeathMatch0"),
    "XP1_001": (
        "ConquestAssaultLarge0", "ConquestAssaultSmall0", "ConquestAssaultSmall1", "RushLarge0", "SquadRush0",
        "SquadDeathMatch0", "TeamDeathMatch0"),
    "XP1_002": ("ConquestLarge0", "ConquestSmall0", "ConquestAssaultSmall0", "RushLarge0", "SquadRush0",
                "SquadDeathMatch0", "TeamDeathMatch0"),
    "XP1_003": ("ConquestAssaultLarge0", "ConquestAssaultSmall0", "ConquestAssaultSmall1", "RushLarge0", "SquadRush0",
                "SquadDeathMatch0", "TeamDeathMatch0"),
    "XP1_004": ("ConquestAssaultLarge0", "ConquestAssaultSmall0", "ConquestAssaultSmall1", "RushLarge0", "SquadRush0",
                "SquadDeathMatch0", "TeamDeathMatch0"),
    "XP2_Factory": ("TeamDeathMatchC0", "GunMaster0", "Domination0", "SquadDeathMatch0"),
    "XP2_Office": ("TeamDeathMatchC0", "GunMaster0", "Domination0", "SquadDeathMatch0"),
    "XP2_Palace": ("TeamDeathMatchC0", "GunMaster0", "Domination0", "SquadDeathMatch0"),
    "XP2_Skybar": ("TeamDeathMatchC0", "GunMaster0", "Domination0", "SquadDeathMatch0"),
    "XP3_Desert": (
        "ConquestLarge0", "ConquestSmall0", "RushLarge0", "SquadRush0", "SquadDeathMatch0", "TeamDeathMatch0",
        "TankSuperiority0"),
    "XP3_Alborz": (
        "ConquestLarge0", "ConquestSmall0", "RushLarge0", "SquadRush0", "SquadDeathMatch0", "TeamDeathMatch0",
        "TankSuperiority0"),
    "XP3_Shield": (
        "ConquestLarge0", "ConquestSmall0", "RushLarge0", "SquadRush0", "SquadDeathMatch0", "TeamDeathMatch0",
        "TankSuperiority0"),
    "XP3_Valley": (
        "ConquestLarge0", "ConquestSmall0", "RushLarge0", "SquadRush0", "SquadDeathMatch0", "TeamDeathMatch0",
        "TankSuperiority0"),
    "XP4_Quake": (
        "ConquestLarge0", "ConquestSmall0", "RushLarge0", "SquadRush0", "SquadDeathMatch0", "TeamDeathMatch0",
        "GunMaster0", "Scavenger0"),
    "XP4_FD": (
        "ConquestLarge0", "ConquestSmall0", "RushLarge0", "SquadRush0", "SquadDeathMatch0", "TeamDeathMatch0",
        "GunMaster0", "Scavenger0"),
    "XP4_Parl": (
        "ConquestLarge0", "ConquestSmall0", "RushLarge0", "SquadRush0", "SquadDeathMatch0", "TeamDeathMatch0",
        "GunMaster0", "Scavenger0"),
    "XP4_Rubble": (
        "ConquestLarge0", "ConquestSmall0", "RushLarge0", "SquadRush0", "SquadDeathMatch0", "TeamDeathMatch0",
        "GunMaster0", "Scavenger0"),
    "XP5_001": (
        "CaptureTheFlag0", "AirSuperiority0", "ConquestLarge0", "ConquestAssaultLarge0", "ConquestSmall0",
        "ConquestAssaultSmall0", "RushLarge0", "SquadRush0", "SquadDeathMatch0", "TeamDeathMatch0"),
    "XP5_002": (
        "CaptureTheFlag0", "AirSuperiority0", "ConquestLarge0", "ConquestAssaultLarge0", "ConquestSmall0",
        "ConquestAssaultSmall0", "RushLarge0", "SquadRush0", "SquadDeathMatch0", "TeamDeathMatch0"),
    "XP5_003": (
        "CaptureTheFlag0", "AirSuperiority0", "ConquestLarge0", "ConquestAssaultLarge0", "ConquestSmall0",
        "ConquestAssaultSmall0", "RushLarge0", "SquadRush0", "SquadDeathMatch0", "TeamDeathMatch0"),
    "XP5_004": (
        "CaptureTheFlag0", "AirSuperiority0", "ConquestLarge0", "ConquestAssaultLarge0", "ConquestSmall0",
        "ConquestAssaultSmall0", "RushLarge0", "SquadRush0", "SquadDeathMatch0", "TeamDeathMatch0"),
 
}
 
GUNMASTER_WEAPONS_PRESET_BY_INDEX = [
    ["Standard Weapon list", ["MP443", "M93", "T44", "PP-19", "P90", "SPAS-12", "MK3A1 Flechette", "ACW-R", "MTAR", "AUG", "SCAR-L", "LSAT", "L86", "M417", "JNG-90", "M320 LVG", "Knife"]],
    ["Standard Weapon list REVERSED", ["JNG-90", "M417", "L86", "LSAT", "SCAR-L", "AUG", "MTAR", "ACW-R", "MK3A1 Flechette", "SPAS-12", "P90", "PP-19", "T44", "M93", "MP443", "M320 LVG, Knife"]],
    ["Light Weight", ["M9", "Glock17", "M93", "870", "Saiga12", "Spas-12", "Dao-12", "M1014", "PP2000", "M5K", "P90", "MP7", "ASVal", "PP-19", "UMP45", "M320 GL", "Knife"]],
    ["Heavy Gear", ["MP412Rex", "T44", "SPAS-12 Slugs", "MK3A3 slugs", "AK47M", "F2000", "G3A3", "FAMAS", "SCAR-L", "SteyrAug", "M249", "M60", "QBB-95", "MG36", "LSAT, C4, Knife"]],
    ["Pistol run!", ["M9", "MP443", "G17c", "M9 Suppressed", "G17 Suppressed", "M1911", "Glock18", "M93", "MP12rex", "Taurus44", "Knife"]],
    ["Snipers Heaven", ["M9 Suppressor", "Glock17 Suppressor", "M1911 Suppressor", "SVD", "SKS", "MK11", "QBU-88", "M417", "M40A5", "SV98", "L96", "JNG90", "M98B", "Crossbow Bolt, Knife"]],
    ["US arms race", ["M9", "M1911", "M870", "PDW-R", "M4A1", "M16", "M249", "M240", "MK11", "M40A5", "SMAW", "Knife"]],
    ["RU arms race", ["MP443", ".412 rex", "Saiga 12k", "PP-2000", "PP-19", "AS Val", "AKS-74u", "AK74M", "RPK-74", "SVD", "RPG-7", "Knife"]],
    ["EU arms race", ["G17", "M93R", "SPAS-12", "MP7", "UMP", "G36", "M416", "L85", "MG36", "M417", "M320 GL", "Knife"]],
    ]
 
GUNMASTER_WEAPONS_PRESET_BY_NAME = dict(GUNMASTER_WEAPONS_PRESET_BY_INDEX)
 
 
class Bf3Parser(AbstractParser):
    gameName = 'bf3'
 
    _gameServerVars = (
        '3dSpotting',
        '3pCam',
        'autoBalance',
        'bannerUrl',
        'bulletDamage',
        'friendlyFire',
        'gameModeCounter',
        'gamePassword',
        'hud',
        'idleBanRounds',
        'idleTimeout',
        'killCam',
        'killRotation',
        'maxPlayers',
        'minimap',
        'minimapSpotting',
        'nameTag',
        'onlySquadLeaderSpawn',
        'playerManDownTime',
        'playerRespawnTime',
        'ranked',
        'regenerateHealth',
        'roundLockdownCountdown',
        'roundRestartPlayerCount',
        'roundStartPlayerCount',
        'roundsPerMap',
        'serverDescription',
        'serverMessage',
        'serverName',
        'soldierHealth',
        'teamKillCountForKick',
        'teamKillKickForBan',
        'teamKillValueDecreasePerSecond',
        'teamKillValueForKick',
        'teamKillValueIncrease',
        'unlockMode',
        'vehicleSpawnAllowed',
        'vehicleSpawnDelay',
        'premiumStatus',
        'gunMasterWeaponsPreset'
    )
 
 
    def startup(self):
        AbstractParser.startup(self)
 
        # create the 'Server' client
        self.clients.newClient('Server', guid='Server', name='Server', hide=True, pbid='Server', team=b3.TEAM_UNKNOWN, squad=None)
 
        self.verbose('GameType: %s, Map: %s' %(self.game.gameType, self.game.mapName))
 
 
    def pluginsStarted(self):
        AbstractParser.pluginsStarted(self)
        self.info('connecting all players...')
        plist = self.getPlayerList()
        for cid, p in plist.iteritems():
            client = self.clients.getByCID(cid)
            if not client:
                #self.clients.newClient(playerdata['cid'], guid=playerdata['guid'], name=playerdata['name'], team=playerdata['team'], squad=playerdata['squad'])
                name = p['name']
                self.debug('client %s found on the server' % cid)
                client = self.clients.newClient(cid, guid=p['name'], name=name, team=p['teamId'], squad=p['squadId'], data=p)
                self.queueEvent(b3.events.Event(b3.events.EVT_CLIENT_JOIN, p, client))
 
 
 
 
    ###############################################################################################
    #
    #    Frostbite2 events handlers
    #    
    ###############################################################################################
 
    def OnPlayerTeamchange(self, action, data):
        """
        player.onTeamChange <soldier name: player name> <team: Team ID> <squad: Squad ID>
        Effect: Player might have changed team
        """
        # ['player.switchTeam', 'Cucurbitaceae', '1', '0']
        client = self.getClient(data[0])
        if client:
            client.squad = int(data[2])
            client.teamId = int(data[1])
            client.team = self.getTeam(data[1]) # .team setter will send team change event
 
 
    def OnPlayerSquadchange(self, action, data):
        """
        player.onSquadChange <soldier name: player name> <team: Team ID> <squad: Squad ID>    
 
        Effect: Player might have changed squad
        NOTE: this event also happens after a player left the game
        """
        client = self.clients.getByCID(data[0])
        if client:
            previous_squad = client.squad
            client.squad = int(data[2])
            client.teamId = int(data[1])
            client.team = self.getTeam(data[1]) # .team setter will send team change event
            if client.squad != previous_squad:
                return b3.events.Event(b3.events.EVT_CLIENT_SQUAD_CHANGE, data[1:], client)
 
 
    ###############################################################################################
    #
    #    B3 Parser interface implementation
    #    
    ###############################################################################################
 
    def getPlayerPings(self):
        """Ask the server for a given client's pings
        """
        pings = {}
        for c in self.clients.getList():
            try:
                words = self.write(("player.ping", c.cid))
                pings[c.cid] = int(words[0])
            except ValueError:
                pass
            except Exception, err:
                self.error("could not get ping info for player %s: %s" % (c, err), exc_info=err)
        return pings
 
    ###############################################################################################
    #
    #    Other methods
    #    
    ###############################################################################################
 
    def checkVersion(self):
        version = self.output.write('version')
        self.info('server version : %s' % version)
        if version[0] != 'BF3':
            raise Exception("the BF3 parser can only work with Battlefield 3")
        if int(version[1]) < BF3_REQUIRED_VERSION:
            raise Exception("the BF3 parser can only work with Battlefield 3 server version %s and above. You are tr"
                            "ying to connect to %s v%s" % (BF3_REQUIRED_VERSION, version[0], version[1]))
 
    def getClient(self, cid, guid=None):
        """Get a connected client from storage or create it
        B3 CID   <--> character name
        B3 GUID  <--> EA_guid
        """
        client = None
        if guid:
            # try to get the client from the storage of already authed clients by guid
            client = self.clients.getByGUID(guid)
        if not client:
            # try to get the client from the storage of already authed clients by name
            client = self.clients.getByCID(cid)
        if not client:
            if cid == 'Server':
                return self.clients.newClient('Server', guid='Server', name='Server', hide=True, pbid='Server', team=b3.TEAM_UNKNOWN, teamId=None, squadId=None)
            if guid:
                client = self.clients.newClient(cid, guid=guid, name=cid, team=b3.TEAM_UNKNOWN, teamId=None, squad=None)
            else:
                # must be the first time we see this client
                # query client info
                words = self.write(('admin.listPlayers', 'player', cid))
                pib = PlayerInfoBlock(words)
                if not len(pib):
                    self.debug('no such client found')
                    return None
                p = pib[0]
                if 'guid' in p:
                    cid = p['name']
                    name = p['name']
                    guid = p['guid']
                    teamId = p['teamId']
                    squadId = p['squadId']
                    client = self.clients.newClient(cid, guid=guid, name=name, team=self.getTeam(teamId), teamId=int(teamId), squad=squadId, data=p)
                    self.queueEvent(b3.events.Event(b3.events.EVT_CLIENT_JOIN, p, client))
        return client
 
 
    def getHardName(self, mapname):
        """ Change real name to level name """
        mapname = mapname.lower()
        try:
            return MAP_ID_BY_NAME[mapname]
        except KeyError:
            self.warning('unknown level name \'%s\'. Please make sure you have entered a valid mapname' % mapname)
            return mapname
 
 
    def getEasyName(self, mapname):
        """ Change levelname to real name """
        try:
            return MAP_NAME_BY_ID[mapname]
        except KeyError:
            self.warning('unknown level name \'%s\'. Please report this on B3 forums' % mapname)
            return mapname
 
 
    def getGameMode(self, gamemode_id):
        """ Convert game mode ID into human friendly name """
        if gamemode_id in GAME_MODES_NAMES:
            return GAME_MODES_NAMES[gamemode_id]
        else:
            self.warning("unknown gamemode \"%s\"" % gamemode_id)
            # fallback by sending gamemode id
            return gamemode_id
 
 
    def getGameModeId(self, gamemode_name):
        """ Get gamemode id by name """
        name = gamemode_name.lower()
        if name in GAMEMODES_IDS_BY_NAME:
            return GAMEMODES_IDS_BY_NAME[name]
        else:
            self.warning("unknown gamemode name \"%s\"" % gamemode_name)
            # fallback by sending gamemode id
            return gamemode_name
 
    def getSupportedMapIds(self):
        """return a list of supported levels for the current game mod"""
        # TODO : remove this method once the method on from AbstractParser is working
        return MAP_NAME_BY_ID.keys()
 
    def getSupportedGameModesByMapId(self, map_id):
        """return a list of supported game modes for the given map id"""
        return GAME_MODES_BY_MAP_ID[map_id]
 
    def getServerVars(self):
        """Update the game property from server fresh data"""
        def getCvar(cvar):
            try:
                return self.getCvar(cvar).getString()
            except Exception:
                pass
        def getCvarBool(cvar):
            try:
                return self.getCvar(cvar).getBoolean()
            except Exception:
                pass
        def getCvarInt(cvar):
            try:
                return self.getCvar(cvar).getInt()
            except Exception:
                pass
        def getCvarFloat(cvar):
            try:
                return self.getCvar(cvar).getFloat()
            except Exception:
                pass
        self.game['3dSpotting'] = getCvarBool('3dSpotting')
        self.game['3pCam'] = getCvarBool('3pCam')
        self.game['autoBalance'] = getCvarBool('autoBalance')
        self.game['bannerUrl'] = getCvar('bannerUrl')
        self.game['bulletDamage'] = getCvarInt('bulletDamage')
        self.game['friendlyFire'] = getCvarBool('friendlyFire')
        self.game['gameModeCounter'] = getCvarInt('gameModeCounter')
        self.game['gamePassword'] = getCvar('gamePassword')
        self.game['hud'] = getCvarBool('hud')
        self.game['idleBanRounds'] = getCvarInt('idleBanRounds')
        self.game['idleTimeout'] = getCvarInt('idleTimeout')
        self.game['killCam'] = getCvarBool('killCam')
        self.game['killRotation'] = getCvarBool('killRotation')
        self.game['maxPlayers'] = getCvarInt('maxPlayers')
        self.game['minimap'] = getCvarBool('minimap')
        self.game['minimapSpotting'] = getCvarBool('minimapSpotting')
        self.game['nameTag'] = getCvarBool('nameTag')
        self.game['onlySquadLeaderSpawn'] = getCvarBool('onlySquadLeaderSpawn')
        self.game['playerManDownTime'] = getCvarInt('playerManDownTime')
        self.game['playerRespawnTime'] = getCvarInt('playerRespawnTime')
        self.game['ranked'] = getCvarBool('ranked')
        self.game['regenerateHealth'] = getCvarBool('regenerateHealth')
        self.game['roundLockdownCountdown'] = getCvarInt('roundLockdownCountdown')
        self.game['roundRestartPlayerCount'] = getCvarInt('roundRestartPlayerCount')
        self.game['roundStartPlayerCount'] = getCvarInt('roundStartPlayerCount')
        self.game['roundsPerMap'] = getCvarInt('roundsPerMap')
        self.game['serverDescription'] = getCvar('serverDescription')
        self.game['serverMessage'] = getCvar('serverMessage')
        self.game['serverName'] = getCvar('serverName')
        self.game['soldierHealth'] = getCvarInt('soldierHealth')
        self.game['teamKillCountForKick'] = getCvarInt('teamKillCountForKick')
        self.game['teamKillKickForBan'] = getCvarInt('teamKillKickForBan')
        self.game['teamKillValueDecreasePerSecond'] = getCvarFloat('teamKillValueDecreasePerSecond')
        self.game['teamKillValueForKick'] = getCvarFloat('teamKillValueForKick')
        self.game['teamKillValueIncrease'] = getCvarFloat('teamKillValueIncrease')
        self.game['unlockMode'] = getCvar('unlockMode')
        self.game['vehicleSpawnAllowed'] = getCvarBool('vehicleSpawnAllowed')
        self.game['vehicleSpawnDelay'] = getCvarInt('vehicleSpawnDelay')
        self.game['premiumStatus'] = getCvarBool('premiumStatus')
        self.game['gunMasterWeaponsPreset'] = getCvarInt('gunMasterWeaponsPreset')
        self.game.timeLimit = self.game.gameModeCounter
        self.game.fragLimit = self.game.gameModeCounter
        self.game.captureLimit = self.game.gameModeCounter
 
 
    def getServerInfo(self):
        """query server info, update self.game and return query results
        Response: OK,serverName,numPlayers,maxPlayers,level,gamemode,[teamscores],isRanked,hasPunkbuster,hasPassword,serverUptime,roundTime
        The first number in the [teamscore] component I listed is numTeams, followed by the score or ticket count for each team (0-4 items), 
        then the targetScore. (e.g. in TDM/SQDM this is the number of kills to win)
        So when you start a Squad Deathmatch round with 50 kills needed to win, it will look like this:
        4,0,0,0,0,50
 
        """
        data = self.write(('serverInfo',))
        data2 = Bf3Parser.decodeServerinfo(data)
        self.debug("decoded server info : %r" % data2)
        self.game.sv_hostname = data2['serverName']
        self.game.sv_maxclients = int(data2['maxPlayers'])
        self.game.mapName = data2['level']
        self.game.gameType = data2['gamemode']
        if 'gameIpAndPort' in data2 and data2['gameIpAndPort']:
            try:
                self._publicIp, self._gamePort = data2['gameIpAndPort'].split(':')
            except ValueError:
                pass
        self.game.serverinfo = data2
        return data
 
    def getTeam(self, team):
        """convert team numbers to B3 team numbers"""
        team = int(team)
        if team == 1:
            return b3.TEAM_RED
        elif team == 2:
            return b3.TEAM_BLUE
        elif team == 3:
            return b3.TEAM_SPEC
        else:
            return b3.TEAM_UNKNOWN
 
    @staticmethod
    def decodeServerinfo(data):
        """
        <serverName: string> <current playercount: integer> <max playercount: integer> <current gamemode: string>
        <current map: string> <roundsPlayed: integer> <roundsTotal: string> <scores: team scores>
        <onlineState: online state> <ranked: boolean> <punkBuster: boolean> <hasGamePassword: boolean>
        <serverUpTime: seconds> <roundTime: seconds>
 
        ['BigBrotherBot #2', '0', '16', 'ConquestLarge0', 'MP_012', '0', '2', '2', '300', '300', '0', '', 'true', 'true', 'false', '5148', '455']
 
        """
        numOfTeams = 0
        if data[7] != '':
            numOfTeams = int(data[7])
 
        response = {
            'serverName': data[0],
            'numPlayers': data[1],
            'maxPlayers': data[2],
            'gamemode': data[3],
            'level': data[4],
            'roundsPlayed': data[5],
            'roundsTotal': data[6],
            'numTeams': data[7],
            # depending on numTeams, there might be between 0 and 4 team scores here
            'team1score': None,
            'team2score': None,
            'team3score': None,
            'team4score': None,
            'targetScore': data[7+numOfTeams + 1],
            'onlineState': data[7+numOfTeams + 2],
            'isRanked': data[7+numOfTeams + 3],
            'hasPunkbuster': data[7+numOfTeams + 4],
            'hasPassword': data[7+numOfTeams + 5],
            'serverUptime': data[7+numOfTeams + 6],
            'roundTime': data[7+numOfTeams + 7],
            'gameIpAndPort': None,
            'punkBusterVersion': None,
            'joinQueueEnabled': None,
            'region': None,
            'closestPingSite': None,
            'country': None,
        }
        if numOfTeams >= 1:
            response['team1score'] = data[8]
        if numOfTeams >= 2:
            response['team2score'] = data[9]
        if numOfTeams >= 3:
            response['team3score'] = data[10]
        if numOfTeams == 4:
            response['team4score'] = data[11]
 
        # since BF3 R9
        new_info = 'gameIpAndPort', 'punkBusterVersion', 'joinQueueEnabled', 'region', 'closestPingSite', 'country'
        start_index = 7 + numOfTeams + 8
        for index, name in zip(range(start_index, start_index + len(new_info)), new_info):
            try:
                response[name] = data[index]
            except IndexError:
                pass
 
        return response