# SourceRPG release 2.0 by Steven Hartin  
# ./addons/eventscripts/sourcerpg/sourcerpg.py

# >>> To configure this addon please see config.cfg <<<
# >>> To configure the skills please see skills.cfg <<<

#################################
#### DO NOT EDIT THIS FILE, #####
####    IF YOU DO, THINGS   #####
####   THINGS COULD BREAK   #####
#################################

import os
import random
import time

from sqlite3 import dbapi2 as sqlite
from path import path as Path

""" Import the psyco module which improves speed """
import psyco
psyco.full()

import es
import popuplib
import langlib
import gamethread
import playerlib
import cfglib
import cmdlib

# Set the addon info data
revision = int(filter(str.isdigit, "$Revision$"))
info = es.AddonInfo()
info.name     = 'SourceRPG'
info.version  = '2.1.%03d' % revision
info.basename = 'sourcerpg'
info.author   = 'Freddukes AKA Pro Noob'
info.url      = 'http://addons.eventscripts.com/addons/view/%s' % info.basename

# Create the langlib function
text = lambda userid, textIdent, tokens = {}: "No strings.ini found in ../sourcerpg/"
textPath = os.path.join( es.getAddonPath(info.basename), "strings.ini" )
if os.path.isfile(textPath):
    text = langlib.Strings(textPath)

# Create the configuration file
basePath = Path(es.getAddonPath(info.basename))
cfgPath = Path(str(es.ServerVar("eventscripts_gamedir"))).joinpath("cfg")
sourcerpgCFG = cfgPath.joinpath("sourcerpg")
if not sourcerpgCFG.isdir():
    sourcerpgCFG.mkdir()
     
skillLoader = basePath.joinpath("skill_loader.cfg")
addonLoader = basePath.joinpath("addon_loader.cfg")
configFile = basePath.joinpath("config.cfg")
skillConfig = basePath.joinpath("skills.cfg")

if skillLoader.exists():
    skillLoader.copy(sourcerpgCFG.joinpath("skill_loader.cfg"))
    skillLoader.remove()

if addonLoader.exists():
    addonLoader.copy(sourcerpgCFG.joinpath("addon_loader.cfg"))
    addonLoader.remove()
    
if configFile.exists():
    configFile.copy(sourcerpgCFG.joinpath("config.cfg"))
    configFile.remove()

if skillConfig.exists():
    skillConfig.copy(sourcerpgCFG.joinpath("skills.cfg"))
    skillConfig.remove()  

configPath = sourcerpgCFG.joinpath("config.cfg")
config = cfglib.AddonCFG(str(configPath))
config.text(info.name + " version " + info.version + " by " + info.author)
config.text("../addons/eventscripts/sourcerpg/config.cfg")
config.text(">>> To config the skills, see ../sourcerpg/skills.cfg <<<")

config.text("""
// ****************************
// ****************************
// ***** MESSAGE SETTINGS *****
// ****************************
// ****************************""", False)
debugMode     = config.cvar('srpg_debugMode', 0, "The level of debug. The higher the level the more SPAM in console and laggier it is. 0 = disable")
prefix        = config.cvar('srpg_prefix', "[SourceRPG]", "The prefix of the script (will appear before every text message)")
announceXp    = config.cvar('srpg_announceXp', 1, "Whether or not a player is told their XP and level each time they gain experience")
spawnAnnounce = config.cvar('srpg_announceOnSpawn', 1, "Whether or not a player is informed about their xp and level status upon spawn")
levelUp       = config.cvar('srpg_levelUp', 1, "Whether or not the server will be notified about a player leveling up")

config.text("""
// ****************************
// ****************************
// ******** XP SETTINGS *******
// ****************************
// ****************************""", False)
unfairAdvantage = config.cvar('srpg_disableUnfairAdvantageXp', 1, "If there are 0 enemies on the opposite team, enabling this stops players from achieving experience")
killXp          = config.cvar('srpg_killXp', 15, 'The amount of experience gained from a kill multiplied by the victim\'s level')
headshotXp      = config.cvar('srgp_headshotXp',      50, 'The amount of additional xp received for a headshot kill')
bombPlantXp     = config.cvar('srpg_bombPlantXp',     25, 'The amount of experience received from planting the bomb multiplied by the user\'s level')
bombExplodeXp   = config.cvar('srpg_bombExplodeXp',   25, 'The amount of experience received from waiting until the bomb exploded multiplied by the user\'s level')
bombDefuseXp    = config.cvar('srpg_bombDefuseXp',    25, 'The amount of experience received from defusing the bomb')
hostageFollowXp = config.cvar('srpg_hostageFollowXp', 25, 'The amount of experience received by making a hostage follow you (per hostage) multiplied by the user\'s level')
hostageRescueXp = config.cvar('srpg_hostageRescueXp', 25, 'The amount of experience received by rescuing a hostage (per hostage) multiplied by the user\'s level')
startXp         = config.cvar('srpg_startXp', 250, 'The amount of experience needed to level up the first time')
xpIncrement     = config.cvar('srpg_xpIncrement',    50, 'After starting, this is the incremental amount of experience from level to level')
startCredits    = config.cvar('srpg_startCredits',    5, 'The amount of credits players begin with')
creditsReceived = config.cvar('srpg_creditsReceived', 5, 'The amount of credits received each level up')
sellPercentage  = config.cvar('srpg_sellPercentage', 75, 'The percentage of credits received when selling credits')

damageXp        = config.cvar('srpg_damageXp', 1.0, 'The amount of experience gained for each point of damage dealt')

config.text("""
// This section will be for adding specific weapons to the damage table.
// If the weapon is not specified here, then each damage dealt with that weapon
// will be damageXp. For example, if you want every damage with a knife
// to gain you 8 experience, then you can specify that here. You can also
// specify the amount of additional experience gained from a kill with this
// weapon. For example if you wanted a knife to gain you 8 experience for
// each damage dealt, and an additonal 50 experience for killing a player,
// the command would be as follows:
//
// srpg addweaponxp knife 8 50
//
// The last parameter (the kill xp) is optional so you can ommit it if you wish.
//
// srpg addweaponxp <weapon name> <amount of xp per damage dealt> [optional: amount of xp per kill]
\
""", False)
config.command("srpg")
config.text("""\n// End of specific weapon commands here""", False)

config.text("""
// ****************************
// ****************************
// ***** GENERAL SETTINGS *****
// ****************************
// ****************************""", False)
maxLevel       = config.cvar('srpg_maxLevel', 999, "The maximum level possible before a player can no longer achieve more experience, 0 to disable")
maxLevelReset  = config.cvar('srpg_maxLevelReset', 1, "When a player reaches the maximum level, will their stats be reset?")
serverMaxLevel = config.cvar('srpg_serverMaxLevel', 9990, "When the top 10 players total levels add together to make this level, the servers database will reset. 0 to disable")
popupStatus    = config.cvar('srpg_popupStatus', 1, "Whether or not a player\'s popup to upgrade their stats will appear when they level up")
inactivityCounter = config.cvar('srpg_inactivityCounter', 7, "The amount of days a player can be inactive before they are removed from the database")

config.text("""
// ****************************
// ****************************
// ****** SOUND SETTINGS ******
// ****************************
// ****************************""", False)
skillUpgradeSound   = config.cvar('srpg_skillUpgradeSound',   "ambient/machines/machine1_hit2.wav", 'The sound played when a player upgrades a skill, use "" for no sound')
skillDowngradeSound = config.cvar('srpg_skillDowngradeSound', "vehicles/apc/apc_shutdown.wav", "The sound played when a player downgrades a skill, use \"\" to disable")
levelupSound        = config.cvar('srpg_levelupSound',        "ambient/energy/whiteflash.wav", "The sound played when a player levels up, use \"\" to disable")

config.text("""
// ****************************
// ****************************
// **** DATABASE SETTINGS *****
// ****************************
// ****************************""", False)
saveType   = config.cvar('srpg_saveType', "round end", "\"round end\" or \"intervals\". Use \"intervals\" for a deathmatch server (see below) otherwise use \"round end\"")
saveLength = config.cvar('srpg_saveLength', 120, "This only applies if saveType is set to intervals. This is the amount of time in seconds that the database will save.")

config.text("""
// ****************************
// ****************************
// ******* TURBO MODE *********
// ****************************
// ****************************""", False)
turboMode = config.cvar('srpg_turboMode', 0, "Whether or not turbo mode is enabled")
turboModeAnnounce = config.cvar('srpg_turboModeAnnounce', 1, "If turbo mode is on, will a message be sent at spawn tell them about it")
turboXpMultiplier = config.cvar('srpg_turboXpMultiplier', 3, "The amount that the experience is multiplied by in turbo mode")
turboCreditMultiplier = config.cvar('srpg_turboCreditMultiplier', 2, "The amount that the credits gained are multiplied by in turbo mode.")

config.text("""
// ****************************
// ****************************
// ********** BOTS ************
// ****************************
// ****************************""", False)
botMaxLevel = config.cvar('srpg_botMaxLevel', 500, "The maximum level of bots, 0 to not have a maximum level")
botsGetXpVsHumans = config.cvar('srpg_botsGetXpVsHumans', 1, "Do bots receive experience when attacking other humans?")
humansGetXpVsBots = config.cvar('srpg_humansGetXpVsBots', 1, "Do humans receive experience when attacking other bots?")
botsGetXpVsBots   = config.cvar('srpg_botsGetXpVsBots',   1, "Do bots receive experience when attacking other bots?")
botsGetXpOnEvents = config.cvar('srpg_botsGetXpOnEvents', 1, "Do bots receive experience on events such as bomb plant and bomb defuse etc?")

config.write()
#config.execute()

# Make Public Variable
es.ServerVar('sourcerpg', info.version, 'SourceRPG Version - Made by Freddukes').makepublic()
turboMode.makepublic()

""" Exceptions """
class DatabaseError(Exception):
    """ Executed when we encounter a logic run time error with the database class """
    pass
    
class PlayerError(Exception):
    """ Executed when we encounter a logic run time error with the player class """
    pass
    
class SkillError(Exception):
    """ Executed when we encounter a logic run time error with the skill class """
    pass
    
class AddonError(Exception):
    """ Executed when we encounter a logic run time error with the addon class """
    pass
    
""" Database class """

class SQLiteManager(object):
    """
    This class manages all database connections. The main idea of this object
    is to create an easy interface so we can easilly and safely query values
    from a database witout worrying about convulent and complex sqlite syntax.
    
    This interface allows us to safely call and remove objects from the database
    so that users do not have to access the database directly, but through
    a simple API structure.
    
    We should only have one database instance so it is unnecesarry to create
    another object to hold the instances of this object.
    """
    skills  = []
    players = []
    def __init__(self, path):
        """
        Default Constructor
        
        Initialize all the default values and open a connection with the SQLite
        database. Create the tables if they don't exist.
        
        @PARAM path - the absolute path of the database 
        """
        debug.write('[SourceRPG] SQLiteManager constructor, path: %s' % path, 0, True)
        self.path       = path 
        self.connection = sqlite.connect(path)
        debug.write('[SourceRPG] SQLite Connected', 0, True)
        self.connection.text_factory = str
        self.cursor     = self.connection.cursor()
        
        self.cursor.execute("PRAGMA journal_mode=OFF")
        self.cursor.execute("PRAGMA locking_mode=EXCLUSIVE")
        self.cursor.execute("PRAGMA synchronous=OFF")
        
        """ Create the table to hold the players global stats """
        self.cursor.execute("""\
CREATE TABLE IF NOT EXISTS Player (
    UserID  INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
    steamid VARCHAR(30) NOT NULL, 
    level   INTEGER DEFAULT 1, 
    xp      INTEGER DEFAULT 0, 
    credits INTEGER DEFAULT 5, 
    popup   INTEGER DEFAULT 1, 
    name    VARCHAR(30) DEFAULT 'default', 
    lastconnected TEXT
)""")
        
        """ Create the table to hold a link between a row id and a list of skill names """
        self.cursor.execute("""\
CREATE TABLE IF NOT EXISTS Skill (
    SkillID INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, 
    name    VARCHAR(30) NOT NULL,
    UserID  INTEGER NOT NULL,
    level   INTEGER DEFAULT 0
)""")
        
        self.cursor.execute("CREATE INDEX IF NOT EXISTS StatIndex   ON Skill(UserID);")
        self.cursor.execute("CREATE INDEX IF NOT EXISTS PlayerIndex ON Player(SteamID);")
        debug.write('[SourceRPG] All tables created', 0, True)
        
    def __del__(self):
        """
        Default deconstructor.
        
        Executed when a database instance is destroyed. Ensure that the database
        is saved and closed.
        """
        self.save()
        self.close()
        
    def __contains__(self, key):
        """
        Executed automatically when we attempt to test to see if an element exists
        within the database.
        
        @PARAM key - The value to test for validity
        @RETURN boolean - Whether or not the value exists
        """
        key = str(key)
        if key in self.players:
            return True
        self.execute("SELECT level FROM Player WHERE steamid=?", key)
        result = self.cursor.fetchone()
        if bool( result ):
            self.players.append(key)
            return True
        return False
        
    def __iter__(self):
        """
        Executed automatically when we attempt to iterate through an instance
        of this class. Query all the steamids from the playerstats table and
        yield each steamid as a seperate object.
        
        @RETURN yield object - string objects which represent player's steamids
        """
        self.execute("SELECT steamid FROM Player")
        for steamid in self.cursor.fetchall():
            yield steamid[0]
        
    def execute(self, parseString, *args):
        """
        A wrapper function to simulate the execute() method of a cursor object.
        
        @PARAM parseString - the string query line of a SQLite statement
        """
        debug.write('[SourceRPG] Executing SQL String: %s' % parseString, 1, True)
        self.cursor.execute(parseString, args)
        debug.write('[SourceRPG] SQL String executed successfully', 2, True)
        
    def addPlayer(self, steamid, name):
        """
        Add a player to the database and ensure that they exist within the
        playerstats table as well as the playerskills table.
        
        @PARAM steamid - the string value of a players steamid
        @PARAM name - the string value of a players name
        """
        self.execute("INSERT INTO Player (steamid, popup, credits, name, lastconnected) VALUES (?,?,?,?,?)", steamid, int(popupStatus), int(startCredits), name, int(time.time()))
        return self.cursor.lastrowid
            
    def getUserIdFromSteamId(self, steamId):
        """
        Return the UserID auto increment value from a given steamid
        
        @PARAM steamId - the steamid of the player
        @RETURN INTEGER - the row, if the user doesn't exist, then return None
        """
        self.execute("SELECT UserID FROM Player WHERE steamid=?", steamId)
        value = self.cursor.fetchone()
        if value is None:
            return None
        return value[0]
            
    def addSkillIntoPlayerDatabase(self, userid, name, level = 0):
        """
        Adds a new skill into a players database with a certain base level
        
        @PARAM steamid - the steamid of the player who you wish to insert the skill for
        @PARAM name    - the name of the skill
        @PARAM level   - the level that the user starts off with
        @RETURN integer - the SkillID.
        """
        if not isinstance(userid, int):
            userid = self.getUserIdFromSteamId(userid)
        self.execute("INSERT OR IGNORE INTO Skill (UserID, name, level) VALUES (?,?,?)", userid, name, level)
        return self.cursor.lastrowid
        
    def updateSkillForPlayer(self, userid, name, level):
        """
        Updates the skill for a player to a new level
        
        @PARAM userid - the userid database int for the player who you wish to insert the skill for
        @PARAM name   - the name of the skill
        @PARAM level  - the new level of the skill
        """
        if not isinstance(userid, int):
            userid = self.getUserIdFromSteamId(userid)
        self.execute("UPDATE Skill SET level=? WHERE UserID=? AND name=?", level, userid, name)
            
    def checkPlayerSkillExists(self, userid, skillName):
        """
        Checks to see if a player has a level instance for the a certain skill
        
        @PARAM userid    - the integer userid of a player to check
        @PARAM skillName - the string name of the skill to check
        @RETURN boolean  - whether or not the skill exists for the player
        """
        if not isinstance(userid, int):
            userid = self.getUserIdFromSteamId(userid)
        self.execute("SELECT level FROM Skill WHERE UserID=? AND name=?", userid, skillName)
        return bool( self.fetchone())
        
    def getPlayerStat(self, userid, statType):
        """
        Returns a players attribute from the playerstats table.
        
        @PARAM userid   - the integer userid of the player your wish to check
        @PARAM statType - the column name of the value you wish to return
        @RETURN object  - returns an object type of the value to which statType returns
        """
        if not isinstance(userid, int):
            userid = self.getUserIdFromSteamId(userid)
        statType = str(statType).replace("'", "''")
        if hasattr(statType, "__len__"):
            query = "SELECT " + ",".join( map( str, statType)) + " FROM Player WHERE UserID=?"
        else:
            query = "SELECT " + str( statType ) + " FROM Player WHERE UserID=?"
        self.execute(query, userid)
        return self.fetchone()
        
    def getSkillLevel(self, userid, skillName):
        """
        Returns a players level of a certain skill
        
        @PARAM steamid   - the integer representation of a players userid
        @PARAM skillName - the string name of a certain skill
        @RETURN integer  - an integral value which represents the level of the players skill
        """
        if not isinstance(userid, int):
            userid = self.getUserIdFromSteamId(userid)

        self.execute("SELECT level FROM Skill WHERE UserID=? AND name=?",
                                userid, skillName)
        value = self.cursor.fetchone()
        if value is None:
            return None
        return value[0]
        
    def update(self, table, primaryKeyName, primaryKeyValue, options):
        """
        Sets certain values to a new amount referenced by a table, primary key
        and primary key value
        
        @PARAM table - the table of which this should take place
        @PARAM primaryKeyName - the name of the key you wish to test for equality
        @PARAM primaryKeyValue - the options to update where the primaryKeyName's value equates to this value
        @PARAM option - dictionary of values, the key is the row to update, the value is the new amount to set the value to
        """
        keys = ""
        if not isinstance(options, dict):
            raise ValueError, "Expected 'options' argument to be a dictionary, instead received: %s" % type(options).__name__
        if options:
            for key, value in options.iteritems():
                if isinstance(key, str):
                    key   = key.replace("'", "''")

                if isinstance(value, str):
                    value = value.replace("'", "''")
                keys += "%s='%s'," % (key, value)
            keys = keys[:-1]
            query = "UPDATE " + str(table) + " SET " + keys + " WHERE " + str(primaryKeyName) + "='" + str(primaryKeyValue) + "'"
            self.execute(query)
        
        
    def increment(self, table, primaryKeyName, primaryKeyValue, options):
        """
        Increments certain values by a positive amount referenced by a 
        table, primary key and primary key value
        
        @PARAM table - the table of which this increment should take place
        @PARAM primaryKeyName - the name of the key you wish to test for equality
        @PARAM primaryKeyValue - the options to update where the primaryKeyName's value equates to this value
        @PARAM option - dictionary of values, the key is the row to update, the value is the amount to increment the current value by
        """
        keys = ""
        if not isinstance(options, dict):
            raise ValueError, "Expected 'options' argument to be a dictionary, instead received: %s" % type(options).__name__
        for key, value in options.iteritems():
            if isinstance(key, str):
                key   = key.replace("'", "''")
            if isinstance(value, str):
                value = value.replace("'", "''")
            keys += "%s=%s+%i," % (key, key, value)
        keys = keys[:-1]
        self.execute("UPDATE ? SET %s WHERE ?=?+?" % keys, table, primaryKeyName, primaryKeyName, primaryKeyValue)
        
    def query(self, table, primaryKeyName, primaryKeyValue, options):
        """
        Queries results from the table for one person and returns either a 
        tuple or a single value depending on the amount of values passed.
        
        @PARAM table - the table of which this query should take place
        @PARAM primaryKeyName - the name of the key you wish to test for equality
        @PARAM primaryKeyValue - the options to update where the primaryKeyName's value equates to this value
        @PARAM options - either a single value or a tuple which we will get the values of
        """
        
        """ Test if the value passed in options is of several values """
        if hasattr(options, "__len__"):
            query = "SELECT " + ",".join( map(lambda x: str(x).replace("'", "''"), options)) + " FROM " + table \
                    + " WHERE " + primaryKeyName + "='" + primaryKeyValue + "'"
        else:
            query = "SELECT " + str(options).replace("'", "''") + " FROM " + table + \
                    " WHERE " + primaryKeyName + "='" + primaryKeyValue + "'"
                    
        """ Execute the SQL statement and fetch the result """
        self.execute(query)
        return self.fetchone()
        
    def fetchall(self):
        """
        Mimics the sqlite fetchall method which recides within a cursor object.
        Ensures that the true values are added to a list so that we don't need
        to index it if the value is only one item in length (e.g. item instead
        of (item,)...)
        
        @RETURN list - attributes from which the query returned 
        """
        debug.write("[SourceRPG] Handling fetchall", 2)
        trueValues = []
        for value in self.cursor.fetchall():
            if isinstance(value, tuple):
                if len(value) > 1:
                    tempValues = []
                    for tempValue in value:
                        if isinstance(tempValue, long):
                            tempValue = int(tempValue)
                        tempValues.append(tempValue)
                    trueValues.append(tempValues)
                else:
                    if isinstance(value[0], long):
                        trueValues.append(int(value[0]))
                    else:
                        trueValues.append(value[0])
            else:
                if isinstance(value, long):
                    value = int(value)
                trueValues.append(value)
        for value in trueValues:
            debug.write("Result: %s" % value, 4)
        debug.write("[SourceRPG] Returning the result", 2)
        return trueValues
        
    def fetchone(self):
        """
        Mimics the sqlite fetchone method which recides within a cursor object.
        Ensures that a single value is returned from the cursor object if only
        one object exists within the tuple from the query, otherwise it returns
        the query result
        
        @RETURN Object - the result from the query command
        """
        debug.write("[SourceRPG] Fetching one object value", 2)
        result = self.cursor.fetchone()
        debug.write("The initial value is: %s" % (result,), 2)
        if hasattr(result, "__iter__"):
            debug.write("Attribute has iterable, test to see if there's only 1 value", 2)
            if len(result) == 1:
                trueResults = result[0]
                if isinstance(trueResults, long):
                    trueResults = int(trueResults)
                return trueResults
            
            else:
                debug.write("Single result attribute", 2)
                trueResults = []
                for trueResult in result:
                    if isinstance(trueResult, long):
                        trueResult = int(trueResult)
                    trueResults.append(trueResult)
                return trueResults

        if isinstance(result, long):
            result = int(result)
        debug.write("Result: %s" % result, 3)
        debug.write("[SourceRPG] Returning the value of the object", 2)
        return result    
        
    def save(self):
        """
        Commits the database to harddrive so that we can load it if the server
        closes.
        """
        debug.write("[SourceRPG] Handling SQL Save", 1)
        if self.path != ":memory:":
            debug.write("Path is not in memory", 2, False)
            if currentTurboMode is False:
                debug.write("We are not in turbo mode", 2, False)
                self.connection.commit()
        debug.write("[SourceRPG] SQL Save handled", 1)
        
    def clear(self, saveDatabase = True):
        """
        Deletes all instance within the tables of the databse.
        
        @PARAM saveDatabase - optional value, if True, it will commit the database
        """
        debug.write("[SourceRPG] Clearing database", 1)
        players.clearList()
        self.execute("DROP TABLE Player")
        self.execute("DROP TABLE Skill")
        if saveDatabase:
            self.save()
        self.__init__(self.path)
        for player in es.getUseridList():
            players.addPlayer(player)
        debug.write("[SourceRPG] Database cleared", 1)
        
    def close(self):
        """
        Closes the connections so that no further queries can be made.
        """
        debug.write("[SourceRPG] handling SQL close", 1)
        self.cursor.close()
        self.connection.close()
        debug.write("[SourceRPG] SQL close handled", 1)

""" Skills interface """

class SkillManager(object):
    """
    This class will be used as a singleton to keep control over the numerous
    skill objects. It allows us to keep a track over them, and to create
    generic functions to control, overlook and manage the skill objects. 
    """
    def __init__(self):
        """ Default constructor, initialize variables """
        self.skills = {}
        self.orderedSkills = []
    
    def __del__(self):
        """ Default deconstructor, clear the variables """
        self.clearList()
    
    def __getitem__(self, skillName):
        """
        Allows us index the singleton in a similar fashion to a dictionary object
        
        @PARAM skillName - the name of the skill object to return
        @RETURN SkillObject - the object of the skill whose name is skillName
        """
        return self.getSkill(skillName)
        
    def __delitem__(self, skillName):
        """
        Allows us to delete an instance from the instance by an index simulating
        removing the key
        
        @PARAM skillName - the name of the skill to remove
        """
        self.removeSkill(skillName)
    
    def __contains__(self, skillName):
        """
        Executed automatically when we test if a skillname is in the singleton:
        
        @RETURN bool, whether or not the skill is in
        """
        return bool(skillName in self.orderedSkills)
        
    def __iter__(self):
        """
        Executed automatically when we iterate through the singleton
        
        @RETURN SkillObject instances - all instances currently loaded
        """
        for skillName in self.orderedSkills:
            yield self.skills[skillName]
    
    def addSkill(self, skillName, maxLevel, creditStart, creditIncrement):
        """
        Add a SkillObject to the class dictionary referenced by the name
        
        @PARAM skillName - the string name of the skill
        @PARAM maxLevel - integral value of the maximum level of this skill
        @PARAM creditStart - integral value, how many credits to purchase level 1
        @PARAM creditIncrement - integral value, after level 1, how much to purshase all thereonafter
        """
        self.skills[skillName] = SkillObject(skillName, maxLevel, creditStart, creditIncrement)
        self.orderedSkills.append(skillName)
        
    def getSkill(self, skillName):
        """
        Returns a SkillObject() from the dictionary attribute if it exists
        
        @PARAM skillName - the string representation of the SkillObject instance
        @RETURN SkillObject
        """
        if self.__contains__(skillName):
            return self.skills[skillName]
        return None
        
    def removeSkill(self, skillName):
        """ 
        Removes a skill instance from the database attribute, 
        calls the SkillObject's deconstructor
        
        @PARAM skillName - the string name of the skill you wish to remove
        """
        if self.__contains__(skillName):
            del self.skills[skillName]
            del self.orderedSkills[self.orderedSkills.index(skillName)]
        
    def clearList(self):
        """ Loop through the dictionary attribute and remove each skill object """
        self.skills.clear()
        del self.orderedSkills[:]

class SkillObject(object):
    """
    This class stores all information about a skills object. This skill object
    will be a singleton in each of the skill's scripts and it gives access to
    skill relevenant information.
    """
    def __init__(self, skillName, maxLevel, creditStart, creditIncrement):
        """
        Default constructor, assign all skill variables here
        
        @PARAM skillName - the string name of the skill
        @PARAM maxLevel - integral value of the maximum level of this skill
        @PARAM creditStart - integral value, how many credits to purchase level 1
        @PARAM creditIncrement - integral value, after level 1, how much to purshase all thereonafter
        """
        self.name     = skillName
        self.maxLevel = maxLevel
        self.info     = None
        self.startCredit     = creditStart
        self.creditIncrement = creditIncrement
        
    def __str__(self):
        """ 
        Automatically executed upon string conversion.
        
        @RETURN string - the name of the skill
        """
        return self.name
    
    def config(self):
        """
        A wrapper function to return the skillConfig singleton
        
        @RETURN ConfigurationObeject - singleton
        """
        return skillConfig
        
class AddonManager(object):
    """
    A class to access and save multiple AddonObjects so we only have
    single instances which we can retrieve at any given point
    """
    def __init__(self):
        """ Default constructor - initialize the container. """
        self.addons = {}
        
    def __del__(self):
        """
        Default deconstructor - deconstruct the addons list, which in turn
        calls the deconstructor on all of the AddonObject's instances.
        """
        del self.addons
    
    def __iter__(self):
        """
        Iterate through the addon container instnace and yield each AddonObject
        instance.
        
        @RETURN yield object - each AddonInstance
        """
        for addon in self.addons.itervalues():
            yield addon
            
    def __contains__(self, addonName):
        """
        x in singleton <==> singleton.__contains__(x)
        Executed automatically when we test for the existance of an item within
        the singleton instance.
        
        @PARAM addonName - the name of the addon
        @RETURN boolean - whether or not the addon exists
        """
        return bool(addonName in self.addons)
        
    def __getitem__(self, addonName):
        """
        singleton[x] <==> singleton.__getitem__(x)
        Executed when we attempt to index the singleton. Return the AddonObject
        if the addon exists
        
        @PARAM addonName - the name of the addon
        @RETURN AddonObject instance - the instance which resides by the addon name
        """
        if self.__contains__(addonName):
            return self.addons[addonName]
        raise AddonError, "Addon %s does not exist" % addonName
        
    def __delitem__(self, addonName):
        """
        del singelton[x] <==> singleton.__delitem__(x)
        Executed when we attempt to remove an item from the singleton by indexing.
        In this case, we wish to remove the addonName from the addon container.
        
        @PARAM addonName - the string name of the addon
        """
        if not self.__contains__(addonName):
            raise AddonError, "Addon %s does not exist" % addonName
        del self.addons[addonName]
        
    def addAddon(self, addonName):
        """
        Insert the addon name into the addons container as an AddonObject instnace
        
        @PARAM addonName - the string name of the addon
        """
        if self.__contains__(addonName):
            raise AddonError, "Addon %s already exists" % addonName 
        self.addons[addonName] = AddonObject(addonName)
        
    def removeAddon(self, addonName):
        """
        This function is a wrapper for the __delitem__ method since they both
        naturally do the same thing.
        
        @PARAM addonName - the string name of the addon
        """
        self.__delitem__(addonName)
        
    def getAddon(self, addonName):
        """
        This function is a wrapper for the __getitem__ method since they both
        naturally do the same thing.
        
        @PARAM addonName - the string name of the addon 
        """
        return self.__getitem__(addonName)
        
    def clearList(self):
        """
        This function clears the list of the AddonObjects - it will call the
        deconstructor on each of the elements
        """
        self.addons.clear()
    
class AddonObject(object):
    """
    Since addons don't naturally modify the game automatically, at this
    current time, the only thing we'll have is a constructor. The reason
    I have left this class 'blank' is if in case we need to revisit the
    addon interface and add new default methods / attributes which can be shared
    throughout all of the addons - e.g. a single configuration file similar
    to the SkillObject class
    """
    def __init__(self, addonName):
        """ 
        Default constructor, register the addonName
        
        addonName - the string name of the addon
        """
        self.name = addonName

class PlayerManager(object):
    """
    This class acts as a container for all the PlayerObject instances but gives
    access to several other features such as the ability to iterate through
    and index the instance
    """
    def __init__(self):
        """ Default constructor, initialize the dictionary to hold the instances """
        self.players = {}
        
    def __getitem__(self, userid):
        """ 
        Automatically executed upon subscript. If the userid exists, return the
        PlayerObject instance
        
        @PARAM userid - the user that you wish to get the PlayerObject instance for
        @RETURN PlayerObject - the instance of that is relevant to the userid
        """
        userid = int(userid)
        if self.__contains__(userid):
            return self.players[userid]
        return None
        
        
    def __delitem__(self, userid):
        """
        del players[<userid>] <==> __delitem__(userid)
        
        Automatically executed when we attempt to delete an item from the
        container. Ensure that the userid object is removed
        
        @PARAM userid - the user to remove from the container
        """
        self.removePlayer(userid)
        
    def __iter__(self):
        """
        Automatically executed when returning all elements within a for loop.
        
        @RETURN yield object, all PlayerObject instance.
        """
        for player in self.players:
            yield self.players[player]
            
    def __contains__(self, userid):
        """
        Executed when testing for validity to see if this container contains
        a userid
        
        @PARAM userid - the user to test for appearance
        """
        userid = int(userid)
        return bool(userid in self.players)
        
    def addPlayer(self, userid):
        """
        A function to add a player into the container and the relevant
        PlayerObject instance as it's key value.
        
        @PARAM userid - the user to add to the container object 
        """
        self.players[int(userid)] = PlayerObject(userid)
        
    def removePlayer(self, userid):
        """
        Removes a user from the container within the class
        
        @PARAM userid - the userid to remove
        """
        userid = int(userid)
        if self.__contains__(userid):
            del self.players[userid].command 
            # we have to manually delete the underlying object so we have no other references to PlayerObject class.
            del self.players[userid] # calls deconstructor on PlayerObject class
            
    def getPlayer(self, userid):
        """
        Returns the player object referenced by the userid in the container
        
        @PARAM userid - the userid to return the PlayerObject instance of 
        """
        return self.__getitem__(userid)
        
    def clearList(self):
        """
        Removes all of the PlayerObjects from within the container, calls
        the destructor on each of the PlayerObject instances.
        """
        self.players.clear()
        
class PlayerObject(object):
    """ 
    This class manages all local aspects of the player. It allows us to keep a
    local copy of all the attributes from the database without creating new
    queries each time. It ensures that we can access the current values of the
    player's attributes and skills very safely and efficiently without modifying
    the database.
    """
    def __init__(self, userid):
        """
        Constructor:
        
        Stores the players userid and steamid then goes on to retrieve the 
        current values from the SQLite database and store them in the relevant
        dictionaries.
        
        @PARAM userid - the userid of the player
        """
        debug.write("[SourceRPG] PlayerObject constructor, userid %s" % userid, 1)
        self.userid   = int( userid )
        self.steamid  = playerlib.uniqueid( userid, True )
        self.name     = es.getplayername( userid )
        self.isbot    = es.isbot( userid )
        self.currentAttributes = {}
        self.oldAttributes     = {}
        self.currentSkills = {}
        self.oldSkills     = {}
        gamethread.delayed(0, self.setCommand)
        self.dbUserid = database.getUserIdFromSteamId(self.steamid)
        if self.dbUserid is None:
            self.dbUserid = database.addPlayer(self.steamid, self.name)
        self.update()
        self.playerAttributes = {}
        self.resetPlayerDefaultAttributes()
        debug.write("[SourceRPG] PlayerObject created for player: %s" % self.name, 1)
 
    def setCommand(self):
        """
        This function sets the command instance to hold a new CommandsDatabase
        instnace. We need to delay it by a tick so that this reference has been
        fully created before we use it.
        """
        self.command = CommandsDatabase(self.userid)
 
    def __del__(self):
        """
        Default deconstructor
        
        Ensure that the players stats are updated to the database when the player
        instance is removed.
        """
        debug.write("[SourceRPG] PlayerObject destructor for player %s (userid: %s)" % (self.name, self.userid), 1)
        self.commit()
        debug.write("[SourceRPG] PlayerObject successfully destroyed", 1)
        
    def __int__(self):
        """
        Automatically executed when an instance attemtps to be statically converted
        to a integer, return the integral representation of the players userid
        """
        return self.userid
        
    def __str__(self):
        """
        Automatically executed when an instance attemtps to be statically converted
        to a string, return the string representation of the players userid
        """
        return str(self.userid)
        
    def __getattr__(self, attribute):
        """
        A short wrapper to allow us to return functions from the CommandDatabase
        as a local function.
        
        @PARAM attribute - the attribute as a string name
        """
        debug.write("[SourceRPG] Accessing attribute outside of the PlayerObject scope", 4)
        if attribute in vars(CommandsDatabase):
            debug.write("[SourceRPG] Returning a command link to the CommandsDatabase object", 4)
            return self.CommandLink(attribute, self.command)
        else:
            debug.write("[SourceRPG] Attribut %s not found", 1)
            raise AttributeError, "Function or Attribute %s cannot be found" % attribute
            
    class CommandLink(object):
        """
        This class acts as a gateway between this object instance and the
        player's CommandsDatabase object, making it seem like we can call all
        the command database as local objects.
        """
        def __init__(self, function, instance):
            """
            Default constructor, assign variables so we can use them later on
            
            @PARAM function - the string name of the function we wish to execute
            @PARAM instance - the instance of the CommandsDatabase() object
            """
            self.instance = instance
            self.function = function
            
        def __call__(self, *args, **kw):
            """
            Executed when the class is used as a function. Execute the string
            function within the CommandsDatabase passing the instance, arguments
            and key words.
            
            @PARAM OPTIONAL *args - list of arguments to unpack
            @PARAM OPTIONAL **kw - dictionary of optional arguments to unpack 
            """
            debug.write("[SourceRPG] Executing the function %s within the CommandsDatabase" % self.function, 4)
            vars(CommandsDatabase)[self.function](self.instance, *args, **kw)
            
        
    def __getitem__(self, item):
        """
        Allows the class instance to be indexed similarly to a dictionary.
        Return the item in currentAttributes or currentSkills if they exist,
        otherwise return None.
        
        @PARAM item - the item of which you'd wish to retrieve from the dictionaries.
        """
        debug.write("[SourceRPG] Handling retrieving of attribute %s for player %s" % (item, self.name), 3)
        if item in self.currentAttributes:
            debug.write("Item is in current attributes, return", 4)
            return self.currentAttributes[item]
        if item in self.currentSkills:
            debug.write("Item is a skill, return skill level", 4)
            level = self.currentSkills[item]
            if item in skills:
                if level > int(skills[item].maxLevel):
                    level = int(skills[item].maxLevel)
            return level
        if item in self.playerAttributes:
            debug.write("The item is an attribute, return from the local cache", 4)
            return self.playerAttributes[item]
        if item in skills:
            """ 
            The item is a skill, however, the user hasn't got a database column
            yet and rather than create one because we don't need it yet, we can
            just return 0
            """
            debug.write("Skill not in player's cache, but is loaded, assume no level", 4)
            return 0
        debug.write("Value not found, return 0", 3)
        return None
        
    def __setitem__(self, item, value):
        """
        Allows us to set information about a player, database relevancy
        takes preference over the playerAttributes dictionary
        
        @PARAM item - the item to set the value to
        @PARAM value - the value that item will hold
        """
        debug.write("[SourceRPG] Assigning attribute %s with the value of %s to player %s" % (item, value, self.name), 3)
        if item in self.currentAttributes:
            debug.write("Value is in current attributes, assign to the currentAttributes dict", 4)
            self.currentAttributes[item] = value
        elif item in self.currentSkills or item in skills:
            debug.write("Value is in skills, assign to the currentSkills dict", 4)
            self.currentSkills[item] = value
        else:
            debug.write("Value is not in any dictionary, assign to the custom playerAttributes dict", 4)
            self.playerAttributes[item] = value
        debug.write("[SourceRPG] Value updated", 3)
        
    def resetPlayerDefaultAttributes(self):
        """
        All the default virtual information about a player which is not database
        related should be set here such as maximum speed and maximum health
        """
        debug.write("[SourceRPG] Resetting player %s default attribute" % self.name, 2)
        self.playerAttributes['baseHealth'] = 100
        self.playerAttributes['maxHealth']  = 100
        self.playerAttributes['maxSpeed']   = 1.0
        self.playerAttributes['maxGravity'] = 1.0
        self.playerAttributes['minStealth'] = 255
        self.playerAttributes['maxArmor']   = 100
        debug.write("[SourceRPG] Attributes reset", 2)
        
    def getSkillLevel(self, skillName):
        """
        Return the level of a certain skill. If it doesn't exist the return 0
        
        @PARAM skillName - the string name of the skill to return the value of
        """
        if skillName in self.currentSkills:
            return self.currentSkills[skillName]
        return 0
        
    def commit(self):
        """
        This method takes the current values and tests them against the original
        values retrieved from the database. This will ensure that only modified
        values will be uploaded thus saving resources by constantly querying the
        database. If turbo mode is on the database will not commit.
        """
        debug.write("[SourceRPG] Committing player stats", 2)
        if not currentTurboMode:
            """ Update the player's generic static stats """
            for key, value in self.currentAttributes.iteritems():
                debug.write("Testing cache on %s" % key, 3)
                if key in self.oldAttributes:
                    # We only want to update current attributes.
                    debug.write("Cache is old, needs updating to db", 4)
                    if value <> self.oldAttributes[key]:
                        database.execute("UPDATE Player SET %s=? WHERE UserID=?" % key,
                                                  value,
                                                  self.dbUserid)       
                    debug.write("Cache updated", 4)    
            """ Update the skills """
            for key, value in self.currentSkills.iteritems():
                debug.write("Testing cache on %s" % key, 3)
                if key not in self.oldSkills:
                    debug.write("Skill does not exist, insert into DB", 3)
                    database.execute("INSERT INTO Skill (name, UserID, level) VALUES (?,?,?)",
                                             key,
                                             self.dbUserid,
                                             value)
                else:
                    debug.write("Skill updated, update DB", 4)
                    if value <> self.oldSkills[key]:
                        database.execute("UPDATE Skill SET level=? WHERE UserID=? AND name=?",
                                                     value,
                                                     self.dbUserid,
                                                     key )
                    debug.write("Cache updated", 4)
            
            """ Make sure the old attributes are updates """
            self.oldAttributes = self.currentAttributes.copy()
            self.oldSkills = self.currentSkills.copy()
            debug.write("Local cache objects updated to the new changes", 2)
        debug.write("[SourceRPG] Player stats commited", 2)
        
    def update(self):
        """
        This method gets the latest information from the sqlite database
        and updates it to the old and new instances within this class.
        
        If turbo mode is on, then it will make up default values.
        """
        debug.write("Querying stats for player %s" % self.name, 3)
        debug.write("Is turbo mode on? %s" % ({True:"Yes", False:"No"}[currentTurboMode]), 3)
        if not currentTurboMode:
            database.execute("SELECT * FROM Player WHERE UserID=?", self.dbUserid)
                    
            """ 
            ~NOTE: Nasty, explicitly defining may break in future updates if I
            alter the DB structure, think of a new way to alter this.
            
            Thoughts:
            
            Make a list of the structure, then call the list and update the oldAttributes
            and currentAttributes with items from the list.
            """
            result = database.fetchone()
            UserID, steamid, level, xp, credits, popup, name, lastconnected = result

            debug.write("Steamid: %s" % steamid, 3)
            debug.write("DB UserID: %s" % UserID, 3)
            debug.write("xp: %s" % xp, 3)
            debug.write("Credits %s" % credits, 3)
            debug.write("Level: %s" % level, 3)
            
            for option in ('steamid', 'level', 'xp', 'credits', 'popup', 'name', 'lastconnected'):
                self.oldAttributes[option] = self.currentAttributes[option] = locals()[option]
                
            """ Retrieve the skills id and level from the database """
            database.execute("SELECT name,level FROM Skill WHERE UserID=?", self.dbUserid )
            
            """ Iterate through all the skills returned """
            for skill in database.fetchall():
                """ Each iteration will produce a 2 itemed tuple with skillrow and level """
                skillName, level = skill

                debug.write("Skill %s :: level %s" % (skillName, level), 3)
                
                """ We only want to add the skill if it's currently loaded """
                self.oldSkills[skillName] = self.currentSkills[skillName] = level
                    
        else:
            """ Turbo mode is on, make the default attributes """   
            defaultValues = {
            "level"   : 1,
            "xp"      : 0,
            "credits" : int(startCredits),
            "popup"   : int(popupStatus),
            "lastconnected" : int(time.time())
            }
            for key, value in defaultValues.iteritems():
                self.oldAttributes[key] = self.currentAttributes[key] = value
        
class CommandsDatabase(object):
    """
    This class should not be accessed directly. It should be accessed through
    the PlayerObject() class method accessing these functions as local functions.
    
    This class is for storing all commands together in a manageable interface
    but still providing access to the PlayerObject class and making it simple
    for the end user
    """
    def __init__(self, userid):
        """
        Default constructor, store the userid and the player instance
        
        @PARAM userid - the id of the user which the commands execute on
        """
        debug.write("[SourceRPG] CommandsDatabase object constructor for userid %s" % userid, 1)
        self.userid = int(userid)
        self.player = players[userid] 
        debug.write("[SourceRPG] CommandsDtabase object created", 1)
    
    def addXp(self, amount, reason = "", tellUserOverride = True):
        """
        This function adds experience to a user. It will chekc their level
        and carry on incrementing their level until no more experience is gained.
        It reassigns the values to the player.
        
        @PARAM amount - the amount of experience to add
        @PARAM OPTIONAL reason - the reason that the experience was added
        @PARAM OPTIONAL tellUserOverride - whether or not that the message will be sent (if False, then it will ignore announceXp server var)
        """
        debug.write("[SourceRPG] Handling addXp function for userid %s" % self.userid, 1)
        if not amount:
            debug.write("No experience given, return early", 1)
            return
        
        """ If turbo mode is on the multiply the experience gained """
        if currentTurboMode:
            debug.write("Turbo mode is on, multiply experience gain", 2)
            amount = int( amount * float(turboXpMultiplier))
            
        oldXp = self.player['xp']
        currentXp  = amount + oldXp
        currentLevel   = self.player['level']
        debug.write("OldXp: %s, currentXp: %s, currentLevel: %s" % (oldXp, currentXp, currentLevel), 2 )
        
        amountOfLevels = 0
        nextLevelXp    = (currentLevel - 1) * int(xpIncrement) + int(startXp)
        
        """ Ensure that multiple levels are added as one instance """
        while currentXp > nextLevelXp:
            amountOfLevels += 1
            currentXp      -= nextLevelXp
            nextLevelXp += int(xpIncrement)
        
        debug.write("Amount of levels gained: %s" % amountOfLevels, 2)
            
        """ If the server owner wishes, tell them the message """
        if tellUserOverride is True and int(announceXp):
            tokens = {}
            tokens['name'] = self.player.name
            tokens['amount'] = amount
            tokens['reason'] = (" for " + reason) if reason else ""
            tell(self.userid, 'xp gained', tokens)
            
        """ Assign the new XP value """
        self.player['xp'] = currentXp
        
        """ Create an fire the gainxp event """
        values = {}
        values["oldxp"] = ("setint", oldXp)
        values["newxp"] = ("setint", currentXp)
        values["userid"] = ("setint", self.userid)
        values["levels"] = ("setint", amountOfLevels)
        values["xpneeded"] = ("setint", nextLevelXp)
        values["reason"] = ("setstring", reason if reason else " ")
        gamethread.delayed(0, fireEvent, ("sourcerpg_gainxp", values))
        
        debug.write("[SourceRPG] addXP handled", 2)
        
        if amountOfLevels:
            self.addLevel( amountOfLevels )
            
    def addLevel(self, amount):
        """
        Increments the user's level and if needed, play a sound, send a message
        and build the skills menu for them
        
        @PARAM amount - the amount of levels to add
        """
        debug.write("[SourceRPG] Handling addLevel", 1)
        self.player['level'] += amount
        
        """ If turbo mode is on multipliy the credits received """
        if currentTurboMode:
            self.player['credits'] += int( amount * int(creditsReceived) * float(turboCreditMultiplier))
        else:
            self.player['credits'] += amount * int(creditsReceived)
        
        """ Check if the level has reached the limit """
        if int(maxLevel) and self.player['level'] > int(maxLevel):
            debug.write("Maximum level reached, ensure that resetSkills", 1)
            """ If we want to reset the skills, reset them """
            if int(maxLevelReset):
                self.resetSkills()
                tell(self.userid, 'maximum level reached')
                debug.write("Levels Reset", 1)
            else:
                """ Othewise assign the level and XP to the maximum possible """
                self.player['level'] = int(maxLevel)
                self.player['xp']    = (self.player['level'] - 1) * int(xpIncrement) + int(startXp) - 1
                debug.write("Assigned XP to maximum value", 1)
        else:        
            """ The level is okay, check for bots and play the message etc """
            if not self.player.isbot:
                debug.write("Player is not a bot", 2)
                """ Only do the following for humans """
                if not int(levelUp):
                    tokens = {}
                    tokens['level']  = self.player['level']
                    tokens['xp']     = self.player['xp']
                    tokens['nextxp'] = (self.player['level'] - 1) * int(xpIncrement) + int(startXp) - self.player['xp']
                    tell( self.userid, 'level gained private', tokens )
                
                if self.player['popup']:
                    debug.write("Building skill menu", 1)
                    buildSkillMenu(self.userid)
            
            else:
                """ Player is a bot, check for the maximum possible level for a bot """
                debug.write("Bot leveled up, choose a random skill", 2)
                if int(botMaxLevel) and self.player['level'] > int(botMaxLevel):
                    debug.write("Reset bot's skills, maximum level achieved", 2)
                    self.resetSkills()
                else:
                    """ Upgrade a random skill if possible """
                    while True:
                        """ Loop until we manually break """
                        possibleChoices = []
                        credits = self.player['credits']
                        for skill in skills:
                            """ 
                            Iterate through all loaded skills and if the bot
                            can afford the skill, append it to the possible choices
                            """
                            if credits >= self.player[skill.name] * skill.creditIncrement + skill.startCredit:
                                if self.player[skill.name] < skill.maxLevel:
                                    possibleChoices.append(skill.name)
                        if not possibleChoices:
                            """ 
                            The bot cannot afford any skills or has maxed out
                            the skills, the manually break
                            """
                            break
                        
                        """ 
                        Finally call the checkSkillForUpgrading function passing
                        the arguments manually rather than letting a popup do it
                        """
                        debug.write("Checking to update a skill", 2)
                        checkSkillForUpgrading(self.userid, random.choice(possibleChoices), None, False )
            
            if int(levelUp):
                tokens = {}
                tokens['name']   = self.player.name
                tokens['level']  = self.player['level']
                tokens['xp']     = self.player['xp']
                tokens['nextxp'] = (self.player['level'] - 1) * int(xpIncrement) + int(startXp)
                
                for userid in filter( lambda x: not es.isbot(x), es.getUseridList()):
                    tell(userid, 'level gained global', tokens)
                
            if str(levelupSound):
                es.emitsound('player', self.userid, str(levelupSound), 0.7, 0.5 )
            
            """ Create and fire the levelup event """
            values = {}
            values["userid"] = ("setint", self.userid)
            values["newlevel"] = ("setint", self.player['level'])
            values["oldlevel"] = ("setint", self.player['level'] - amount)
            values["amount"] = ("setint", amount)
            values["xp"] = ("setint", self.player['xp'])
            values["xpneeded"] = ("setint", (self.player['level'] - 1) * int(xpIncrement) + int(startXp))
            gamethread.delayed(0, fireEvent, ("sourcerpg_levelup", values))
        debug.write("[SourceRPG] Handled addLevel", 1)
                
    def resetSkills(self):
        """
        Reset a player's skills. Just assign all the default skills and attributes
        back to 0 for the player, then slay to ensure that the attributes
        reset correctly
        """
        """ Reset the default attributes """
        self.player['level']   = 1
        self.player['xp']      = 0
        self.player['credits'] = int(startCredits)
        self.player['popup']   = int(popupStatus)
        self.player['name']    = self.player.name
        self.player['lastconnected'] = int(time.time())

        
        """ Iterate through the skills list then set each skill to 0 """
        for skill in skills:
            self.player[skill.name] = 0

        """ Slay the player """
        es.server.queuecmd("damage %s %s" % (self.userid, es.getplayerprop(self.userid, "CBasePlayer.m_iHealth")))
        
        """ Notify the user """
        tell(self.userid, 'info deleted')
        
class RankManager(object):
    """
    This class just saves the sorted top 10 so we can call it at certain stages
    without it 
    """
    def __init__(self, update=False):
        """ Executed when the class is instanced, update the ranks """
        if update is not False:
            self.update()
        
    def __len__(self):
        """
        Called when len(instance) is executed
        
        @RETURN integer - the amount of items the ranks contain
        """
        return len(self.ranks)
        
    def __contains__(self, steamid):
        """
        Called when testing for valadity to see if a steamid is in the list
        
        @PARAM steamid - the steamid to test for
        @RETURN boolean - whether or not the steamid exists within the ranks
        """
        return bool( steamid in self.ranks )
        
    def update(self):
        """
        Updates the ranks with the latest information from the sqlite database
        """
        debug.write("[SourceRPG] Updating all ranked positions", 1)
        database.execute("SELECT steamid FROM Player ORDER BY level DESC,xp DESC")
        results = database.cursor.fetchall()
        self.ranks = []
        for index, steamid in enumerate(results):
            debug.write("Rank: %s Steamid: %s" % (index, steamid), 5)
            self.ranks.append(steamid[0])
        debug.write("[SourceRPG] All ranked positions updated", 1)
        
    def getRank(self, steamid):
        """
        Returns the rank position of a steamid within the ranks
        
        @PARAM steamid - the steamid to test for
        @RETURN integer - the position of the steamid
        """
        if self.__contains__(steamid):
            return self.ranks.index(steamid) + 1
        return self.__len__()
        
    def getPlayerSlice(self, bottom = 0, top = 10):
        """
        Returns the slice of players. If there aren't 10 players, it will return
        all the players.
        
        @RETURN list - of the sliced steamids
        """
        if bottom > top:
            top, bottom = bottom, top
        return self.ranks[bottom:top]
        
class ConfigurationObject(cfglib.AddonCFG):
    """ 
    Create a child class of an AddonCFG class within the cfglib library.
    
    Overwrite the write() and execute() function so they are only called from
    this file rather than giving access to each of the skills as the instance
    only should be executed once after all the files have been written.
    
    Provide a single interface where all configurations should be done together.
    """        
    def addInfo(self, name, information):
        """
        Add information about a specific skill. Create a header and a footer
        which will embedd information about the specfic skill.
        
        @PARAM name - the string name of the skill
        @PARAM information - the information about the skill, accepts multiline 
        """
        gamethread.delayed(0, gamethread.delayed, (0, self.setSkillInfo, (name, information))) # delay by 2 ticks to allow skills to register
        header = "\n%s\n%s\n\n" % ('*' * 50, name.center(50))
        footer = "\n%s" % ("*" * 50)
        information = information.strip() # strip whitespace at begggining and end of lines
        information = (header + information + footer).replace('\n', '\n// ')
        self.text(information, False)
        
    def setSkillInfo(self, name, information):
        """
        This function updates the skills object with the latest information
        
        @PARAM name - The name of the skill to add information to
        @INFORMATION - the information of the skill
        """
        skills[name].info = information
        
    def execute(self, queuecmd = False, internal = False):
        """
        Overwrite the parent class as a virtual function. Append another argument
        so that only the internal script has access to this function (unless
        some other script adds the True value)
        
        @PARAM queuecmd - Append the execution to the queue stack
        @PARAM internal - is this an internal call
        """
        debug.write("[SourceRPG] Executing skills.cfg", 0, True)
        if internal:
            es.server.cmd('exec ' + self.cfgpath.replace(str(cfgPath).replace("\\", "/"), '', 1).lstrip("/"))
            
    def write(self, internal = False):
        """
        Overwrite the parent class as a virtual function. Ensure that only an
        internal call should have access to this file
        
        @PARAM internal - is this an internal call
        """
        debug.write("[SourceRPG] Writing skills.cfg", 0, True)
        if internal:
            cfglib.AddonCFG.write(self)
        
class CommandManager(object):
    """
    A class which contains all relevant server commands as static methods
    """
    def mainCommand(self, args):
        """
        Executed when the sourcerpg server command is issued. The first argument
        should be the string name of the method here, e.g. srpg addxp
        will run the function addxp within this class.
        
        @PARAM args - the arguments of the command
        """
        command = args.pop(0).lower() # calls exception if no arguments present
        if command in vars(CommandManager):
            vars(CommandManager)[command](self, *args) # calls exception if wrong amount of arguments
        
    def addweaponxp(self, weapon, experiencePerDamage, experiencePerKill = 0):
        """
        A server command executed when srpg addweaponxp is found. Add the
        weapon and its relevant information to the weaponXp dictionary
        
        @PARAM weapon - the weapon to add additional experience for
        @PARAM experiencePerDamage - the amount of experience per damage gained
        @PARAM experiencePerKill - the amount of experience gained for killing with this weapon
        """
        weaponXp[weapon.replace('weapon_', '').lower()] = ( int( experiencePerDamage ), int( experiencePerKill))
        
    def addxp(self, userid, amount, *reason):
        """
        A server command to add experience to a certain user
        
        @PARAM userid - the user who to add the xp to
        @PARAM amount - anount of experience to add
        @PARAM OPTIONAL reason - the reason for the experience
        """
        players[userid].addXp( int(amount), " ".join(reason))
            
    def addlevel(self, userid, amount):
        """
        A server command to add levels to a certain user
        
        @PARAM userid - the user who to add th elevels to
        @PARAM amount - the amount of levels to add 
        """
        players[userid].addLevel(amount)
        
    def load(self, skillName):
        """
        A server command to load a skill into this mod
        
        @PARAM skillName - the name of the skill they wish to load
        """
        es.load("%s/skills/%s" % (info.basename, skillName))
        
    def unload(self, skillName):
        """
        A server command to unload a skill from this mod
        
        @PARAM skillName - the name of the skill to unload
        """
        es.unload("%s/skills/%s" % (info.basename, skillName))
        
    def unloadallskills(self):
        """
        This method unloads all current loaded skills
        """
        for skill in skills.skills.copy():
            es.unload("%s/skills/%s" % (info.basename, skill))
        skills.clearList()
    
    def loadallskills(self):
        """
        This metod loads all skills within the skills folder
        """
        for skill in os.listdir( os.path.join( es.getAddonPath( info.basename ), "skills" )):
            es.load("%s/skills/%s" % (info.basename, skill))
            
    def loadaddon(self, addonName):
        """
        A server command to load an addon from the ../sourcerpg/addons
        directory.
        
        @PARAM addonName - the name of the addon to load
        """
        es.load("%s/addons/%s" % (info.basename, addonName))
        
    def unloadaddon(self, addonName):
        """
        A server command to unload an addon from the ../sourcerpg/addons
        directory.
        
        @PARAM addonName - the name of the addon to unload
        """
        es.unload("%s/addons/%s" % (info.basename, addonName))
        
class SayCommandsManager(object):
    """
    This class only acts as a container for several functions so I can seperate
    the main methods from the say command functions.
    
    All functions accept the following parameters:
    
    @PARAM userid - The userid of the person who executed the say command
    @PARAM args   - A list of arguments following the say command 
    """
    @staticmethod
    def mainMenu(userid, args):
        """ Executed when a user types 'rpgmenu' """
        if popuplib.isqueued("sourcerpg_rpgmenu", userid):
            return
        popuplib.send('sourcerpg_rpgmenu', userid)
        
    @staticmethod
    def upgradeMenu(userid, args):
        """
        Executed when a user types 'rpgupgrade'. Update their popup and send 
        it to them 
        """
        buildSkillMenu(userid)
        
    @staticmethod
    def sellMenu(userid, args):
        """
        Executed when a user types 'rpgsell' in chat. Update their sell
        menu then send it to them
        """
        buildSellMenu(userid)
    
    @staticmethod
    def helpMenu(userid, args):
        """
        Executed when a user types 'rpghelp' in chat. Send the help menu to them
        """
        if popuplib.isqueued("sourcerpg_help", userid):
            return
        popuplib.send('sourcerpg_help', userid)
    
    @staticmethod
    def stats(userid, args):
        """
        Executed when a user types 'rpgstats' in chat. Builds a stats menu
        about them and sends it to them.
        """
        if len(args):
            playerToTest = es.getuserid(str(args))
            if playerToTest is None:
                playerToTest = userid
            buildStatsMenu(userid, playerToTest)
        else:
            buildStatsMenu(userid, userid)
        
    @staticmethod
    def rank(userid, args):
        """
        Executed when a user types 'rpgrank' in chat. Tells them the players
        rank or another player's rank.
        """
        testUserid = userid
        if len(args):
            testUserid = es.getuserid(str(args))
            if not es.exists('userid', testUserid):
                testUserid = userid
        player = players[testUserid]
        tokens = {}
        tokens['name']    = player['name']
        tokens['level']   = player['level']
        tokens['xp']      = player['xp']
        tokens['nextxp']  = (player['level'] - 1) * int(xpIncrement) + int(startXp)
        tokens['credits'] = player['credits']
        tokens['rank']    = ranks.getRank(player['steamid'])
        tokens['total']   = len( ranks )
        for tellUserid in es.getUseridList():
            tell(tellUserid, 'rank', tokens)
        
    @staticmethod
    def top10(userid, args):
        """
        Display the top 10 RPG players
        """
        if popuplib.isqueued("sourcerpg_rpgtop5", userid):
            return
        popuplib.send('sourcerpg_rpgtop5', userid)
        
    @staticmethod
    def togglePopup(userid, args):
        """
        This method toggles on or off a players popup status
        """
        player = players[userid]
        player['popup'] = 1 - player['popup']
        
        """ Tell them the message """
        tokens = {}
        tokens['status'] = {1:"on", 0:"off"}[player['popup']]
        tell(userid, 'toggle popup', tokens)
        
class PopupCallbacks(object):
    """
    A popup which contains only static methods to categorize and order all
    popup callbacks.
    
    All functions will be static and except three parameters, they are as follows:
    
    @PARAM userid - the user who chose an option from the menu
    @PARAM choice - the choice that the user picked
    @PARAM popupid - the name of the popup
    """
    @staticmethod
    def rpgmenu(userid, choice, popupid):
        """ Executed when the user selects an option from the main rpgmenu """
        if choice == 1:
            """ Rebuild the skills menu and send it to the user """
            buildSkillMenu(userid)
        elif choice == 2:
            """ Rebuild the sell menu and send it to the user """
            buildSellMenu(userid)
        elif choice == 3:
            """ Send the help menu to the user """
            if popuplib.isqueued("sourcerpg_help", userid):
                return
            popuplib.send('sourcerpg_help', userid)
        elif choice == 4:
            """ Rebuild the stats menu for a specific user and send them the details """
            buildStatsMenu(userid, userid)
        else:
            """ Send them the confirmation popup to ensure they wish to delete their skills """
            if popuplib.isqueued("sourcerpg_confirm", userid):
                return
            popuplib.send('sourcerpg_confirm', userid)
    
    @staticmethod
    def helpmenu(userid, choice, popupid):
        """ This menu is the callback for the help popup option. """
        if choice == 1:
            """ Send the about this mod popup """
            if popuplib.isqueued("sourcerpg_about", userid):
                return
            popuplib.send('sourcerpg_about', userid)
        elif choice == 2:
            """ Send the commands list to the player """
            if popuplib.isqueued("sourcerpg_commands", userid):
                return
            popuplib.send('sourcerpg_commands', userid)
        elif choice == 3:
            """ Build the new skills reference popup and send it to the user """
            buildSkillsReferencePopup(userid)
        elif choice == 4:
            """ Send the menu crediting authors of this script to the user """
            if popuplib.isqueued("sourcerpg_creditmenu", userid):
                return
            popuplib.send('sourcerpg_creditmenu', userid)
            
    @staticmethod
    def confirm(userid, choice, popupid):
        """
        This callback function is processed when a user has selected their
        choice to confirm if they want to reset their skills or not. If they
        do, choice will be True, otherwise False.
        """
        if choice:
            players[userid].resetSkills()

class DebugManager(object):
    """
    This class manages writing debug messages to text files for easier reading.
    """
    def __init__(self, path):
        """
        The default constructor, this should set up the file and raise an
        exception if the file doesn't exist.
        
        @PARAM path - the path to the debug.
        """
        self.path = path
        """ If the file doesn't exist, touch it """
        open(self.path, 'w').close()
        
    def write(self, text, debugLevel, writeTime = True):
        """
        Writes a line to the debug file.
        
        @PARAM text       - the text to write to the file
        @PARAM debugLevel - if the debug server variable allows it, then write it
        @PARAM writeTime  - whether or not we should 
        """
        if int(debugMode) >= debugLevel:
            es.dbgmsg(0, text)
            
            if writeTime is True:
                """ Prepend the time to the text """
                currentTime = time.strftime("%d/%m/%Y - %H:%M:%S")
                text = currentTime + " - " + text
            
            """ Write to the file stream """
            fileStream = open(self.path, 'a')
            fileStream.write(text + "\n")
            fileStream.close()

""" Create the singletons to hold the object of the manager classes """
weaponXp    = {}
commands = CommandManager()
cmdlib.registerServerCommand("srpg", commands.mainCommand, "srpg <command> [args]")
es.server.cmd("exec sourcerpg/config.cfg")
currentTurboMode = bool( int(turboMode))
DATABASE_STORAGE_METHOD = SQLiteManager
database = None
databasePath = os.path.join( es.getAddonPath(info.basename), "players.sqlite" )
debugPath    = os.path.join( es.getAddonPath(info.basename), "debuglog.txt"   )
skills   = SkillManager()
addons   = AddonManager()
players  = PlayerManager()
popups   = PopupCallbacks()
ranks    = RankManager()
debug    = DebugManager(debugPath)
skillConfig = ConfigurationObject(str(sourcerpgCFG.joinpath("skills.cfg")))
sayCommands = SayCommandsManager()

####################################
### API ENDS HERE, Program below ###
####################################

def load():
    """
    Executed when the script loads. Ensures that all the commands are registered
    and all the default popups loaded
    """
    global database
    global ranks
    
    osPlatform = ("Windows" if os.name == "nt" else "Linux" if os.name == "posix" else os.name)
    debug.write('Log file started at %s' % time.strftime("%A %d %B %Y - %H:%M:%S"), 0, False)
    debug.write('\n*******************************************************',        0, False)
    debug.write('[SourceRPG]: Turning your Server into a Role Playing Game',        0, False)
    debug.write('[SourceRPG]: Current Version - %s' % info.version,                 0, False)
    debug.write('[SourceRPG]: Made by %s' % info.author,                            0, False)
    debug.write('\nSystem Info:',                                                   0, False)
    debug.write('\tOS: %s' % osPlatform,                                            0, False)
    debug.write('\tEventscripts Version: %s' % es.ServerVar('eventscripts_ver'),    0, False)
    debug.write('\tCorelib Version: %s' % es.ServerVar('es_corelib_ver'),           0, False)
    debug.write('\tEventscript Tools Version: %s' % es.ServerVar('est_version'),    0, False)
    debug.write('\tEventscripts Noisy: %s' % es.ServerVar('eventscripts_noisy'),    0, False)
    debug.write('\tPopuplib version: %s' % popuplib.info.version,                   0, False) 
    
    cmdlib.registerSayCommand("rpgmenu",    sayCommands.mainMenu,    "Opens the rpg main menu")
    cmdlib.registerSayCommand("rpgupgrade", sayCommands.upgradeMenu, "Opens the upgrade menu")
    cmdlib.registerSayCommand("rpgsell",    sayCommands.sellMenu,    "Opens the sell menu")
    cmdlib.registerSayCommand("rpghelp",    sayCommands.helpMenu,    "Opens the help menu")
    cmdlib.registerSayCommand("rpgstats",   sayCommands.stats,       "Opens the stats menu for the user or another player")
    cmdlib.registerSayCommand("rpgrank",    sayCommands.rank,        "Tells the player their rank or another player's rank")
    cmdlib.registerSayCommand("rpgpopup",   sayCommands.togglePopup, "Tells the player their rank or another player's rank")
    cmdlib.registerSayCommand("rpgtop10",   sayCommands.top10,       "Sends the player the last updated top 10 scores")
    
    es.server.cmd("exec sourcerpg/skill_loader.cfg")
    
    es.server.cmd("exec sourcerpg/addon_loader.cfg")
    
    skillConfig.write(True)
    skillConfig.execute(True, True)

    debug.write('[SourceRPG] Starting the popup creation', 0, False)

    """ Create the default popups which aren't unique to players """
    rpgmenu = popuplib.easymenu("sourcerpg_rpgmenu", "_popup_choice", popups.rpgmenu)
    rpgmenu.settitle("=== %s Menu ===" % prefix)
    rpgmenu.addoption(1, "Upgrade Skills")
    rpgmenu.addoption(2, "Sell Skills")
    rpgmenu.addoption(3, "RPG Help")
    rpgmenu.addoption(4, "RPG Stats")
    rpgmenu.addoption(5, "Reset Skills")
    
    helpMenu = popuplib.easymenu('sourcerpg_help', '_popup_choice', popups.helpmenu)
    helpMenu.settitle('=== %s Help ===' % prefix)
    helpMenu.addoption(1, 'About SourceRPG')
    helpMenu.addoption(2, 'List of Commands')
    helpMenu.addoption(3, 'About SourceRPG Skills')
    helpMenu.addoption(4, 'Credit')
    helpMenu.submenu(10, "sourcerpg_rpgmenu")
    
    confirmation = popuplib.easymenu('sourcerpg_confirm', '_popup_choice', popups.confirm)
    confirmation.settitle("=== %s Reset Stats ===" % prefix)
    confirmation.setdescription("""Are you sure you want to remove
your skills? There is no chance
or recovering them again!""")
    confirmation.addoption(True,  "Yes")
    confirmation.addoption(False, "No")
    
    about = popuplib.create('sourcerpg_about')
    about.addline('=== About %s ===' % prefix)
    about.addline('-' * 30)
    about.addline('SourceRPG is a python coded mod')
    about.addline('for EventScripts 2+. It enables')
    about.addline('players to gain Levels, by gaining')
    about.addline('XP from certain events, such as')
    about.addline('planting the bomb, or killing')
    about.addline('another player. Each level gives')
    about.addline('%s Credits, which allows you to' % creditsReceived)
    about.addline('buy certain skills which aid you')
    about.addline('in killing other players.')
    about.addline('-' * 30)
    about.addline('->8. Back')
    about.addline('0. Cancel')
    about.submenu(8, 'sourcerpg_help')
    
    commandspopup = popuplib.create('sourcerpg_commands')
    commandspopup.addline("=== %s Commands ===" % prefix)
    commandspopup.addline("-" * 30)
    commandspopup.addline("rpghelp - displays the help menu")
    commandspopup.addline("rpgmenu - displays the main menu")
    commandspopup.addline("rpgrank - displays your RPG rank")
    commandspopup.addline("rpgpopup - toggles on / off automatic popup display")
    commandspopup.addline("rpgupgrade - upgrade skills")
    commandspopup.addline("rpgsell - sell skills")
    commandspopup.addline("rpgstats - display your stats")
    commandspopup.addline("-" * 30)
    commandspopup.addline("->8. Back")
    commandspopup.addline("0. Cancel")
    commandspopup.submenu(8, 'sourcerpg_help')
    
    creditmenu = popuplib.create('sourcerpg_creditmenu') 
    creditmenu.addline('=== %s Credits ===' % prefix)
    creditmenu.addline('-' * 30)
    creditmenu.addline(info.author)
    creditmenu.addline('  Script Creator')
    creditmenu.addline(' ')
    creditmenu.addline('SumGuy14 and Murphey')
    creditmenu.addline('  Letting me use their Long Jump code')
    creditmenu.addline(' ')
    creditmenu.addline('SuperDave')
    creditmenu.addline('  He turned my failing SmogNade code into')
    creditmenu.addline('  a working code! Thank him for that skill.')
    creditmenu.addline(' ')
    creditmenu.addline('JoeyT2008 (Jordan Thomas)')
    creditmenu.addline('  Awesome scripter who made the database conversion')
    creditmenu.addline(' ')
    creditmenu.addline('EventScripts Community')
    creditmenu.addline('  Help and support, and such a good plugin.')
    creditmenu.addline('-' * 30)
    creditmenu.addline('8. Back')
    creditmenu.addline('0. Cancel')
    creditmenu.submenu(8, 'sourcerpg_help')
    
    debug.write('[SourceRPG] Popups created', 0, False)
    
    
    if int(turboMode):
        database = DATABASE_STORAGE_METHOD(":memory:")
    else:
        database = DATABASE_STORAGE_METHOD(databasePath)
        
    ranks = RankManager()
    
    """ If the script is loaded late then make sure all players are inserted """
    if es.getplayercount():
        for player in es.getUseridList():
            players.addPlayer( player )
            
        es.server.queuecmd('mp_restartgame 1')

    if str( es.ServerVar('eventscripts_currentmap')):
        es_map_start({})

    """ If we want to save by intervals then create a repeat to save the database """
    if str( saveType ) == "intervals":
        gamethread.delayedname(float(saveLength), 'sourcerpg_databasesave', saveDatabase)
        
    debug.write('[SourceRPG]: Finished Loading... Enjoy your stay!',         0, False)
    debug.write('*******************************************************\n', 0, False)
    
    
def unload():
    """
    Executed when SourceRPG is unloaded. Clear up all the global values which
    will still be left in other libraries. Save the database.
    """
    database.save() # Save the database

    """ Remove any popups """
    deleted = []
    for popup in popuplib.gPopups:
        if popup.startswith('sourcerpg_'):
            deleted.append(popup)
    for popup in deleted:
        popuplib.delete(popup)

    """ Unload all skills """
    for skill in skills:
        es.unload("sourcerpg/skills/" + skill.name)

    """ Unload all addons """
    for addon in addons:
        es.unload("sourcerpg/addons/" + addon.name)

    """ Unregister the server commands """
    cmdlib.unregisterServerCommand("srpg")
    cmdlib.unregisterSayCommand("rpgmenu")
    cmdlib.unregisterSayCommand("rpgupgrade")
    cmdlib.unregisterSayCommand("rpgsell")
    cmdlib.unregisterSayCommand("rpghelp")
    cmdlib.unregisterSayCommand("rpgstats")
    cmdlib.unregisterSayCommand("rpgrank")
    cmdlib.unregisterSayCommand("rpgpopup")
    cmdlib.unregisterSayCommand("rpgtop10")
        
    gamethread.cancelDelayed('sourcerpg_databasesave')
    
def es_map_start(event_var):
    """
    Executed when a new map loads. Ensure that the events are executed. Update
    the ranks and recreate the top10 menu
    
    @PARAM event_var - an automatically passed event instance
    """
    debug.write('[SourceRPG] Executing the es_map_start event', 1, True)
    es.loadevents('declare', 'addons/eventscripts/%s/events/events.res' % info.basename)
    debug.write('[SourceRPG] Events loaded', 1, True)
    
    ranks.update()
    
    debug.write('[SourceRPG] Ranks updated', 1, True)
    
    if ranks.getPlayerSlice(0, 10):
        debug.write('[SourceRPG] Building top 10 menu', 1, True)
        query = "SELECT name,level,xp,credits FROM Player WHERE steamid IN ("
        for steamid in ranks.getPlayerSlice(0, 10):
            query += "'%s', " % steamid
        query = query[:-2] + ") ORDER BY level DESC,xp DESC"

        database.execute(query)
        results = database.cursor.fetchall()

        rpgTop5Popup = popuplib.create("sourcerpg_rpgtop5")
        rpgTop5Popup.addline("=== %s Top Players ===" % prefix)
        rpgTop5Popup.addline("-" * 30)
        for index, values in enumerate(results[:5]):
            name, level, xp, credits = values
            if len(name) > 16:
                name = name[0:14]+'...'
            rpgTop5Popup.addline("->%s. %s - Lvl: %s XP: %s Credits: %s" % (index + 1, name, level, xp, credits ))
        rpgTop5Popup.addline("-" * 30)
        if len(results) > 5:
            rpgTop5Popup.addline("-> 9. Next")
            rpgTop5Popup.submenu(9, 'sourcerpg_rpgtop10')

            rpgTop10Popup = popuplib.create("sourcerpg_rpgtop10")
            rpgTop10Popup.addline("=== %s Top Players ===" % prefix)
            rpgTop10Popup.addline("-" * 30)

            for index, values in enumerate(results[5:]):
                name, level, xp, credits = values
                if len(name) > 16:
                    name = name[0:14]+'...'
                rpgTop10Popup.addline("->%s. %s - Lvl: %s XP: %s Credits: %s" % (index + 6, name, level, xp, credits ))

            rpgTop10Popup.addline("-" * 30)
            rpgTop10Popup.addline("->8. Back")
            rpgTop10Popup.addline("0. Cancel")
            rpgTop10Popup.submenu(8, 'sourcerpg_rpgtop5')
        rpgTop5Popup.addline("0. Close")
        
        debug.write('[SourceRPG] Top 10 created', 0, True)
        
    """ Delete all inactive people """
    currentTime = int(time.time())
    currentTime -= ( float(inactivityCounter) * 86400 ) # 86400 = seconds in a day
    
    debug.write('[SourceRPG] Removing all inactve players!', 0, True)
    
    database.execute("SELECT UserID FROM Player WHERE lastconnected < ?", int(currentTime))
    for userid in database.fetchall():
        debug.write('[SourceRPG] Removing UserID %s' % userid, 3, False)
        database.execute("DELETE FROM Player WHERE UserID=?", userid )
        database.execute("DELETE FROM Skill WHERE UserID=?", userid )
    
    debug.write('[SourceRPG] es_map_start event finished executing', 1, True)
    
def player_activate(event_var):
    """
    An event which occurs when a user activates on the server. Ensure that their
    skills are pulled from the database and that all the local stats are updated
    
    @PARAM event_var - an automatically passed event instance
    """
    debug.write("[SourceRPG] Handling player_activate", 1)
    if "PENDING" in event_var['es_steamid']:
        debug.write("[SourceRPG] Player joining had a pending steamid, being kicked")
        es.server.cmd('kickid %s "We had an error with you joining, please reconnect"' % event_var['userid'])
    else:
        debug.write("Player successfully joined and activated", 1)
        players.addPlayer( event_var['userid'] )
    debug.write("[SourceRPG] player_activate handled", 1)
    
def player_disconnect(event_var):
    """
    An event which occurs when a player disconnects from the server. Update them
    with the time that they last connected, then remove the instance from the
    PlayerManager class. This will call the destructor on the PlayerObject class
    and update the SQLite Database
    
    @PARAM event_var - an automatically passed event instance
    """
    debug.write("[SourceRPG] Handling player_disconnect", 1)
    userid = event_var['userid']
    gamethread.cancelDelayed('sourcerpg_reset_%s' % userid)
    if userid in players:
        debug.write("Remove player instance from players manager", 1)
        players[userid]['lastconnected'] = int(time.time())
        debug.write("Calling object destructor...", 1)
        del players[userid] # call's the destructor
    debug.write("[SourceRPG] player_disconnect handled", 1)
    
def player_changename(event_var):
    """
    An event which occurs when a player changes their name. Update their local
    copy so that the rpgtop10 menu displays the correct name etc.
    
    @PARAM event_var - an automatically passed event instance
    """
    debug.write("[SourceRPG] Handling player_changename", 1)
    players[event_var['userid']]['name'] = event_var['newname']
    debug.write("[SourceRPG] player_changename handled", 1)
    
def round_end(event_var):
    """
    An event which occurs when the round ends. Ensure that the datbase
    is saved if the saveType is equal to round end. Also updates all players
    stats that they achieved in that round.
    
    @PARAM event_var - event variable instancce passed automatically
    """
    debug.write("[SourceRPG] Round End Processing", 1)
    if not currentTurboMode:
        debug.write("Normal mode is on", 1)
        if str(saveType).lower() == "round end":
            debug.write("Attempting to save the database", 1)
            saveDatabase()
            debug.write("Database successfully saved.", 1)
    for userid in es.getUseridList():
        debug.write("Fetching player object for %s" % es.getplayername(userid), 2)
        player = players[userid]
        if player is not None:
            debug.write("Object successfully fetched, delaying 4.8 seconds to reset default attributes", 2)
            gamethread.delayedname(2, 'sourcerpg_reset_%s' % player, player.resetPlayerDefaultAttributes)
    debug.write("[SourceRPG] Round End has been processed\n", 1)
            
def player_spawn(event_var):
    """
    An event which occurs when a player spawns. If the option is turned on,
    notify them of their level and experience.
    
    @PARAM event_var - event variable instance passed automatically
    """
    debug.write("[SourceRPG] Player Spawn processing", 1)
    userid = event_var['userid']
    if not es.getplayerprop(userid, 'CBasePlayer.pl.deadflag'):
        player = players[userid]
        if player is not None:
            tell(userid, 'round start')
            if int(spawnAnnounce):
                debug.write("Sending player rank information", 1)
                tokens = {}
                tokens['level']  = player['level']
                tokens['xp']     = player['xp']
                tokens['nextxp'] = (player['level'] - 1) * int(xpIncrement) + int(startXp)
                tell(userid, 'level gained private', tokens)
                debug.write("Player rank information sent", 1)
            if currentTurboMode and int(turboModeAnnounce):
                tell(userid, 'turbo mode on')
    debug.write("[SourceRPG] Player Spawn processsed\n", 1)
        
def server_cvar(event_var):
    """
    An event which occurs when a public variable is altered. Here we will check
    to see if the event was turbo mode, then either save the database or open
    a new connection depending on the effect.
    
    @PARAM event_var - an automatically passed event instance
    """
    global currentTurboMode
    global database
    debug.write("[SourceRPG] Handling server_cvar", 1)
    if event_var['cvarname'] == "srpg_turboMode":
        debug.write("Altering turbo mode", 1)
        newValue = bool( int(event_var['cvarvalue']))
        debug.write("New value of turbo mode: %s" % newValue, 1)
        if newValue <> currentTurboMode:
            debug.write("New Value differs to the old value", 1)
            if newValue:
                """ Turbo mode was activated. Restart the round """
                debug.write("Closing database and creating a new memory object", 1)
                gamethread.cancelDelayed('sourcerpg_databasesave')
                
                database = SQLiteManager(":memory:") # Calls deconstructor on the database
                es.server.queuecmd('mp_restartgame 2')
            else:
                debug.write("Closing database and opening the real database",1 )
                """ Turbo mode was switched off """
                database = SQLiteManager(databasePath)
                es.server.queuecmd('mp_restartgame 2')
                
                """ If we need to start the loop again """
                gamethread.cancelDelayed('sourcerpg_databasesave')
                if str( saveType ) == "intervals":
                    gamethread.delayedname(float(saveLength), 'sourcerpg_databasesave', saveDatabase)
                
            currentTurboMode = newValue
                
            """ Ensure that all players are activated and their new values are reset """
            for player in es.getUseridList():
                debug.write("Reloading stats for player %s" % es.getplayername(player), 2)
                tokens = {}
                tokens['userid']     = player
                tokens['es_steamid'] = es.getplayersteamid(player)
                player_activate(tokens)
    debug.write("[SourceRPG] server_cvar handled", 1)
                
def player_death(event_var):
    """
    Executed upon the event "player_death". Add experience to the attacker.
    
    @PARAM event_var - an automatically passed argument as the event instance
    """
    debug.write("[SourceRPG] Handling player_death", 1)
    userid   = event_var['userid']
    attacker = event_var['attacker']
    """ Only pass if the user did not kill themselves and are not on the same team """
    if userid <> attacker:
        debug.write("Userid is not the same as the attacker", 2)
        if attacker.isdigit() and int(attacker) > 0:
            debug.write("Attacker is not of world spawn", 2)
            if event_var['es_userteam'] <> event_var['es_attacker']:
                """ If one of the players is a bot and is not legible for experience, return """
                debug.write("Users are on different teams", 2)
                if not canReceiveExperience(userid, attacker):
                    return
                debug.write("Adding the experience", 1)
                player = players[attacker]
                player.addXp( int(killXp) * players[userid]['level'], "making a kill" )
                if event_var['headshot'] and int(event_var['headshot']):
                    player.addXp( int(headshotXp), "scoring a headshot" )
                weapon = event_var['weapon']
                if weapon in weaponXp:
                    if weaponXp[weapon][1]:
                        useN = "n" if weapon[0] in ("a", "e", "i", "o", "u") else ""
                        player.addXp( weaponXp[weapon][1], "killing a player with a%s %s" % ( useN, weapon ))
    debug.write("Resetting player to default attributes", 1)
    players[userid].resetPlayerDefaultAttributes()
    debug.write("[SourceRPG] player_death handled", 1)
                    
def player_hurt(event_var):
    """
    Executed upon the event "player_hurt". Add experience to the attacker.
    
    @PARAM event_var - an automatically passed argument as the event instance.
    """
    debug.write("[SourceRPG] handling player_hurt", 1)
    userid   = event_var['userid']
    attacker = event_var['attacker']
    """ Only pass if the user did not kill themselves and are not on the same team """
    if userid <> attacker:
        debug.write("Playerids are not the same", 2)
        if attacker.isdigit() and int(attacker) > 0:
            debug.write("Attacker is not work spawn", 2)
            if event_var['es_userteam'] <> event_var['es_attacker']:
                """ If one of the players is a bot and is not legible for experience, return """
                debug.write("Players are not on the same team", 2)
                if not canReceiveExperience(userid, attacker):
                    return
                debug.write("Handling the experience rewards", 1)
                player = players[attacker]
                weapon = event_var['weapon']
                if weapon in weaponXp:
                    if weaponXp[weapon][0]:
                        player.addXp( weaponXp[weapon][0], tellUserOverride = False)
                else:
                    player.addXp( int(damageXp), tellUserOverride = False )
    debug.write("[SourceRPG] player_hurt handled", 1)
    
def bomb_planted(event_var):
    """
    Executed upon the event "bomb_planted". Add experience to the bomb planter.
    
    @PARAM event_var - an automatically passed argument as the event instance.
    """
    debug.write("[SourceRPG] Handling bomb_planted", 1)
    if isFairForTeam(event_var['es_userteam']) or not int(unfairAdvantage):
        if es.isbot(event_var['userid']) and not int(botsGetXpOnEvents):
            return
        player = players[event_var['userid']]
        player.addXp( int(bombPlantXp) * player['level'], 'planting the bomb' )
    debug.write("[SourceRPG] bomb_planted handled", 1)

def bomb_exploded(event_var):
    """
    Executed upon the event "bomb_exploded". Add experience to the bomb planter.
    
    @PARAM event_var - an automatically passed argument as the event instance.
    """
    debug.write("[SourceRPG] Handling bomb_exploded", 1)
    if isFairForTeam(event_var['es_userteam']) or not int(unfairAdvantage):
        if es.isbot(event_var['userid']) and not int(botsGetXpOnEvents):
            return
        player = players[event_var['userid']]
        player.addXp( int(bombExplodeXp) * player['level'], 'allowing the bomb to explode' )
    debug.write("[SourceRPG] bomb_exploded handled", 1)
    
def bomb_defused(event_var):
    """
    Executed upon the event "bomb_defused". Add experience to the defuser.
    
    @PARAM event_var - an automatically passed argument as the event instance.
    """
    debug.write("[SourceRPG] Handling bomb_defused", 1)
    if isFairForTeam(event_var['es_userteam']) or not int(unfairAdvantage):
        if es.isbot(event_var['userid']) and not int(botsGetXpOnEvents):
            return
        player = players[event_var['userid']]
        player.addXp( int(bombDefuseXp) * player['level'], 'defusing the bomb' )
    debug.write("[SourceRPG] bomb_defused handled", 1)
    
def hostage_follows(event_var):
    """
    Executed upon the event "hostage_follows". Add experience to the rescuer.
    
    @PARAM event_var - an automatically passed argument as the event instance.
    """
    debug.write("[SourceRPG] Handling hostage_follows", 1)
    if isFairForTeam(event_var['es_userteam']) or not int(unfairAdvantage):
        if es.isbot(event_var['userid']) and not int(botsGetXpOnEvents):
            return
        player = players[event_var['userid']]
        player.addXp( int(hostageFollowXp) * player['level'], 'making a hostage follow you' )
    debug.write("[SourceRPG] hostage_follows handled", 1)
    
def hostage_rescued(event_var):
    """
    Executed upon the event "hostage_rescued". Add experience to the rescuer
    
    @PARAM event_var - an automatically passed argument as the event instance.
    """
    debug.write("[SourceRPG] Handling hostage_rescued", 1)
    if isFairForTeam(event_var['es_userteam']) or not int(unfairAdvantage):
        if es.isbot(event_var['userid']) and not int(botsGetXpOnEvents):
            return
        player = players[event_var['userid']]
        player.addXp( int(hostageRescueXp) * player['level'], 'rescuing a hostage' )
    debug.write("[SourceRPG] hostage_rescued handled", 1)
    
def isFairForTeam(teamNumber):
    """
    This method returns whether there are currently any living players on the
    other team
    
    @PARAM teamNumber - test to see if it's fair for the players team
    @RETURN boolean - whether or not it's fair
    """
    debug.write("Testing if experience gain is fair for player teams", 2)
    if not 1 < int(teamNumber) < 4:
        debug.write("Incorrect teamnumber passed, teams are not fair", 2)
        return False 
    return bool( es.getlivingplayercount( 5 - int( teamNumber )))
    
def canReceiveExperience(userid, attacker):
    """
    This method tests players to see if it's possible for the attacker to gain
    experience when a bot is involved.
    
    @PARAM userid - the victim involved
    @PARAM attacker - the attacker involved
    @RETURN boolean - whether or not the attacker is legible for experience
    """
    debug.write("Testing if user can gain experience", 2)
    if not es.isbot(userid) and not es.isbot(attacker):
        debug.write("Both players are humans, experience gain is fair", 2)
        return True
    if es.isbot(attacker):
        debug.write("Attacker is a bot", 2)
        if es.isbot(attacker):
            debug.write("Victim is a bot", 2)
            return bool( int( botsGetXpVsBots ))
        else:
            debug.write("Victim is a human", 2)
            return bool( int( botsGetXpVsHumans ))
    else:
        debug.write("Victim is a bot, attacker is a human", 2)
        return bool( int( humansGetXpVsBots ))
    
def getSteamid(value):
    """
    This function will return the actual steamid of a value given. If a STEAMID
    was input then we return that in case we want an offline check. Otherwise
    we return the uniqueid of the players userid if on, otherwise None
    
    @PARAM value - the original input, either userid, partial name or steamid
    @RETURN string - the actual steamid of the input. 
    """
    value = str(value)
    if value.startswith( ("STEAM_", "BOT_")):
        return value
    userid = es.getuserid(value)
    if userid:
        steamid = playerlib.uniqueid( userid, True )
        return steamid
    return None
    
def saveDatabase():
    """
    This function will go through all the players and update the SQLiteDatabase
    with any different values from the ones stored. After all the queries
    have taken place, the database will save to disk
    """
    debug.write("saveDatabase processing", 1)
    """ Only process if turbo mode is off """
    if not currentTurboMode:
        debug.write("turbo mode off, process the save", 1)
        """ Update all the player's stats gained and commit the database"""
        for player in players:
            debug.write("Commiting indivudal players to the virtual database: %s" % player.name, 2)
            player.commit()
        debug.write("Attempting to save the database itself", 1)
        database.save()
        debug.write("SQLite database saved", 1)
        debug.write("Creating the event", 1)
        """ Create and fire the event """
        values = {"type":("setstring", str(saveType))}
        gamethread.delayed(0, fireEvent, ("sourcerpg_databasesaved", values))
        debug.write("Event fired", 1)
        
        """ Create a loop if we need to """
        if str( saveType ) == "intervals":
            gamethread.delayedname(float(saveLength), 'sourcerpg_databasesave', saveDatabase)
    debug.write("saveDatabase processed", 1)
    
def buildSkillMenu(userid):
    """
    This method creates a new unique popup instance for a certain user and
    adds all the current active skills to the list and tells them how much
    it will cost to upgrade if they can.
    
    @PARAM userid - the userid of the player the popup should be built for
    """
    if popuplib.isqueued("sourcerpg_skillsmenu_user%s" % userid, userid):
        return
    debug.write("[SourceRPG] Handling buildSkillMenu for userid %s" % userid, 2)
    player    = players[userid]
    debug.write("building popup", 2)
    skillMenu = popuplib.easymenu("sourcerpg_skillsmenu_user%s" % userid, "_popup_choice", checkSkillForUpgrading)
    skillMenu.settitle("Select a skill to upgrade:\nCredits: %s\nPage: " % player["credits"])
    for skill in skills:
        level = player[skill.name]
        if level >= int(skill.maxLevel):
            """ If the user has already maxed out the skill, then make the option unselectable """
            skillMenu.addoption(None, str(skill.name) + " [MAXED]" ,False)
        else:
            cost = level * int(skill.creditIncrement) + int(skill.startCredit)
            skillMenu.addoption(skill.name, skill.name + " => %s [COST %s]" % (level + 1, cost), bool(player['credits'] >= cost))
    skillMenu.c_exitformat = "0. Close"
    debug.write("popup built", 2)
    skillMenu.send(userid)
    debug.write("[SourceRPG] Handled buildSkillMenu for userid %s" % userid, 2)
    
def checkSkillForUpgrading(userid, choice, popupid, resend = True, useCredits = True):
    """
    This method is a return function from a popup return. It validates the user's
    choice, and if correct, deduct the credits and increment the skill
    
    @PARAM userid - the id of the user who chose an option from the menu
    @PARAM choice - the name of the skill of which to upgrade
    @PARAM popupid - the name of the popup
    """
    player = players[userid]
    skill  = skills[choice]
    creditsRequired = player[str(skill.name)] * int(skill.creditIncrement) + int(skill.startCredit)
    
    if player['credits'] >= creditsRequired or useCredits is not True:
        """ Remove the credits and increment the skill """
        if useCredits is True:
            player['credits'] -= creditsRequired
        player[str(skill.name)] += 1
        
        """ If the skill upgrade sound is not blank, emmit it from the player """
        if str(skillUpgradeSound):
            es.emitsound('player', userid, str(skillUpgradeSound), '0.7', '0.5')
            
        """ Notify the user of their purchase """
        tokens = {}
        tokens['level'] = player[skill.name]
        tokens['skill'] = str(skill.name)
        tell(userid, 'skill upgrade', tokens)
        
        """ Fire the skill upgrade event """
        values = {}
        values["userid"] = ("setint", userid)
        values["level"] = ("setint", player[str(skill.name)])
        values["cost"] = ("setint", creditsRequired)
        values["skill"] = ("setstring", skill.name)
        gamethread.delayed(0, fireEvent, ("sourcerpg_skillupgrade", values))
        
    else:
    
        """ Notify the user that they cannot purchase due to lack of credits """
        tokens = {}
        tokens['credreq'] = creditsRequired
        tokens['credits'] = player['credits']
        tokens['amountofcreds'] = creditsRequired - player['credits']
        
        tell(userid, 'insufficient credits', tokens)
    
    """ Rebuild the skill menu """
    if resend is True:
        buildSkillMenu(userid)
        
def buildSellMenu(userid):
    """
    This method creates a new unique popup instance for a certain user and
    adds all the current active skills to the list and tells them how much
    they will receive for selling a skill
    
    @PARAM userid - the userid of the player the popup should be built for
    """
    debug.write("[SourceRPG] Handling buildSellMenu for userid %s" % userid, 2)
    player   = players[userid]
    debug.write("building popups", 1)
    if popuplib.isqueued("sourcerpg_sellmenu_user%s" % userid, userid):
        return
    sellMenu = popuplib.easymenu("sourcerpg_sellmenu_user%s" % userid, "_popup_choice", checkSkillForSelling)
    sellMenu.settitle("Select a skill to sell:\nCredits: %s\nPage: " % player["credits"])
    for skill in skills:
        level = player[str(skill.name)]
        if level > 0:
            sellMenu.addoption(skill.name, skill.name + " => %s [GAIN %s]" % (level - 1, int( ( (level - 1) * int(skill.creditIncrement) + int(skill.startCredit)) * float(sellPercentage) / 100.)))
        else:
            sellMenu.addoption(None, skill.name + " [NEED LEVEL]", False)
    sellMenu.c_exitformat = "0. Close"
    debug.write("popup built", 1)
    sellMenu.send(userid)
    debug.write("[SourceRPG] Handled buildSellMenu for userid %s" % userid, 2)

def checkSkillForSelling(userid, choice, popupid, resend=True, gainCredits = True):
    """
    This method is a return function from a popup return. It validates the user's
    choice, and if correct, increments the credits and decrements the skill
    
    @PARAM userid - the id of the user who chose an option from the menu
    @PARAM choice - the name of the skill of which to downgrade
    @PARAM popupid - the name of the popup
    """
    player = players[userid]
    skill  = skills[choice]
    level  = player[skill.name]
    creditsGained = int( ( (level - 1) * int(skill.creditIncrement) + int(skill.startCredit)) * float(sellPercentage) / 100.)
    if creditsGained > 0 or gainCredits is not True:
        """ Only do the purchase if credits are obtained """
        player[skill.name] -= 1
        if gainCredits is True:
            player['credits']  += creditsGained
        
        """ If the skill downgrade sound is not blank, emmit it from the player """
        if str(skillDowngradeSound):
            es.emitsound('player', userid, str(skillDowngradeSound), '0.7', '0.5')
            
        """ Notify the user of their sale """
        tokens = {}
        tokens['level'] = player[skill.name]
        tokens['skill'] = skill.name 
        tell(userid, 'skill downgrade', tokens)
        
        """ Fire the skill downgrade event """
        values = {}
        values["userid"] = ("setint", userid)
        values["level"] = ("setint", player[skill.name])
        values["gained"] = ("setint", creditsGained)
        values["skill"] = ("setstring", skill.name)
        gamethread.delayed(0, fireEvent, ("sourcerpg_skilldowngrade", values))
        
    if resend is True:
        buildSellMenu(userid)
    
def buildStatsMenu(userid, playerToTest):
    """
    This function builds stats for a player and shows them more detailed stats
    in a popup.
    
    @PARAM userid - the userid who to send the popup to
    @PARAM playerToTest - the player who we shall get the stats of
    """
    if popuplib.isqueued("sourcerpg_statsmenu_user%s" % userid, userid):
        return
    player = players[playerToTest]
    statsmenu = popuplib.create('sourcerpg_statsmenu_user%s' % userid)
    statsmenu.addline("=== %s Stats ===" % prefix )
    statsmenu.addline("-" * 30)
    statsmenu.addline("Name: %s" % player['name'])
    statsmenu.addline("Last Disconnected: %s" % (time.strftime("%a %d %b %Y, %H:%M:%S", time.localtime( float( player['lastconnected'] )))))
    statsmenu.addline("Level: %s" % player['level'])
    statsmenu.addline("XP: %s/%s" % (player['xp'], (player['level'] - 1) * int(xpIncrement) + int(startXp)))
    statsmenu.addline("Rank: %s/%s" % ( ranks.getRank( player['steamid'] ), len( ranks )))
    statsmenu.addline("-" * 30)
    statsmenu.addline("->9. See skills")
    statsmenu.addline("0. Close")
    statsmenu.submenu(9, buildSkillsMenuForPlayer(userid, playerToTest, False))
    statsmenu.send(userid)

def buildSkillsMenuForPlayer(userid, playerToTest, send = True):
    """
    A function to build a skill menu for a certain player and show a user
    all their skills and the levels
    
    @PARAM userid - the userid who to send the popup to
    @PARAM playerToTest - the player who we shall get the levels of
    @PARAM OPTIONAL send - if this is true then the menu will be sent to the player
    @RETRUN string - the name of the popup
    """
    if popuplib.isqueued("sourcerpg_skillssmenu_user%s" % userid, userid):
        return 
    popupName = 'sourcerpg_skillsmenu_user%s' % userid
    statsmenu = popuplib.easymenu(popupName, '_popup_choice', None)
    statsmenu.settitle("=== %s Skill Menu ==" % prefix)
    
    player = players[playerToTest]
    if player is not None:
        for skill in skills:
            level = player[skill.name]
            statsmenu.addoption(None, "%s - %s" % (skill.name, level), False )
            
    if send is True:
        statsmenu.send(userid)
    return popupName
    
def buildSkillsReferencePopup(userid):
    """
    This function builds a new skills menu which lists all the skills. The
    callback function will build information on the skills
    
    @PARAM userid - the user to send the popup to
    """
    if popuplib.isqueued("sourcerpg_skillsinfo_user%s" % userid, userid):
        return
    skillsPopup = popuplib.easymenu('sourcerpg_skillsinfo_user%s' % userid, '_popup_choice', buildSkillInfo)
    skillsPopup.settitle("=== %s Skills info ===" % prefix)
    skillsPopup.setdescription("Select a skill to view information on it")
    for skill in skills:
        skillsPopup.addoption(skill.name, skill.name)
    skillsPopup.send(userid)
    
def buildSkillInfo(userid, choice, popupid):
    """
    This function builds information about a certain skill and displays it to
    a user.
    
    @userid - the user who to send the popup to
    @choice - the name of the skill
    @popupid - the name of the popup we just executed.
    """
    if popuplib.isqueued("sourcerpg_skilldescription_user%s" % userid, userid):
        return
    skill = skills[choice]
    info  = skill.info
    maxLevel    = skill.maxLevel
    startCredit = skill.startCredit
    creditIncrement = skill.creditIncrement
    
    lines = []
    if info is not None:
        info  = info.replace('\n', ' ').replace('//','')
        while len(info) > 30:
            space = info.find(' ', 29)
            if space == -1:
                break
            else:
                lines.append(info[:space])
                info = info[space + 1:]
        lines.append(info)
    
    skillInfo = popuplib.create("sourcerpg_skilldescription_user%s" % userid)
    skillInfo.addline("=== %s Skill Info ===" % prefix )
    skillInfo.addline("-" * 30)
    skillInfo.addline("%s Information:" % skill.name)
    skillInfo.addline(" ")
    if lines:
        for line in lines:
            skillInfo.addline(line)
    else:
        skillInfo.addline("[No additional information]")
    skillInfo.addline(" ")
    skillInfo.addline("Maximum Level: %s" % maxLevel)
    skillInfo.addline("Start Credit Cost: %s" % startCredit)
    skillInfo.addline("Credit Incremental Costs: %s" % creditIncrement)
    skillInfo.addline("-" * 30)
    skillInfo.addline("->8. Back")
    skillInfo.addline("0. Cancel")
    skillInfo.submenu(8, popupid)
    skillInfo.send(userid)
    
def tell(userid, textIdent, tokens = {} ):
    """
    This method acts as a wrapper for es.tell(). It ensures that we only need
    a minimum of 2 parameters instead of 5, and prefixes all messages with
    the prefix. Automatically retrieves the player's language
    
    @PARAM userid - the id of the user you wish to tell the message to
    @PARAM textIdent - the identification of the text from the ConfigObj object (langlib)
    @OPTIONAL PARAM tokens - a dictionary of variables you'd like to insert into the string
    """
    message = "#green%s #default- #lightgreen%s" % ( str(prefix), text(textIdent, tokens, playerlib.getPlayer(userid).get("lang"))) 
    es.tell(userid, '#multi', message)
    
def fireEvent(eventName, values):
    """
    Fires a custom event. This allows us to call the event with a tick delay
    to ensure that we arent' in any other event. In the OB engine, if we cause
    an event to be fired whilst we are in another event, it causes the event
    queue to mess up and crash the server. A 1 tick delay removes the chance of
    an event firing whilst in another event.
    
    @PARAM str eventName The name of the event to fire
    @PARAM dict values A dictionary containing all the event variables:
        Dict should be in this syntax:
        {<event_var name>} = (<setint/setstring...>, <event_var value>)
        e.g.:
        {"userid"} = ("setint", 2)
    """
    debug.write("[SourceRPG] Handling custom event firing", 1)
    debug.write("[SourceRPG] Firing event %s" % eventName, 1)
    es.event("initialize", eventName)
    for key, value in values.iteritems():
        debug.write("[SourceRPG] (Type: %s) %s=%s" % (value[0], key, value[1]), 2)
        es.event(value[0], eventName, key, value[1])
    debug.write("[SourceRPG] Firing event", 1)
    es.event("fire", eventName)
    debug.write("[SourceRPG] custom event firing handled", 1)
7af7cce24f025593391043be8cc5cb635afe001e
# SourceRPG release 2.0 by Steven Hartin  
# ./addons/eventscripts/sourcerpg/sourcerpg.py

# >>> To configure this addon please see config.cfg <<<
# >>> To configure the skills please see skills.cfg <<<

#################################
#### DO NOT EDIT THIS FILE, #####
####    IF YOU DO, THINGS   #####
####   THINGS COULD BREAK   #####
#################################

import os
import random
import time

from sqlite3 import dbapi2 as sqlite
from path import path as Path

""" Import the psyco module which improves speed """
import psyco
psyco.full()

import es
import popuplib
import langlib
import gamethread
import playerlib
import cfglib
import cmdlib

# Set the addon info data
revision = int(filter(str.isdigit, "$Revision$"))
info = es.AddonInfo()
info.name     = 'SourceRPG'
info.version  = '2.1.%03d' % revision
info.basename = 'sourcerpg'
info.author   = 'Freddukes AKA Pro Noob'
info.url      = 'http://addons.eventscripts.com/addons/view/%s' % info.basename

# Create the langlib function
text = lambda userid, textIdent, tokens = {}: "No strings.ini found in ../sourcerpg/"
textPath = os.path.join( es.getAddonPath(info.basename), "strings.ini" )
if os.path.isfile(textPath):
    text = langlib.Strings(textPath)

# Create the configuration file
basePath = Path(es.getAddonPath(info.basename))
cfgPath = Path(str(es.ServerVar("eventscripts_gamedir"))).joinpath("cfg")
sourcerpgCFG = cfgPath.joinpath("sourcerpg")
if not sourcerpgCFG.isdir():
    sourcerpgCFG.mkdir()
     
skillLoader = basePath.joinpath("skill_loader.cfg")
addonLoader = basePath.joinpath("addon_loader.cfg")
configFile = basePath.joinpath("config.cfg")
skillConfig = basePath.joinpath("skills.cfg")

if skillLoader.exists():
    skillLoader.copy(sourcerpgCFG.joinpath("skill_loader.cfg"))
    skillLoader.remove()

if addonLoader.exists():
    addonLoader.copy(sourcerpgCFG.joinpath("addon_loader.cfg"))
    addonLoader.remove()
    
if configFile.exists():
    configFile.copy(sourcerpgCFG.joinpath("config.cfg"))
    configFile.remove()

if skillConfig.exists():
    skillConfig.copy(sourcerpgCFG.joinpath("skills.cfg"))
    skillConfig.remove()  

configPath = sourcerpgCFG.joinpath("config.cfg")
config = cfglib.AddonCFG(str(configPath))
config.text(info.name + " version " + info.version + " by " + info.author)
config.text("../addons/eventscripts/sourcerpg/config.cfg")
config.text(">>> To config the skills, see ../sourcerpg/skills.cfg <<<")

config.text("""
// ****************************
// ****************************
// ***** MESSAGE SETTINGS *****
// ****************************
// ****************************""", False)
debugMode     = config.cvar('srpg_debugMode', 0, "The level of debug. The higher the level the more SPAM in console and laggier it is. 0 = disable")
prefix        = config.cvar('srpg_prefix', "[SourceRPG]", "The prefix of the script (will appear before every text message)")
announceXp    = config.cvar('srpg_announceXp', 1, "Whether or not a player is told their XP and level each time they gain experience")
spawnAnnounce = config.cvar('srpg_announceOnSpawn', 1, "Whether or not a player is informed about their xp and level status upon spawn")
levelUp       = config.cvar('srpg_levelUp', 1, "Whether or not the server will be notified about a player leveling up")

config.text("""
// ****************************
// ****************************
// ******** XP SETTINGS *******
// ****************************
// ****************************""", False)
unfairAdvantage = config.cvar('srpg_disableUnfairAdvantageXp', 1, "If there are 0 enemies on the opposite team, enabling this stops players from achieving experience")
killXp          = config.cvar('srpg_killXp', 15, 'The amount of experience gained from a kill multiplied by the victim\'s level')
headshotXp      = config.cvar('srgp_headshotXp',      50, 'The amount of additional xp received for a headshot kill')
bombPlantXp     = config.cvar('srpg_bombPlantXp',     25, 'The amount of experience received from planting the bomb multiplied by the user\'s level')
bombExplodeXp   = config.cvar('srpg_bombExplodeXp',   25, 'The amount of experience received from waiting until the bomb exploded multiplied by the user\'s level')
bombDefuseXp    = config.cvar('srpg_bombDefuseXp',    25, 'The amount of experience received from defusing the bomb')
hostageFollowXp = config.cvar('srpg_hostageFollowXp', 25, 'The amount of experience received by making a hostage follow you (per hostage) multiplied by the user\'s level')
hostageRescueXp = config.cvar('srpg_hostageRescueXp', 25, 'The amount of experience received by rescuing a hostage (per hostage) multiplied by the user\'s level')
startXp         = config.cvar('srpg_startXp', 250, 'The amount of experience needed to level up the first time')
xpIncrement     = config.cvar('srpg_xpIncrement',    50, 'After starting, this is the incremental amount of experience from level to level')
startCredits    = config.cvar('srpg_startCredits',    5, 'The amount of credits players begin with')
creditsReceived = config.cvar('srpg_creditsReceived', 5, 'The amount of credits received each level up')
sellPercentage  = config.cvar('srpg_sellPercentage', 75, 'The percentage of credits received when selling credits')

damageXp        = config.cvar('srpg_damageXp', 1.0, 'The amount of experience gained for each point of damage dealt')

config.text("""
// This section will be for adding specific weapons to the damage table.
// If the weapon is not specified here, then each damage dealt with that weapon
// will be damageXp. For example, if you want every damage with a knife
// to gain you 8 experience, then you can specify that here. You can also
// specify the amount of additional experience gained from a kill with this
// weapon. For example if you wanted a knife to gain you 8 experience for
// each damage dealt, and an additonal 50 experience for killing a player,
// the command would be as follows:
//
// srpg addweaponxp knife 8 50
//
// The last parameter (the kill xp) is optional so you can ommit it if you wish.
//
// srpg addweaponxp <weapon name> <amount of xp per damage dealt> [optional: amount of xp per kill]
\
""", False)
config.command("srpg")
config.text("""\n// End of specific weapon commands here""", False)

config.text("""
// ****************************
// ****************************
// ***** GENERAL SETTINGS *****
// ****************************
// ****************************""", False)
maxLevel       = config.cvar('srpg_maxLevel', 999, "The maximum level possible before a player can no longer achieve more experience, 0 to disable")
maxLevelReset  = config.cvar('srpg_maxLevelReset', 1, "When a player reaches the maximum level, will their stats be reset?")
serverMaxLevel = config.cvar('srpg_serverMaxLevel', 9990, "When the top 10 players total levels add together to make this level, the servers database will reset. 0 to disable")
popupStatus    = config.cvar('srpg_popupStatus', 1, "Whether or not a player\'s popup to upgrade their stats will appear when they level up")
inactivityCounter = config.cvar('srpg_inactivityCounter', 7, "The amount of days a player can be inactive before they are removed from the database")

config.text("""
// ****************************
// ****************************
// ****** SOUND SETTINGS ******
// ****************************
// ****************************""", False)
skillUpgradeSound   = config.cvar('srpg_skillUpgradeSound',   "ambient/machines/machine1_hit2.wav", 'The sound played when a player upgrades a skill, use "" for no sound')
skillDowngradeSound = config.cvar('srpg_skillDowngradeSound', "vehicles/apc/apc_shutdown.wav", "The sound played when a player downgrades a skill, use \"\" to disable")
levelupSound        = config.cvar('srpg_levelupSound',        "ambient/energy/whiteflash.wav", "The sound played when a player levels up, use \"\" to disable")

config.text("""
// ****************************
// ****************************
// **** DATABASE SETTINGS *****
// ****************************
// ****************************""", False)
saveType   = config.cvar('srpg_saveType', "round end", "\"round end\" or \"intervals\". Use \"intervals\" for a deathmatch server (see below) otherwise use \"round end\"")
saveLength = config.cvar('srpg_saveLength', 120, "This only applies if saveType is set to intervals. This is the amount of time in seconds that the database will save.")

config.text("""
// ****************************
// ****************************
// ******* TURBO MODE *********
// ****************************
// ****************************""", False)
turboMode = config.cvar('srpg_turboMode', 0, "Whether or not turbo mode is enabled")
turboModeAnnounce = config.cvar('srpg_turboModeAnnounce', 1, "If turbo mode is on, will a message be sent at spawn tell them about it")
turboXpMultiplier = config.cvar('srpg_turboXpMultiplier', 3, "The amount that the experience is multiplied by in turbo mode")
turboCreditMultiplier = config.cvar('srpg_turboCreditMultiplier', 2, "The amount that the credits gained are multiplied by in turbo mode.")

config.text("""
// ****************************
// ****************************
// ********** BOTS ************
// ****************************
// ****************************""", False)
botMaxLevel = config.cvar('srpg_botMaxLevel', 500, "The maximum level of bots, 0 to not have a maximum level")
botsGetXpVsHumans = config.cvar('srpg_botsGetXpVsHumans', 1, "Do bots receive experience when attacking other humans?")
humansGetXpVsBots = config.cvar('srpg_humansGetXpVsBots', 1, "Do humans receive experience when attacking other bots?")
botsGetXpVsBots   = config.cvar('srpg_botsGetXpVsBots',   1, "Do bots receive experience when attacking other bots?")
botsGetXpOnEvents = config.cvar('srpg_botsGetXpOnEvents', 1, "Do bots receive experience on events such as bomb plant and bomb defuse etc?")

config.write()
#config.execute()

# Make Public Variable
es.ServerVar('sourcerpg', info.version, 'SourceRPG Version - Made by Freddukes').makepublic()
turboMode.makepublic()

""" Exceptions """
class DatabaseError(Exception):
    """ Executed when we encounter a logic run time error with the database class """
    pass
    
class PlayerError(Exception):
    """ Executed when we encounter a logic run time error with the player class """
    pass
    
class SkillError(Exception):
    """ Executed when we encounter a logic run time error with the skill class """
    pass
    
class AddonError(Exception):
    """ Executed when we encounter a logic run time error with the addon class """
    pass
    
""" Database class """

class SQLiteManager(object):
    """
    This class manages all database connections. The main idea of this object
    is to create an easy interface so we can easilly and safely query values
    from a database witout worrying about convulent and complex sqlite syntax.
    
    This interface allows us to safely call and remove objects from the database
    so that users do not have to access the database directly, but through
    a simple API structure.
    
    We should only have one database instance so it is unnecesarry to create
    another object to hold the instances of this object.
    """
    skills  = []
    players = []
    def __init__(self, path):
        """
        Default Constructor
        
        Initialize all the default values and open a connection with the SQLite
        database. Create the tables if they don't exist.
        
        @PARAM path - the absolute path of the database 
        """
        debug.write('[SourceRPG] SQLiteManager constructor, path: %s' % path, 0, True)
        self.path       = path 
        self.connection = sqlite.connect(path)
        debug.write('[SourceRPG] SQLite Connected', 0, True)
        self.connection.text_factory = str
        self.cursor     = self.connection.cursor()
        
        self.cursor.execute("PRAGMA journal_mode=OFF")
        self.cursor.execute("PRAGMA locking_mode=EXCLUSIVE")
        self.cursor.execute("PRAGMA synchronous=OFF")
        
        """ Create the table to hold the players global stats """
        self.cursor.execute("""\
CREATE TABLE IF NOT EXISTS Player (
    UserID  INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
    steamid VARCHAR(30) NOT NULL, 
    level   INTEGER DEFAULT 1, 
    xp      INTEGER DEFAULT 0, 
    credits INTEGER DEFAULT 5, 
    popup   INTEGER DEFAULT 1, 
    name    VARCHAR(30) DEFAULT 'default', 
    lastconnected TEXT
)""")
        
        """ Create the table to hold a link between a row id and a list of skill names """
        self.cursor.execute("""\
CREATE TABLE IF NOT EXISTS Skill (
    SkillID INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, 
    name    VARCHAR(30) NOT NULL,
    UserID  INTEGER NOT NULL,
    level   INTEGER DEFAULT 0
)""")
        
        self.cursor.execute("CREATE INDEX IF NOT EXISTS StatIndex   ON Skill(UserID);")
        self.cursor.execute("CREATE INDEX IF NOT EXISTS PlayerIndex ON Player(SteamID);")
        debug.write('[SourceRPG] All tables created', 0, True)
        
    def __del__(self):
        """
        Default deconstructor.
        
        Executed when a database instance is destroyed. Ensure that the database
        is saved and closed.
        """
        self.save()
        self.close()
        
    def __contains__(self, key):
        """
        Executed automatically when we attempt to test to see if an element exists
        within the database.
        
        @PARAM key - The value to test for validity
        @RETURN boolean - Whether or not the value exists
        """
        key = str(key)
        if key in self.players:
            return True
        self.execute("SELECT level FROM Player WHERE steamid=?", key)
        result = self.cursor.fetchone()
        if bool( result ):
            self.players.append(key)
            return True
        return False
        
    def __iter__(self):
        """
        Executed automatically when we attempt to iterate through an instance
        of this class. Query all the steamids from the playerstats table and
        yield each steamid as a seperate object.
        
        @RETURN yield object - string objects which represent player's steamids
        """
        self.execute("SELECT steamid FROM Player")
        for steamid in self.cursor.fetchall():
            yield steamid[0]
        
    def execute(self, parseString, *args):
        """
        A wrapper function to simulate the execute() method of a cursor object.
        
        @PARAM parseString - the string query line of a SQLite statement
        """
        debug.write('[SourceRPG] Executing SQL String: %s' % parseString, 1, True)
        self.cursor.execute(parseString, args)
        debug.write('[SourceRPG] SQL String executed successfully', 2, True)
        
    def addPlayer(self, steamid, name):
        """
        Add a player to the database and ensure that they exist within the
        playerstats table as well as the playerskills table.
        
        @PARAM steamid - the string value of a players steamid
        @PARAM name - the string value of a players name
        """
        self.execute("INSERT INTO Player (steamid, popup, credits, name, lastconnected) VALUES (?,?,?,?,?)", steamid, int(popupStatus), int(startCredits), name, int(time.time()))
        return self.cursor.lastrowid
            
    def getUserIdFromSteamId(self, steamId):
        """
        Return the UserID auto increment value from a given steamid
        
        @PARAM steamId - the steamid of the player
        @RETURN INTEGER - the row, if the user doesn't exist, then return None
        """
        self.execute("SELECT UserID FROM Player WHERE steamid=?", steamId)
        value = self.cursor.fetchone()
        if value is None:
            return None
        return value[0]
            
    def addSkillIntoPlayerDatabase(self, userid, name, level = 0):
        """
        Adds a new skill into a players database with a certain base level
        
        @PARAM steamid - the steamid of the player who you wish to insert the skill for
        @PARAM name    - the name of the skill
        @PARAM level   - the level that the user starts off with
        @RETURN integer - the SkillID.
        """
        if not isinstance(userid, int):
            userid = self.getUserIdFromSteamId(userid)
        self.execute("INSERT OR IGNORE INTO Skill (UserID, name, level) VALUES (?,?,?)", userid, name, level)
        return self.cursor.lastrowid
        
    def updateSkillForPlayer(self, userid, name, level):
        """
        Updates the skill for a player to a new level
        
        @PARAM userid - the userid database int for the player who you wish to insert the skill for
        @PARAM name   - the name of the skill
        @PARAM level  - the new level of the skill
        """
        if not isinstance(userid, int):
            userid = self.getUserIdFromSteamId(userid)
        self.execute("UPDATE Skill SET level=? WHERE UserID=? AND name=?", level, userid, name)
            
    def checkPlayerSkillExists(self, userid, skillName):
        """
        Checks to see if a player has a level instance for the a certain skill
        
        @PARAM userid    - the integer userid of a player to check
        @PARAM skillName - the string name of the skill to check
        @RETURN boolean  - whether or not the skill exists for the player
        """
        if not isinstance(userid, int):
            userid = self.getUserIdFromSteamId(userid)
        self.execute("SELECT level FROM Skill WHERE UserID=? AND name=?", userid, skillName)
        return bool( self.fetchone())
        
    def getPlayerStat(self, userid, statType):
        """
        Returns a players attribute from the playerstats table.
        
        @PARAM userid   - the integer userid of the player your wish to check
        @PARAM statType - the column name of the value you wish to return
        @RETURN object  - returns an object type of the value to which statType returns
        """
        if not isinstance(userid, int):
            userid = self.getUserIdFromSteamId(userid)
        statType = str(statType).replace("'", "''")
        if hasattr(statType, "__len__"):
            query = "SELECT " + ",".join( map( str, statType)) + " FROM Player WHERE UserID=?"
        else:
            query = "SELECT " + str( statType ) + " FROM Player WHERE UserID=?"
        self.execute(query, userid)
        return self.fetchone()
        
    def getSkillLevel(self, userid, skillName):
        """
        Returns a players level of a certain skill
        
        @PARAM steamid   - the integer representation of a players userid
        @PARAM skillName - the string name of a certain skill
        @RETURN integer  - an integral value which represents the level of the players skill
        """
        if not isinstance(userid, int):
            userid = self.getUserIdFromSteamId(userid)

        self.execute("SELECT level FROM Skill WHERE UserID=? AND name=?",
                                userid, skillName)
        value = self.cursor.fetchone()
        if value is None:
            return None
        return value[0]
        
    def update(self, table, primaryKeyName, primaryKeyValue, options):
        """
        Sets certain values to a new amount referenced by a table, primary key
        and primary key value
        
        @PARAM table - the table of which this should take place
        @PARAM primaryKeyName - the name of the key you wish to test for equality
        @PARAM primaryKeyValue - the options to update where the primaryKeyName's value equates to this value
        @PARAM option - dictionary of values, the key is the row to update, the value is the new amount to set the value to
        """
        keys = ""
        if not isinstance(options, dict):
            raise ValueError, "Expected 'options' argument to be a dictionary, instead received: %s" % type(options).__name__
        if options:
            for key, value in options.iteritems():
                if isinstance(key, str):
                    key   = key.replace("'", "''")

                if isinstance(value, str):
                    value = value.replace("'", "''")
                keys += "%s='%s'," % (key, value)
            keys = keys[:-1]
            query = "UPDATE " + str(table) + " SET " + keys + " WHERE " + str(primaryKeyName) + "='" + str(primaryKeyValue) + "'"
            self.execute(query)
        
        
    def increment(self, table, primaryKeyName, primaryKeyValue, options):
        """
        Increments certain values by a positive amount referenced by a 
        table, primary key and primary key value
        
        @PARAM table - the table of which this increment should take place
        @PARAM primaryKeyName - the name of the key you wish to test for equality
        @PARAM primaryKeyValue - the options to update where the primaryKeyName's value equates to this value
        @PARAM option - dictionary of values, the key is the row to update, the value is the amount to increment the current value by
        """
        keys = ""
        if not isinstance(options, dict):
            raise ValueError, "Expected 'options' argument to be a dictionary, instead received: %s" % type(options).__name__
        for key, value in options.iteritems():
            if isinstance(key, str):
                key   = key.replace("'", "''")
            if isinstance(value, str):
                value = value.replace("'", "''")
            keys += "%s=%s+%i," % (key, key, value)
        keys = keys[:-1]
        self.execute("UPDATE ? SET %s WHERE ?=?+?" % keys, table, primaryKeyName, primaryKeyName, primaryKeyValue)
        
    def query(self, table, primaryKeyName, primaryKeyValue, options):
        """
        Queries results from the table for one person and returns either a 
        tuple or a single value depending on the amount of values passed.
        
        @PARAM table - the table of which this query should take place
        @PARAM primaryKeyName - the name of the key you wish to test for equality
        @PARAM primaryKeyValue - the options to update where the primaryKeyName's value equates to this value
        @PARAM options - either a single value or a tuple which we will get the values of
        """
        
        """ Test if the value passed in options is of several values """
        if hasattr(options, "__len__"):
            query = "SELECT " + ",".join( map(lambda x: str(x).replace("'", "''"), options)) + " FROM " + table \
                    + " WHERE " + primaryKeyName + "='" + primaryKeyValue + "'"
        else:
            query = "SELECT " + str(options).replace("'", "''") + " FROM " + table + \
                    " WHERE " + primaryKeyName + "='" + primaryKeyValue + "'"
                    
        """ Execute the SQL statement and fetch the result """
        self.execute(query)
        return self.fetchone()
        
    def fetchall(self):
        """
        Mimics the sqlite fetchall method which recides within a cursor object.
        Ensures that the true values are added to a list so that we don't need
        to index it if the value is only one item in length (e.g. item instead
        of (item,)...)
        
        @RETURN list - attributes from which the query returned 
        """
        debug.write("[SourceRPG] Handling fetchall", 2)
        trueValues = []
        for value in self.cursor.fetchall():
            if isinstance(value, tuple):
                if len(value) > 1:
                    tempValues = []
                    for tempValue in value:
                        if isinstance(tempValue, long):
                            tempValue = int(tempValue)
                        tempValues.append(tempValue)
                    trueValues.append(tempValues)
                else:
                    if isinstance(value[0], long):
                        trueValues.append(int(value[0]))
                    else:
                        trueValues.append(value[0])
            else:
                if isinstance(value, long):
                    value = int(value)
                trueValues.append(value)
        for value in trueValues:
            debug.write("Result: %s" % value, 4)
        debug.write("[SourceRPG] Returning the result", 2)
        return trueValues
        
    def fetchone(self):
        """
        Mimics the sqlite fetchone method which recides within a cursor object.
        Ensures that a single value is returned from the cursor object if only
        one object exists within the tuple from the query, otherwise it returns
        the query result
        
        @RETURN Object - the result from the query command
        """
        debug.write("[SourceRPG] Fetching one object value", 2)
        result = self.cursor.fetchone()
        debug.write("The initial value is: %s" % (result,), 2)
        if hasattr(result, "__iter__"):
            debug.write("Attribute has iterable, test to see if there's only 1 value", 2)
            if len(result) == 1:
                trueResults = result[0]
                if isinstance(trueResults, long):
                    trueResults = int(trueResults)
                return trueResults
            
            else:
                debug.write("Single result attribute", 2)
                trueResults = []
                for trueResult in result:
                    if isinstance(trueResult, long):
                        trueResult = int(trueResult)
                    trueResults.append(trueResult)
                return trueResults

        if isinstance(result, long):
            result = int(result)
        debug.write("Result: %s" % result, 3)
        debug.write("[SourceRPG] Returning the value of the object", 2)
        return result    
        
    def save(self):
        """
        Commits the database to harddrive so that we can load it if the server
        closes.
        """
        debug.write("[SourceRPG] Handling SQL Save", 1)
        if self.path != ":memory:":
            debug.write("Path is not in memory", 2, False)
            if currentTurboMode is False:
                debug.write("We are not in turbo mode", 2, False)
                self.connection.commit()
        debug.write("[SourceRPG] SQL Save handled", 1)
        
    def clear(self, saveDatabase = True):
        """
        Deletes all instance within the tables of the databse.
        
        @PARAM saveDatabase - optional value, if True, it will commit the database
        """
        debug.write("[SourceRPG] Clearing database", 1)
        players.clearList()
        self.execute("DROP TABLE Player")
        self.execute("DROP TABLE Skill")
        if saveDatabase:
            self.save()
        self.__init__(self.path)
        for player in es.getUseridList():
            players.addPlayer(player)
        debug.write("[SourceRPG] Database cleared", 1)
        
    def close(self):
        """
        Closes the connections so that no further queries can be made.
        """
        debug.write("[SourceRPG] handling SQL close", 1)
        self.cursor.close()
        self.connection.close()
        debug.write("[SourceRPG] SQL close handled", 1)

""" Skills interface """

class SkillManager(object):
    """
    This class will be used as a singleton to keep control over the numerous
    skill objects. It allows us to keep a track over them, and to create
    generic functions to control, overlook and manage the skill objects. 
    """
    def __init__(self):
        """ Default constructor, initialize variables """
        self.skills = {}
        self.orderedSkills = []
    
    def __del__(self):
        """ Default deconstructor, clear the variables """
        self.clearList()
    
    def __getitem__(self, skillName):
        """
        Allows us index the singleton in a similar fashion to a dictionary object
        
        @PARAM skillName - the name of the skill object to return
        @RETURN SkillObject - the object of the skill whose name is skillName
        """
        return self.getSkill(skillName)
        
    def __delitem__(self, skillName):
        """
        Allows us to delete an instance from the instance by an index simulating
        removing the key
        
        @PARAM skillName - the name of the skill to remove
        """
        self.removeSkill(skillName)
    
    def __contains__(self, skillName):
        """
        Executed automatically when we test if a skillname is in the singleton:
        
        @RETURN bool, whether or not the skill is in
        """
        return bool(skillName in self.orderedSkills)
        
    def __iter__(self):
        """
        Executed automatically when we iterate through the singleton
        
        @RETURN SkillObject instances - all instances currently loaded
        """
        for skillName in self.orderedSkills:
            yield self.skills[skillName]
    
    def addSkill(self, skillName, maxLevel, creditStart, creditIncrement):
        """
        Add a SkillObject to the class dictionary referenced by the name
        
        @PARAM skillName - the string name of the skill
        @PARAM maxLevel - integral value of the maximum level of this skill
        @PARAM creditStart - integral value, how many credits to purchase level 1
        @PARAM creditIncrement - integral value, after level 1, how much to purshase all thereonafter
        """
        self.skills[skillName] = SkillObject(skillName, maxLevel, creditStart, creditIncrement)
        self.orderedSkills.append(skillName)
        
    def getSkill(self, skillName):
        """
        Returns a SkillObject() from the dictionary attribute if it exists
        
        @PARAM skillName - the string representation of the SkillObject instance
        @RETURN SkillObject
        """
        if self.__contains__(skillName):
            return self.skills[skillName]
        return None
        
    def removeSkill(self, skillName):
        """ 
        Removes a skill instance from the database attribute, 
        calls the SkillObject's deconstructor
        
        @PARAM skillName - the string name of the skill you wish to remove
        """
        if self.__contains__(skillName):
            del self.skills[skillName]
            del self.orderedSkills[self.orderedSkills.index(skillName)]
        
    def clearList(self):
        """ Loop through the dictionary attribute and remove each skill object """
        self.skills.clear()
        del self.orderedSkills[:]

class SkillObject(object):
    """
    This class stores all information about a skills object. This skill object
    will be a singleton in each of the skill's scripts and it gives access to
    skill relevenant information.
    """
    def __init__(self, skillName, maxLevel, creditStart, creditIncrement):
        """
        Default constructor, assign all skill variables here
        
        @PARAM skillName - the string name of the skill
        @PARAM maxLevel - integral value of the maximum level of this skill
        @PARAM creditStart - integral value, how many credits to purchase level 1
        @PARAM creditIncrement - integral value, after level 1, how much to purshase all thereonafter
        """
        self.name     = skillName
        self.maxLevel = maxLevel
        self.info     = None
        self.startCredit     = creditStart
        self.creditIncrement = creditIncrement
        
    def __str__(self):
        """ 
        Automatically executed upon string conversion.
        
        @RETURN string - the name of the skill
        """
        return self.name
    
    def config(self):
        """
        A wrapper function to return the skillConfig singleton
        
        @RETURN ConfigurationObeject - singleton
        """
        return skillConfig
        
class AddonManager(object):
    """
    A class to access and save multiple AddonObjects so we only have
    single instances which we can retrieve at any given point
    """
    def __init__(self):
        """ Default constructor - initialize the container. """
        self.addons = {}
        
    def __del__(self):
        """
        Default deconstructor - deconstruct the addons list, which in turn
        calls the deconstructor on all of the AddonObject's instances.
        """
        del self.addons
    
    def __iter__(self):
        """
        Iterate through the addon container instnace and yield each AddonObject
        instance.
        
        @RETURN yield object - each AddonInstance
        """
        for addon in self.addons.itervalues():
            yield addon
            
    def __contains__(self, addonName):
        """
        x in singleton <==> singleton.__contains__(x)
        Executed automatically when we test for the existance of an item within
        the singleton instance.
        
        @PARAM addonName - the name of the addon
        @RETURN boolean - whether or not the addon exists
        """
        return bool(addonName in self.addons)
        
    def __getitem__(self, addonName):
        """
        singleton[x] <==> singleton.__getitem__(x)
        Executed when we attempt to index the singleton. Return the AddonObject
        if the addon exists
        
        @PARAM addonName - the name of the addon
        @RETURN AddonObject instance - the instance which resides by the addon name
        """
        if self.__contains__(addonName):
            return self.addons[addonName]
        raise AddonError, "Addon %s does not exist" % addonName
        
    def __delitem__(self, addonName):
        """
        del singelton[x] <==> singleton.__delitem__(x)
        Executed when we attempt to remove an item from the singleton by indexing.
        In this case, we wish to remove the addonName from the addon container.
        
        @PARAM addonName - the string name of the addon
        """
        if not self.__contains__(addonName):
            raise AddonError, "Addon %s does not exist" % addonName
        del self.addons[addonName]
        
    def addAddon(self, addonName):
        """
        Insert the addon name into the addons container as an AddonObject instnace
        
        @PARAM addonName - the string name of the addon
        """
        if self.__contains__(addonName):
            raise AddonError, "Addon %s already exists" % addonName 
        self.addons[addonName] = AddonObject(addonName)
        
    def removeAddon(self, addonName):
        """
        This function is a wrapper for the __delitem__ method since they both
        naturally do the same thing.
        
        @PARAM addonName - the string name of the addon
        """
        self.__delitem__(addonName)
        
    def getAddon(self, addonName):
        """
        This function is a wrapper for the __getitem__ method since they both
        naturally do the same thing.
        
        @PARAM addonName - the string name of the addon 
        """
        return self.__getitem__(addonName)
        
    def clearList(self):
        """
        This function clears the list of the AddonObjects - it will call the
        deconstructor on each of the elements
        """
        self.addons.clear()
    
class AddonObject(object):
    """
    Since addons don't naturally modify the game automatically, at this
    current time, the only thing we'll have is a constructor. The reason
    I have left this class 'blank' is if in case we need to revisit the
    addon interface and add new default methods / attributes which can be shared
    throughout all of the addons - e.g. a single configuration file similar
    to the SkillObject class
    """
    def __init__(self, addonName):
        """ 
        Default constructor, register the addonName
        
        addonName - the string name of the addon
        """
        self.name = addonName

class PlayerManager(object):
    """
    This class acts as a container for all the PlayerObject instances but gives
    access to several other features such as the ability to iterate through
    and index the instance
    """
    def __init__(self):
        """ Default constructor, initialize the dictionary to hold the instances """
        self.players = {}
        
    def __getitem__(self, userid):
        """ 
        Automatically executed upon subscript. If the userid exists, return the
        PlayerObject instance
        
        @PARAM userid - the user that you wish to get the PlayerObject instance for
        @RETURN PlayerObject - the instance of that is relevant to the userid
        """
        userid = int(userid)
        if self.__contains__(userid):
            return self.players[userid]
        return None
        
        
    def __delitem__(self, userid):
        """
        del players[<userid>] <==> __delitem__(userid)
        
        Automatically executed when we attempt to delete an item from the
        container. Ensure that the userid object is removed
        
        @PARAM userid - the user to remove from the container
        """
        self.removePlayer(userid)
        
    def __iter__(self):
        """
        Automatically executed when returning all elements within a for loop.
        
        @RETURN yield object, all PlayerObject instance.
        """
        for player in self.players:
            yield self.players[player]
            
    def __contains__(self, userid):
        """
        Executed when testing for validity to see if this container contains
        a userid
        
        @PARAM userid - the user to test for appearance
        """
        userid = int(userid)
        return bool(userid in self.players)
        
    def addPlayer(self, userid):
        """
        A function to add a player into the container and the relevant
        PlayerObject instance as it's key value.
        
        @PARAM userid - the user to add to the container object 
        """
        self.players[int(userid)] = PlayerObject(userid)
        
    def removePlayer(self, userid):
        """
        Removes a user from the container within the class
        
        @PARAM userid - the userid to remove
        """
        userid = int(userid)
        if self.__contains__(userid):
            del self.players[userid].command 
            # we have to manually delete the underlying object so we have no other references to PlayerObject class.
            del self.players[userid] # calls deconstructor on PlayerObject class
            
    def getPlayer(self, userid):
        """
        Returns the player object referenced by the userid in the container
        
        @PARAM userid - the userid to return the PlayerObject instance of 
        """
        return self.__getitem__(userid)
        
    def clearList(self):
        """
        Removes all of the PlayerObjects from within the container, calls
        the destructor on each of the PlayerObject instances.
        """
        self.players.clear()
        
class PlayerObject(object):
    """ 
    This class manages all local aspects of the player. It allows us to keep a
    local copy of all the attributes from the database without creating new
    queries each time. It ensures that we can access the current values of the
    player's attributes and skills very safely and efficiently without modifying
    the database.
    """
    def __init__(self, userid):
        """
        Constructor:
        
        Stores the players userid and steamid then goes on to retrieve the 
        current values from the SQLite database and store them in the relevant
        dictionaries.
        
        @PARAM userid - the userid of the player
        """
        debug.write("[SourceRPG] PlayerObject constructor, userid %s" % userid, 1)
        self.userid   = int( userid )
        self.steamid  = playerlib.uniqueid( userid, True )
        self.name     = es.getplayername( userid )
        self.isbot    = es.isbot( userid )
        self.currentAttributes = {}
        self.oldAttributes     = {}
        self.currentSkills = {}
        self.oldSkills     = {}
        gamethread.delayed(0, self.setCommand)
        self.dbUserid = database.getUserIdFromSteamId(self.steamid)
        if self.dbUserid is None:
            self.dbUserid = database.addPlayer(self.steamid, self.name)
        self.update()
        self.playerAttributes = {}
        self.resetPlayerDefaultAttributes()
        debug.write("[SourceRPG] PlayerObject created for player: %s" % self.name, 1)
 
    def setCommand(self):
        """
        This function sets the command instance to hold a new CommandsDatabase
        instnace. We need to delay it by a tick so that this reference has been
        fully created before we use it.
        """
        self.command = CommandsDatabase(self.userid)
 
    def __del__(self):
        """
        Default deconstructor
        
        Ensure that the players stats are updated to the database when the player
        instance is removed.
        """
        debug.write("[SourceRPG] PlayerObject destructor for player %s (userid: %s)" % (self.name, self.userid), 1)
        self.commit()
        debug.write("[SourceRPG] PlayerObject successfully destroyed", 1)
        
    def __int__(self):
        """
        Automatically executed when an instance attemtps to be statically converted
        to a integer, return the integral representation of the players userid
        """
        return self.userid
        
    def __str__(self):
        """
        Automatically executed when an instance attemtps to be statically converted
        to a string, return the string representation of the players userid
        """
        return str(self.userid)
        
    def __getattr__(self, attribute):
        """
        A short wrapper to allow us to return functions from the CommandDatabase
        as a local function.
        
        @PARAM attribute - the attribute as a string name
        """
        debug.write("[SourceRPG] Accessing attribute outside of the PlayerObject scope", 4)
        if attribute in vars(CommandsDatabase):
            debug.write("[SourceRPG] Returning a command link to the CommandsDatabase object", 4)
            return self.CommandLink(attribute, self.command)
        else:
            debug.write("[SourceRPG] Attribut %s not found", 1)
            raise AttributeError, "Function or Attribute %s cannot be found" % attribute
            
    class CommandLink(object):
        """
        This class acts as a gateway between this object instance and the
        player's CommandsDatabase object, making it seem like we can call all
        the command database as local objects.
        """
        def __init__(self, function, instance):
            """
            Default constructor, assign variables so we can use them later on
            
            @PARAM function - the string name of the function we wish to execute
            @PARAM instance - the instance of the CommandsDatabase() object
            """
            self.instance = instance
            self.function = function
            
        def __call__(self, *args, **kw):
            """
            Executed when the class is used as a function. Execute the string
            function within the CommandsDatabase passing the instance, arguments
            and key words.
            
            @PARAM OPTIONAL *args - list of arguments to unpack
            @PARAM OPTIONAL **kw - dictionary of optional arguments to unpack 
            """
            debug.write("[SourceRPG] Executing the function %s within the CommandsDatabase" % self.function, 4)
            vars(CommandsDatabase)[self.function](self.instance, *args, **kw)
            
        
    def __getitem__(self, item):
        """
        Allows the class instance to be indexed similarly to a dictionary.
        Return the item in currentAttributes or currentSkills if they exist,
        otherwise return None.
        
        @PARAM item - the item of which you'd wish to retrieve from the dictionaries.
        """
        debug.write("[SourceRPG] Handling retrieving of attribute %s for player %s" % (item, self.name), 3)
        if item in self.currentAttributes:
            debug.write("Item is in current attributes, return", 4)
            return self.currentAttributes[item]
        if item in self.currentSkills:
            debug.write("Item is a skill, return skill level", 4)
            level = self.currentSkills[item]
            if item in skills:
                if level > int(skills[item].maxLevel):
                    level = int(skills[item].maxLevel)
            return level
        if item in self.playerAttributes:
            debug.write("The item is an attribute, return from the local cache", 4)
            return self.playerAttributes[item]
        if item in skills:
            """ 
            The item is a skill, however, the user hasn't got a database column
            yet and rather than create one because we don't need it yet, we can
            just return 0
            """
            debug.write("Skill not in player's cache, but is loaded, assume no level", 4)
            return 0
        debug.write("Value not found, return 0", 3)
        return None
        
    def __setitem__(self, item, value):
        """
        Allows us to set information about a player, database relevancy
        takes preference over the playerAttributes dictionary
        
        @PARAM item - the item to set the value to
        @PARAM value - the value that item will hold
        """
        debug.write("[SourceRPG] Assigning attribute %s with the value of %s to player %s" % (item, value, self.name), 3)
        if item in self.currentAttributes:
            debug.write("Value is in current attributes, assign to the currentAttributes dict", 4)
            self.currentAttributes[item] = value
        elif item in self.currentSkills or item in skills:
            debug.write("Value is in skills, assign to the currentSkills dict", 4)
            self.currentSkills[item] = value
        else:
            debug.write("Value is not in any dictionary, assign to the custom playerAttributes dict", 4)
            self.playerAttributes[item] = value
        debug.write("[SourceRPG] Value updated", 3)
        
    def resetPlayerDefaultAttributes(self):
        """
        All the default virtual information about a player which is not database
        related should be set here such as maximum speed and maximum health
        """
        debug.write("[SourceRPG] Resetting player %s default attribute" % self.name, 2)
        self.playerAttributes['baseHealth'] = 100
        self.playerAttributes['maxHealth']  = 100
        self.playerAttributes['maxSpeed']   = 1.0
        self.playerAttributes['maxGravity'] = 1.0
        self.playerAttributes['minStealth'] = 255
        self.playerAttributes['maxArmor']   = 100
        debug.write("[SourceRPG] Attributes reset", 2)
        
    def getSkillLevel(self, skillName):
        """
        Return the level of a certain skill. If it doesn't exist the return 0
        
        @PARAM skillName - the string name of the skill to return the value of
        """
        if skillName in self.currentSkills:
            return self.currentSkills[skillName]
        return 0
        
    def commit(self):
        """
        This method takes the current values and tests them against the original
        values retrieved from the database. This will ensure that only modified
        values will be uploaded thus saving resources by constantly querying the
        database. If turbo mode is on the database will not commit.
        """
        debug.write("[SourceRPG] Committing player stats", 2)
        if not currentTurboMode:
            """ Update the player's generic static stats """
            for key, value in self.currentAttributes.iteritems():
                debug.write("Testing cache on %s" % key, 3)
                if key in self.oldAttributes:
                    # We only want to update current attributes.
                    debug.write("Cache is old, needs updating to db", 4)
                    if value <> self.oldAttributes[key]:
                        database.execute("UPDATE Player SET %s=? WHERE UserID=?" % key,
                                                  value,
                                                  self.dbUserid)       
                    debug.write("Cache updated", 4)    
            """ Update the skills """
            for key, value in self.currentSkills.iteritems():
                debug.write("Testing cache on %s" % key, 3)
                if key not in self.oldSkills:
                    debug.write("Skill does not exist, insert into DB", 3)
                    database.execute("INSERT INTO Skill (name, UserID, level) VALUES (?,?,?)",
                                             key,
                                             self.dbUserid,
                                             value)
                else:
                    debug.write("Skill updated, update DB", 4)
                    if value <> self.oldSkills[key]:
                        database.execute("UPDATE Skill SET level=? WHERE UserID=? AND name=?",
                                                     value,
                                                     self.dbUserid,
                                                     key )
                    debug.write("Cache updated", 4)
            
            """ Make sure the old attributes are updates """
            self.oldAttributes = self.currentAttributes.copy()
            self.oldSkills = self.currentSkills.copy()
            debug.write("Local cache objects updated to the new changes", 2)
        debug.write("[SourceRPG] Player stats commited", 2)
        
    def update(self):
        """
        This method gets the latest information from the sqlite database
        and updates it to the old and new instances within this class.
        
        If turbo mode is on, then it will make up default values.
        """
        debug.write("Querying stats for player %s" % self.name, 3)
        debug.write("Is turbo mode on? %s" % ({True:"Yes", False:"No"}[currentTurboMode]), 3)
        if not currentTurboMode:
            database.execute("SELECT * FROM Player WHERE UserID=?", self.dbUserid)
                    
            """ 
            ~NOTE: Nasty, explicitly defining may break in future updates if I
            alter the DB structure, think of a new way to alter this.
            
            Thoughts:
            
            Make a list of the structure, then call the list and update the oldAttributes
            and currentAttributes with items from the list.
            """
            result = database.fetchone()
            UserID, steamid, level, xp, credits, popup, name, lastconnected = result

            debug.write("Steamid: %s" % steamid, 3)
            debug.write("DB UserID: %s" % UserID, 3)
            debug.write("xp: %s" % xp, 3)
            debug.write("Credits %s" % credits, 3)
            debug.write("Level: %s" % level, 3)
            
            for option in ('steamid', 'level', 'xp', 'credits', 'popup', 'name', 'lastconnected'):
                self.oldAttributes[option] = self.currentAttributes[option] = locals()[option]
                
            """ Retrieve the skills id and level from the database """
            database.execute("SELECT name,level FROM Skill WHERE UserID=?", self.dbUserid )
            
            """ Iterate through all the skills returned """
            for skill in database.fetchall():
                """ Each iteration will produce a 2 itemed tuple with skillrow and level """
                skillName, level = skill

                debug.write("Skill %s :: level %s" % (skillName, level), 3)
                
                """ We only want to add the skill if it's currently loaded """
                self.oldSkills[skillName] = self.currentSkills[skillName] = level
                    
        else:
            """ Turbo mode is on, make the default attributes """   
            defaultValues = {
            "level"   : 1,
            "xp"      : 0,
            "credits" : int(startCredits),
            "popup"   : int(popupStatus),
            "lastconnected" : int(time.time())
            }
            for key, value in defaultValues.iteritems():
                self.oldAttributes[key] = self.currentAttributes[key] = value
        
class CommandsDatabase(object):
    """
    This class should not be accessed directly. It should be accessed through
    the PlayerObject() class method accessing these functions as local functions.
    
    This class is for storing all commands together in a manageable interface
    but still providing access to the PlayerObject class and making it simple
    for the end user
    """
    def __init__(self, userid):
        """
        Default constructor, store the userid and the player instance
        
        @PARAM userid - the id of the user which the commands execute on
        """
        debug.write("[SourceRPG] CommandsDatabase object constructor for userid %s" % userid, 1)
        self.userid = int(userid)
        self.player = players[userid] 
        debug.write("[SourceRPG] CommandsDtabase object created", 1)
    
    def addXp(self, amount, reason = "", tellUserOverride = True):
        """
        This function adds experience to a user. It will chekc their level
        and carry on incrementing their level until no more experience is gained.
        It reassigns the values to the player.
        
        @PARAM amount - the amount of experience to add
        @PARAM OPTIONAL reason - the reason that the experience was added
        @PARAM OPTIONAL tellUserOverride - whether or not that the message will be sent (if False, then it will ignore announceXp server var)
        """
        debug.write("[SourceRPG] Handling addXp function for userid %s" % self.userid, 1)
        if not amount:
            debug.write("No experience given, return early", 1)
            return
        
        """ If turbo mode is on the multiply the experience gained """
        if currentTurboMode:
            debug.write("Turbo mode is on, multiply experience gain", 2)
            amount = int( amount * float(turboXpMultiplier))
            
        oldXp = self.player['xp']
        currentXp  = amount + oldXp
        currentLevel   = self.player['level']
        debug.write("OldXp: %s, currentXp: %s, currentLevel: %s" % (oldXp, currentXp, currentLevel), 2 )
        
        amountOfLevels = 0
        nextLevelXp    = (currentLevel - 1) * int(xpIncrement) + int(startXp)
        
        """ Ensure that multiple levels are added as one instance """
        while currentXp > nextLevelXp:
            amountOfLevels += 1
            currentXp      -= nextLevelXp
            nextLevelXp += int(xpIncrement)
        
        debug.write("Amount of levels gained: %s" % amountOfLevels, 2)
            
        """ If the server owner wishes, tell them the message """
        if tellUserOverride is True and int(announceXp):
            tokens = {}
            tokens['name'] = self.player.name
            tokens['amount'] = amount
            tokens['reason'] = (" for " + reason) if reason else ""
            tell(self.userid, 'xp gained', tokens)
            
        """ Assign the new XP value """
        self.player['xp'] = currentXp
        
        """ Create an fire the gainxp event """
        values = {}
        values["oldxp"] = ("setint", oldXp)
        values["newxp"] = ("setint", currentXp)
        values["userid"] = ("setint", self.userid)
        values["levels"] = ("setint", amountOfLevels)
        values["xpneeded"] = ("setint", nextLevelXp)
        values["reason"] = ("setstring", reason if reason else " ")
        gamethread.delayed(0, fireEvent, ("sourcerpg_gainxp", values))
        
        debug.write("[SourceRPG] addXP handled", 2)
        
        if amountOfLevels:
            self.addLevel( amountOfLevels )
            
    def addLevel(self, amount):
        """
        Increments the user's level and if needed, play a sound, send a message
        and build the skills menu for them
        
        @PARAM amount - the amount of levels to add
        """
        debug.write("[SourceRPG] Handling addLevel", 1)
        self.player['level'] += amount
        
        """ If turbo mode is on multipliy the credits received """
        if currentTurboMode:
            self.player['credits'] += int( amount * int(creditsReceived) * float(turboCreditMultiplier))
        else:
            self.player['credits'] += amount * int(creditsReceived)
        
        """ Check if the level has reached the limit """
        if int(maxLevel) and self.player['level'] > int(maxLevel):
            debug.write("Maximum level reached, ensure that resetSkills", 1)
            """ If we want to reset the skills, reset them """
            if int(maxLevelReset):
                self.resetSkills()
                tell(self.userid, 'maximum level reached')
                debug.write("Levels Reset", 1)
            else:
                """ Othewise assign the level and XP to the maximum possible """
                self.player['level'] = int(maxLevel)
                self.player['xp']    = (self.player['level'] - 1) * int(xpIncrement) + int(startXp) - 1
                debug.write("Assigned XP to maximum value", 1)
        else:        
            """ The level is okay, check for bots and play the message etc """
            if not self.player.isbot:
                debug.write("Player is not a bot", 2)
                """ Only do the following for humans """
                if not int(levelUp):
                    tokens = {}
                    tokens['level']  = self.player['level']
                    tokens['xp']     = self.player['xp']
                    tokens['nextxp'] = (self.player['level'] - 1) * int(xpIncrement) + int(startXp) - self.player['xp']
                    tell( self.userid, 'level gained private', tokens )
                
                if self.player['popup']:
                    debug.write("Building skill menu", 1)
                    buildSkillMenu(self.userid)
            
            else:
                """ Player is a bot, check for the maximum possible level for a bot """
                debug.write("Bot leveled up, choose a random skill", 2)
                if int(botMaxLevel) and self.player['level'] > int(botMaxLevel):
                    debug.write("Reset bot's skills, maximum level achieved", 2)
                    self.resetSkills()
                else:
                    """ Upgrade a random skill if possible """
                    while True:
                        """ Loop until we manually break """
                        possibleChoices = []
                        credits = self.player['credits']
                        for skill in skills:
                            """ 
                            Iterate through all loaded skills and if the bot
                            can afford the skill, append it to the possible choices
                            """
                            if credits >= self.player[skill.name] * skill.creditIncrement + skill.startCredit:
                                if self.player[skill.name] < skill.maxLevel:
                                    possibleChoices.append(skill.name)
                        if not possibleChoices:
                            """ 
                            The bot cannot afford any skills or has maxed out
                            the skills, the manually break
                            """
                            break
                        
                        """ 
                        Finally call the checkSkillForUpgrading function passing
                        the arguments manually rather than letting a popup do it
                        """
                        debug.write("Checking to update a skill", 2)
                        checkSkillForUpgrading(self.userid, random.choice(possibleChoices), None, False )
            
            if int(levelUp):
                tokens = {}
                tokens['name']   = self.player.name
                tokens['level']  = self.player['level']
                tokens['xp']     = self.player['xp']
                tokens['nextxp'] = (self.player['level'] - 1) * int(xpIncrement) + int(startXp)
                
                for userid in filter( lambda x: not es.isbot(x), es.getUseridList()):
                    tell(userid, 'level gained global', tokens)
                
            if str(levelupSound):
                es.emitsound('player', self.userid, str(levelupSound), 0.7, 0.5 )
            
            """ Create and fire the levelup event """
            values = {}
            values["userid"] = ("setint", self.userid)
            values["newlevel"] = ("setint", self.player['level'])
            values["oldlevel"] = ("setint", self.player['level'] - amount)
            values["amount"] = ("setint", amount)
            values["xp"] = ("setint", self.player['xp'])
            values["xpneeded"] = ("setint", (self.player['level'] - 1) * int(xpIncrement) + int(startXp))
            gamethread.delayed(0, fireEvent, ("sourcerpg_levelup", values))
        debug.write("[SourceRPG] Handled addLevel", 1)
                
    def resetSkills(self):
        """
        Reset a player's skills. Just assign all the default skills and attributes
        back to 0 for the player, then slay to ensure that the attributes
        reset correctly
        """
        """ Reset the default attributes """
        self.player['level']   = 1
        self.player['xp']      = 0
        self.player['credits'] = int(startCredits)
        self.player['popup']   = int(popupStatus)
        self.player['name']    = self.player.name
        self.player['lastconnected'] = int(time.time())

        
        """ Iterate through the skills list then set each skill to 0 """
        for skill in skills:
            self.player[skill.name] = 0

        """ Slay the player """
        es.server.queuecmd("damage %s %s" % (self.userid, es.getplayerprop(self.userid, "CBasePlayer.m_iHealth")))
        
        """ Notify the user """
        tell(self.userid, 'info deleted')
        
class RankManager(object):
    """
    This class just saves the sorted top 10 so we can call it at certain stages
    without it 
    """
    def __init__(self, update=False):
        """ Executed when the class is instanced, update the ranks """
        if update is not False:
            self.update()
        
    def __len__(self):
        """
        Called when len(instance) is executed
        
        @RETURN integer - the amount of items the ranks contain
        """
        return len(self.ranks)
        
    def __contains__(self, steamid):
        """
        Called when testing for valadity to see if a steamid is in the list
        
        @PARAM steamid - the steamid to test for
        @RETURN boolean - whether or not the steamid exists within the ranks
        """
        return bool( steamid in self.ranks )
        
    def update(self):
        """
        Updates the ranks with the latest information from the sqlite database
        """
        debug.write("[SourceRPG] Updating all ranked positions", 1)
        database.execute("SELECT steamid FROM Player ORDER BY level DESC,xp DESC")
        results = database.cursor.fetchall()
        self.ranks = []
        for index, steamid in enumerate(results):
            debug.write("Rank: %s Steamid: %s" % (index, steamid), 5)
            self.ranks.append(steamid[0])
        debug.write("[SourceRPG] All ranked positions updated", 1)
        
    def getRank(self, steamid):
        """
        Returns the rank position of a steamid within the ranks
        
        @PARAM steamid - the steamid to test for
        @RETURN integer - the position of the steamid
        """
        if self.__contains__(steamid):
            return self.ranks.index(steamid) + 1
        return self.__len__()
        
    def getPlayerSlice(self, bottom = 0, top = 10):
        """
        Returns the slice of players. If there aren't 10 players, it will return
        all the players.
        
        @RETURN list - of the sliced steamids
        """
        if bottom > top:
            top, bottom = bottom, top
        return self.ranks[bottom:top]
        
class ConfigurationObject(cfglib.AddonCFG):
    """ 
    Create a child class of an AddonCFG class within the cfglib library.
    
    Overwrite the write() and execute() function so they are only called from
    this file rather than giving access to each of the skills as the instance
    only should be executed once after all the files have been written.
    
    Provide a single interface where all configurations should be done together.
    """        
    def addInfo(self, name, information):
        """
        Add information about a specific skill. Create a header and a footer
        which will embedd information about the specfic skill.
        
        @PARAM name - the string name of the skill
        @PARAM information - the information about the skill, accepts multiline 
        """
        gamethread.delayed(0, gamethread.delayed, (0, self.setSkillInfo, (name, information))) # delay by 2 ticks to allow skills to register
        header = "\n%s\n%s\n\n" % ('*' * 50, name.center(50))
        footer = "\n%s" % ("*" * 50)
        information = information.strip() # strip whitespace at begggining and end of lines
        information = (header + information + footer).replace('\n', '\n// ')
        self.text(information, False)
        
    def setSkillInfo(self, name, information):
        """
        This function updates the skills object with the latest information
        
        @PARAM name - The name of the skill to add information to
        @INFORMATION - the information of the skill
        """
        skills[name].info = information
        
    def execute(self, queuecmd = False, internal = False):
        """
        Overwrite the parent class as a virtual function. Append another argument
        so that only the internal script has access to this function (unless
        some other script adds the True value)
        
        @PARAM queuecmd - Append the execution to the queue stack
        @PARAM internal - is this an internal call
        """
        debug.write("[SourceRPG] Executing skills.cfg", 0, True)
        if internal:
            es.server.cmd('exec ' + self.cfgpath.replace(str(cfgPath).replace("\\", "/"), '', 1).lstrip("/"))
            
    def write(self, internal = False):
        """
        Overwrite the parent class as a virtual function. Ensure that only an
        internal call should have access to this file
        
        @PARAM internal - is this an internal call
        """
        debug.write("[SourceRPG] Writing skills.cfg", 0, True)
        if internal:
            cfglib.AddonCFG.write(self)
        
class CommandManager(object):
    """
    A class which contains all relevant server commands as static methods
    """
    def mainCommand(self, args):
        """
        Executed when the sourcerpg server command is issued. The first argument
        should be the string name of the method here, e.g. srpg addxp
        will run the function addxp within this class.
        
        @PARAM args - the arguments of the command
        """
        command = args.pop(0).lower() # calls exception if no arguments present
        if command in vars(CommandManager):
            vars(CommandManager)[command](self, *args) # calls exception if wrong amount of arguments
        
    def addweaponxp(self, weapon, experiencePerDamage, experiencePerKill = 0):
        """
        A server command executed when srpg addweaponxp is found. Add the
        weapon and its relevant information to the weaponXp dictionary
        
        @PARAM weapon - the weapon to add additional experience for
        @PARAM experiencePerDamage - the amount of experience per damage gained
        @PARAM experiencePerKill - the amount of experience gained for killing with this weapon
        """
        weaponXp[weapon.replace('weapon_', '').lower()] = ( int( experiencePerDamage ), int( experiencePerKill))
        
    def addxp(self, userid, amount, *reason):
        """
        A server command to add experience to a certain user
        
        @PARAM userid - the user who to add the xp to
        @PARAM amount - anount of experience to add
        @PARAM OPTIONAL reason - the reason for the experience
        """
        players[userid].addXp( int(amount), " ".join(reason))
            
    def addlevel(self, userid, amount):
        """
        A server command to add levels to a certain user
        
        @PARAM userid - the user who to add th elevels to
        @PARAM amount - the amount of levels to add 
        """
        players[userid].addLevel(amount)
        
    def load(self, skillName):
        """
        A server command to load a skill into this mod
        
        @PARAM skillName - the name of the skill they wish to load
        """
        es.load("%s/skills/%s" % (info.basename, skillName))
        
    def unload(self, skillName):
        """
        A server command to unload a skill from this mod
        
        @PARAM skillName - the name of the skill to unload
        """
        es.unload("%s/skills/%s" % (info.basename, skillName))
        
    def unloadallskills(self):
        """
        This method unloads all current loaded skills
        """
        for skill in skills.skills.copy():
            es.unload("%s/skills/%s" % (info.basename, skill))
        skills.clearList()
    
    def loadallskills(self):
        """
        This metod loads all skills within the skills folder
        """
        for skill in os.listdir( os.path.join( es.getAddonPath( info.basename ), "skills" )):
            es.load("%s/skills/%s" % (info.basename, skill))
            
    def loadaddon(self, addonName):
        """
        A server command to load an addon from the ../sourcerpg/addons
        directory.
        
        @PARAM addonName - the name of the addon to load
        """
        es.load("%s/addons/%s" % (info.basename, addonName))
        
    def unloadaddon(self, addonName):
        """
        A server command to unload an addon from the ../sourcerpg/addons
        directory.
        
        @PARAM addonName - the name of the addon to unload
        """
        es.unload("%s/addons/%s" % (info.basename, addonName))
        
class SayCommandsManager(object):
    """
    This class only acts as a container for several functions so I can seperate
    the main methods from the say command functions.
    
    All functions accept the following parameters:
    
    @PARAM userid - The userid of the person who executed the say command
    @PARAM args   - A list of arguments following the say command 
    """
    @staticmethod
    def mainMenu(userid, args):
        """ Executed when a user types 'rpgmenu' """
        if popuplib.isqueued("sourcerpg_rpgmenu", userid):
            return
        popuplib.send('sourcerpg_rpgmenu', userid)
        
    @staticmethod
    def upgradeMenu(userid, args):
        """
        Executed when a user types 'rpgupgrade'. Update their popup and send 
        it to them 
        """
        buildSkillMenu(userid)
        
    @staticmethod
    def sellMenu(userid, args):
        """
        Executed when a user types 'rpgsell' in chat. Update their sell
        menu then send it to them
        """
        buildSellMenu(userid)
    
    @staticmethod
    def helpMenu(userid, args):
        """
        Executed when a user types 'rpghelp' in chat. Send the help menu to them
        """
        if popuplib.isqueued("sourcerpg_help", userid):
            return
        popuplib.send('sourcerpg_help', userid)
    
    @staticmethod
    def stats(userid, args):
        """
        Executed when a user types 'rpgstats' in chat. Builds a stats menu
        about them and sends it to them.
        """
        if len(args):
            playerToTest = es.getuserid(str(args))
            if playerToTest is None:
                playerToTest = userid
            buildStatsMenu(userid, playerToTest)
        else:
            buildStatsMenu(userid, userid)
        
    @staticmethod
    def rank(userid, args):
        """
        Executed when a user types 'rpgrank' in chat. Tells them the players
        rank or another player's rank.
        """
        testUserid = userid
        if len(args):
            testUserid = es.getuserid(str(args))
            if not es.exists('userid', testUserid):
                testUserid = userid
        player = players[testUserid]
        tokens = {}
        tokens['name']    = player['name']
        tokens['level']   = player['level']
        tokens['xp']      = player['xp']
        tokens['nextxp']  = (player['level'] - 1) * int(xpIncrement) + int(startXp)
        tokens['credits'] = player['credits']
        tokens['rank']    = ranks.getRank(player['steamid'])
        tokens['total']   = len( ranks )
        for tellUserid in es.getUseridList():
            tell(tellUserid, 'rank', tokens)
        
    @staticmethod
    def top10(userid, args):
        """
        Display the top 10 RPG players
        """
        if popuplib.isqueued("sourcerpg_rpgtop5", userid):
            return
        popuplib.send('sourcerpg_rpgtop5', userid)
        
    @staticmethod
    def togglePopup(userid, args):
        """
        This method toggles on or off a players popup status
        """
        player = players[userid]
        player['popup'] = 1 - player['popup']
        
        """ Tell them the message """
        tokens = {}
        tokens['status'] = {1:"on", 0:"off"}[player['popup']]
        tell(userid, 'toggle popup', tokens)
        
class PopupCallbacks(object):
    """
    A popup which contains only static methods to categorize and order all
    popup callbacks.
    
    All functions will be static and except three parameters, they are as follows:
    
    @PARAM userid - the user who chose an option from the menu
    @PARAM choice - the choice that the user picked
    @PARAM popupid - the name of the popup
    """
    @staticmethod
    def rpgmenu(userid, choice, popupid):
        """ Executed when the user selects an option from the main rpgmenu """
        if choice == 1:
            """ Rebuild the skills menu and send it to the user """
            buildSkillMenu(userid)
        elif choice == 2:
            """ Rebuild the sell menu and send it to the user """
            buildSellMenu(userid)
        elif choice == 3:
            """ Send the help menu to the user """
            if popuplib.isqueued("sourcerpg_help", userid):
                return
            popuplib.send('sourcerpg_help', userid)
        elif choice == 4:
            """ Rebuild the stats menu for a specific user and send them the details """
            buildStatsMenu(userid, userid)
        else:
            """ Send them the confirmation popup to ensure they wish to delete their skills """
            if popuplib.isqueued("sourcerpg_confirm", userid):
                return
            popuplib.send('sourcerpg_confirm', userid)
    
    @staticmethod
    def helpmenu(userid, choice, popupid):
        """ This menu is the callback for the help popup option. """
        if choice == 1:
            """ Send the about this mod popup """
            if popuplib.isqueued("sourcerpg_about", userid):
                return
            popuplib.send('sourcerpg_about', userid)
        elif choice == 2:
            """ Send the commands list to the player """
            if popuplib.isqueued("sourcerpg_commands", userid):
                return
            popuplib.send('sourcerpg_commands', userid)
        elif choice == 3:
            """ Build the new skills reference popup and send it to the user """
            buildSkillsReferencePopup(userid)
        elif choice == 4:
            """ Send the menu crediting authors of this script to the user """
            if popuplib.isqueued("sourcerpg_creditmenu", userid):
                return
            popuplib.send('sourcerpg_creditmenu', userid)
            
    @staticmethod
    def confirm(userid, choice, popupid):
        """
        This callback function is processed when a user has selected their
        choice to confirm if they want to reset their skills or not. If they
        do, choice will be True, otherwise False.
        """
        if choice:
            players[userid].resetSkills()

class DebugManager(object):
    """
    This class manages writing debug messages to text files for easier reading.
    """
    def __init__(self, path):
        """
        The default constructor, this should set up the file and raise an
        exception if the file doesn't exist.
        
        @PARAM path - the path to the debug.
        """
        self.path = path
        """ If the file doesn't exist, touch it """
        open(self.path, 'w').close()
        
    def write(self, text, debugLevel, writeTime = True):
        """
        Writes a line to the debug file.
        
        @PARAM text       - the text to write to the file
        @PARAM debugLevel - if the debug server variable allows it, then write it
        @PARAM writeTime  - whether or not we should 
        """
        if int(debugMode) >= debugLevel:
            es.dbgmsg(0, text)
            
            if writeTime is True:
                """ Prepend the time to the text """
                currentTime = time.strftime("%d/%m/%Y - %H:%M:%S")
                text = currentTime + " - " + text
            
            """ Write to the file stream """
            fileStream = open(self.path, 'a')
            fileStream.write(text + "\n")
            fileStream.close()

""" Create the singletons to hold the object of the manager classes """
weaponXp    = {}
commands = CommandManager()
cmdlib.registerServerCommand("srpg", commands.mainCommand, "srpg <command> [args]")
es.server.cmd("exec sourcerpg/config.cfg")
currentTurboMode = bool( int(turboMode))
DATABASE_STORAGE_METHOD = SQLiteManager
database = None
databasePath = os.path.join( es.getAddonPath(info.basename), "players.sqlite" )
debugPath    = os.path.join( es.getAddonPath(info.basename), "debuglog.txt"   )
skills   = SkillManager()
addons   = AddonManager()
players  = PlayerManager()
popups   = PopupCallbacks()
ranks    = RankManager()
debug    = DebugManager(debugPath)
skillConfig = ConfigurationObject(str(sourcerpgCFG.joinpath("skills.cfg")))
sayCommands = SayCommandsManager()

####################################
### API ENDS HERE, Program below ###
####################################

def load():
    """
    Executed when the script loads. Ensures that all the commands are registered
    and all the default popups loaded
    """
    global database
    global ranks
    
    osPlatform = ("Windows" if os.name == "nt" else "Linux" if os.name == "posix" else os.name)
    debug.write('Log file started at %s' % time.strftime("%A %d %B %Y - %H:%M:%S"), 0, False)
    debug.write('\n*******************************************************',        0, False)
    debug.write('[SourceRPG]: Turning your Server into a Role Playing Game',        0, False)
    debug.write('[SourceRPG]: Current Version - %s' % info.version,                 0, False)
    debug.write('[SourceRPG]: Made by %s' % info.author,                            0, False)
    debug.write('\nSystem Info:',                                                   0, False)
    debug.write('\tOS: %s' % osPlatform,                                            0, False)
    debug.write('\tEventscripts Version: %s' % es.ServerVar('eventscripts_ver'),    0, False)
    debug.write('\tCorelib Version: %s' % es.ServerVar('es_corelib_ver'),           0, False)
    debug.write('\tEventscript Tools Version: %s' % es.ServerVar('est_version'),    0, False)
    debug.write('\tEventscripts Noisy: %s' % es.ServerVar('eventscripts_noisy'),    0, False)
    debug.write('\tPopuplib version: %s' % popuplib.info.version,                   0, False) 
    
    cmdlib.registerSayCommand("rpgmenu",    sayCommands.mainMenu,    "Opens the rpg main menu")
    cmdlib.registerSayCommand("rpgupgrade", sayCommands.upgradeMenu, "Opens the upgrade menu")
    cmdlib.registerSayCommand("rpgsell",    sayCommands.sellMenu,    "Opens the sell menu")
    cmdlib.registerSayCommand("rpghelp",    sayCommands.helpMenu,    "Opens the help menu")
    cmdlib.registerSayCommand("rpgstats",   sayCommands.stats,       "Opens the stats menu for the user or another player")
    cmdlib.registerSayCommand("rpgrank",    sayCommands.rank,        "Tells the player their rank or another player's rank")
    cmdlib.registerSayCommand("rpgpopup",   sayCommands.togglePopup, "Tells the player their rank or another player's rank")
    cmdlib.registerSayCommand("rpgtop10",   sayCommands.top10,       "Sends the player the last updated top 10 scores")
    
    es.server.cmd("exec sourcerpg/skill_loader.cfg")
    
    es.server.cmd("exec sourcerpg/addon_loader.cfg")
    
    skillConfig.write(True)
    skillConfig.execute(True, True)

    debug.write('[SourceRPG] Starting the popup creation', 0, False)

    """ Create the default popups which aren't unique to players """
    rpgmenu = popuplib.easymenu("sourcerpg_rpgmenu", "_popup_choice", popups.rpgmenu)
    rpgmenu.settitle("=== %s Menu ===" % prefix)
    rpgmenu.addoption(1, "Upgrade Skills")
    rpgmenu.addoption(2, "Sell Skills")
    rpgmenu.addoption(3, "RPG Help")
    rpgmenu.addoption(4, "RPG Stats")
    rpgmenu.addoption(5, "Reset Skills")
    
    helpMenu = popuplib.easymenu('sourcerpg_help', '_popup_choice', popups.helpmenu)
    helpMenu.settitle('=== %s Help ===' % prefix)
    helpMenu.addoption(1, 'About SourceRPG')
    helpMenu.addoption(2, 'List of Commands')
    helpMenu.addoption(3, 'About SourceRPG Skills')
    helpMenu.addoption(4, 'Credit')
    helpMenu.submenu(10, "sourcerpg_rpgmenu")
    
    confirmation = popuplib.easymenu('sourcerpg_confirm', '_popup_choice', popups.confirm)
    confirmation.settitle("=== %s Reset Stats ===" % prefix)
    confirmation.setdescription("""Are you sure you want to remove
your skills? There is no chance
or recovering them again!""")
    confirmation.addoption(True,  "Yes")
    confirmation.addoption(False, "No")
    
    about = popuplib.create('sourcerpg_about')
    about.addline('=== About %s ===' % prefix)
    about.addline('-' * 30)
    about.addline('SourceRPG is a python coded mod')
    about.addline('for EventScripts 2+. It enables')
    about.addline('players to gain Levels, by gaining')
    about.addline('XP from certain events, such as')
    about.addline('planting the bomb, or killing')
    about.addline('another player. Each level gives')
    about.addline('%s Credits, which allows you to' % creditsReceived)
    about.addline('buy certain skills which aid you')
    about.addline('in killing other players.')
    about.addline('-' * 30)
    about.addline('->8. Back')
    about.addline('0. Cancel')
    about.submenu(8, 'sourcerpg_help')
    
    commandspopup = popuplib.create('sourcerpg_commands')
    commandspopup.addline("=== %s Commands ===" % prefix)
    commandspopup.addline("-" * 30)
    commandspopup.addline("rpghelp - displays the help menu")
    commandspopup.addline("rpgmenu - displays the main menu")
    commandspopup.addline("rpgrank - displays your RPG rank")
    commandspopup.addline("rpgpopup - toggles on / off automatic popup display")
    commandspopup.addline("rpgupgrade - upgrade skills")
    commandspopup.addline("rpgsell - sell skills")
    commandspopup.addline("rpgstats - display your stats")
    commandspopup.addline("-" * 30)
    commandspopup.addline("->8. Back")
    commandspopup.addline("0. Cancel")
    commandspopup.submenu(8, 'sourcerpg_help')
    
    creditmenu = popuplib.create('sourcerpg_creditmenu') 
    creditmenu.addline('=== %s Credits ===' % prefix)
    creditmenu.addline('-' * 30)
    creditmenu.addline(info.author)
    creditmenu.addline('  Script Creator')
    creditmenu.addline(' ')
    creditmenu.addline('SumGuy14 and Murphey')
    creditmenu.addline('  Letting me use their Long Jump code')
    creditmenu.addline(' ')
    creditmenu.addline('SuperDave')
    creditmenu.addline('  He turned my failing SmogNade code into')
    creditmenu.addline('  a working code! Thank him for that skill.')
    creditmenu.addline(' ')
    creditmenu.addline('JoeyT2008 (Jordan Thomas)')
    creditmenu.addline('  Awesome scripter who made the database conversion')
    creditmenu.addline(' ')
    creditmenu.addline('EventScripts Community')
    creditmenu.addline('  Help and support, and such a good plugin.')
    creditmenu.addline('-' * 30)
    creditmenu.addline('8. Back')
    creditmenu.addline('0. Cancel')
    creditmenu.submenu(8, 'sourcerpg_help')
    
    debug.write('[SourceRPG] Popups created', 0, False)
    
    
    if int(turboMode):
        database = DATABASE_STORAGE_METHOD(":memory:")
    else:
        database = DATABASE_STORAGE_METHOD(databasePath)
        
    ranks = RankManager()
    
    """ If the script is loaded late then make sure all players are inserted """
    if es.getplayercount():
        for player in es.getUseridList():
            players.addPlayer( player )
            
        es.server.queuecmd('mp_restartgame 1')

    if str( es.ServerVar('eventscripts_currentmap')):
        es_map_start({})

    """ If we want to save by intervals then create a repeat to save the database """
    if str( saveType ) == "intervals":
        gamethread.delayedname(float(saveLength), 'sourcerpg_databasesave', saveDatabase)
        
    debug.write('[SourceRPG]: Finished Loading... Enjoy your stay!',         0, False)
    debug.write('*******************************************************\n', 0, False)
    
    
def unload():
    """
    Executed when SourceRPG is unloaded. Clear up all the global values which
    will still be left in other libraries. Save the database.
    """
    database.save() # Save the database

    """ Remove any popups """
    deleted = []
    for popup in popuplib.gPopups:
        if popup.startswith('sourcerpg_'):
            deleted.append(popup)
    for popup in deleted:
        popuplib.delete(popup)

    """ Unload all skills """
    for skill in skills:
        es.unload("sourcerpg/skills/" + skill.name)

    """ Unload all addons """
    for addon in addons:
        es.unload("sourcerpg/addons/" + addon.name)

    """ Unregister the server commands """
    cmdlib.unregisterServerCommand("srpg")
    cmdlib.unregisterSayCommand("rpgmenu")
    cmdlib.unregisterSayCommand("rpgupgrade")
    cmdlib.unregisterSayCommand("rpgsell")
    cmdlib.unregisterSayCommand("rpghelp")
    cmdlib.unregisterSayCommand("rpgstats")
    cmdlib.unregisterSayCommand("rpgrank")
    cmdlib.unregisterSayCommand("rpgpopup")
    cmdlib.unregisterSayCommand("rpgtop10")
        
    gamethread.cancelDelayed('sourcerpg_databasesave')
    
def es_map_start(event_var):
    """
    Executed when a new map loads. Ensure that the events are executed. Update
    the ranks and recreate the top10 menu
    
    @PARAM event_var - an automatically passed event instance
    """
    debug.write('[SourceRPG] Executing the es_map_start event', 1, True)
    es.loadevents('declare', 'addons/eventscripts/%s/events/events.res' % info.basename)
    debug.write('[SourceRPG] Events loaded', 1, True)
    
    ranks.update()
    
    debug.write('[SourceRPG] Ranks updated', 1, True)
    
    if ranks.getPlayerSlice(0, 10):
        debug.write('[SourceRPG] Building top 10 menu', 1, True)
        query = "SELECT name,level,xp,credits FROM Player WHERE steamid IN ("
        for steamid in ranks.getPlayerSlice(0, 10):
            query += "'%s', " % steamid
        query = query[:-2] + ") ORDER BY level DESC,xp DESC"

        database.execute(query)
        results = database.cursor.fetchall()

        rpgTop5Popup = popuplib.create("sourcerpg_rpgtop5")
        rpgTop5Popup.addline("=== %s Top Players ===" % prefix)
        rpgTop5Popup.addline("-" * 30)
        for index, values in enumerate(results[:5]):
            name, level, xp, credits = values
            if len(name) > 16:
                name = name[0:14]+'...'
            rpgTop5Popup.addline("->%s. %s - Lvl: %s XP: %s Credits: %s" % (index + 1, name, level, xp, credits ))
        rpgTop5Popup.addline("-" * 30)
        if len(results) > 5:
            rpgTop5Popup.addline("-> 9. Next")
            rpgTop5Popup.submenu(9, 'sourcerpg_rpgtop10')

            rpgTop10Popup = popuplib.create("sourcerpg_rpgtop10")
            rpgTop10Popup.addline("=== %s Top Players ===" % prefix)
            rpgTop10Popup.addline("-" * 30)

            for index, values in enumerate(results[5:]):
                name, level, xp, credits = values
                if len(name) > 16:
                    name = name[0:14]+'...'
                rpgTop10Popup.addline("->%s. %s - Lvl: %s XP: %s Credits: %s" % (index + 6, name, level, xp, credits ))

            rpgTop10Popup.addline("-" * 30)
            rpgTop10Popup.addline("->8. Back")
            rpgTop10Popup.addline("0. Cancel")
            rpgTop10Popup.submenu(8, 'sourcerpg_rpgtop5')
        rpgTop5Popup.addline("0. Close")
        
        debug.write('[SourceRPG] Top 10 created', 0, True)
        
    """ Delete all inactive people """
    currentTime = int(time.time())
    currentTime -= ( float(inactivityCounter) * 86400 ) # 86400 = seconds in a day
    
    debug.write('[SourceRPG] Removing all inactve players!', 0, True)
    
    database.execute("SELECT UserID FROM Player WHERE lastconnected < ?", int(currentTime))
    for userid in database.fetchall():
        debug.write('[SourceRPG] Removing UserID %s' % userid, 3, False)
        database.execute("DELETE FROM Player WHERE UserID=?", userid )
        database.execute("DELETE FROM Skill WHERE UserID=?", userid )
    
    debug.write('[SourceRPG] es_map_start event finished executing', 1, True)
    
def player_activate(event_var):
    """
    An event which occurs when a user activates on the server. Ensure that their
    skills are pulled from the database and that all the local stats are updated
    
    @PARAM event_var - an automatically passed event instance
    """
    debug.write("[SourceRPG] Handling player_activate", 1)
    if "PENDING" in event_var['es_steamid']:
        debug.write("[SourceRPG] Player joining had a pending steamid, being kicked")
        es.server.cmd('kickid %s "We had an error with you joining, please reconnect"' % event_var['userid'])
    else:
        debug.write("Player successfully joined and activated", 1)
        players.addPlayer( event_var['userid'] )
    debug.write("[SourceRPG] player_activate handled", 1)
    
def player_disconnect(event_var):
    """
    An event which occurs when a player disconnects from the server. Update them
    with the time that they last connected, then remove the instance from the
    PlayerManager class. This will call the destructor on the PlayerObject class
    and update the SQLite Database
    
    @PARAM event_var - an automatically passed event instance
    """
    debug.write("[SourceRPG] Handling player_disconnect", 1)
    userid = event_var['userid']
    gamethread.cancelDelayed('sourcerpg_reset_%s' % userid)
    if userid in players:
        debug.write("Remove player instance from players manager", 1)
        players[userid]['lastconnected'] = int(time.time())
        debug.write("Calling object destructor...", 1)
        del players[userid] # call's the destructor
    debug.write("[SourceRPG] player_disconnect handled", 1)
    
def player_changename(event_var):
    """
    An event which occurs when a player changes their name. Update their local
    copy so that the rpgtop10 menu displays the correct name etc.
    
    @PARAM event_var - an automatically passed event instance
    """
    debug.write("[SourceRPG] Handling player_changename", 1)
    players[event_var['userid']]['name'] = event_var['newname']
    debug.write("[SourceRPG] player_changename handled", 1)
    
def round_end(event_var):
    """
    An event which occurs when the round ends. Ensure that the datbase
    is saved if the saveType is equal to round end. Also updates all players
    stats that they achieved in that round.
    
    @PARAM event_var - event variable instancce passed automatically
    """
    debug.write("[SourceRPG] Round End Processing", 1)
    if not currentTurboMode:
        debug.write("Normal mode is on", 1)
        if str(saveType).lower() == "round end":
            debug.write("Attempting to save the database", 1)
            saveDatabase()
            debug.write("Database successfully saved.", 1)
    for userid in es.getUseridList():
        debug.write("Fetching player object for %s" % es.getplayername(userid), 2)
        player = players[userid]
        if player is not None:
            debug.write("Object successfully fetched, delaying 4.8 seconds to reset default attributes", 2)
            gamethread.delayedname(2, 'sourcerpg_reset_%s' % player, player.resetPlayerDefaultAttributes)
    debug.write("[SourceRPG] Round End has been processed\n", 1)
            
def player_spawn(event_var):
    """
    An event which occurs when a player spawns. If the option is turned on,
    notify them of their level and experience.
    
    @PARAM event_var - event variable instance passed automatically
    """
    debug.write("[SourceRPG] Player Spawn processing", 1)
    userid = event_var['userid']
    if not es.getplayerprop(userid, 'CBasePlayer.pl.deadflag'):
        player = players[userid]
        if player is not None:
            tell(userid, 'round start')
            if int(spawnAnnounce):
                debug.write("Sending player rank information", 1)
                tokens = {}
                tokens['level']  = player['level']
                tokens['xp']     = player['xp']
                tokens['nextxp'] = (player['level'] - 1) * int(xpIncrement) + int(startXp)
                tell(userid, 'level gained private', tokens)
                debug.write("Player rank information sent", 1)
            if currentTurboMode and int(turboModeAnnounce):
                tell(userid, 'turbo mode on')
    debug.write("[SourceRPG] Player Spawn processsed\n", 1)
        
def server_cvar(event_var):
    """
    An event which occurs when a public variable is altered. Here we will check
    to see if the event was turbo mode, then either save the database or open
    a new connection depending on the effect.
    
    @PARAM event_var - an automatically passed event instance
    """
    global currentTurboMode
    global database
    debug.write("[SourceRPG] Handling server_cvar", 1)
    if event_var['cvarname'] == "srpg_turboMode":
        debug.write("Altering turbo mode", 1)
        newValue = bool( int(event_var['cvarvalue']))
        debug.write("New value of turbo mode: %s" % newValue, 1)
        if newValue <> currentTurboMode:
            debug.write("New Value differs to the old value", 1)
            if newValue:
                """ Turbo mode was activated. Restart the round """
                debug.write("Closing database and creating a new memory object", 1)
                gamethread.cancelDelayed('sourcerpg_databasesave')
                
                database = SQLiteManager(":memory:") # Calls deconstructor on the database
                es.server.queuecmd('mp_restartgame 2')
            else:
                debug.write("Closing database and opening the real database",1 )
                """ Turbo mode was switched off """
                database = SQLiteManager(databasePath)
                es.server.queuecmd('mp_restartgame 2')
                
                """ If we need to start the loop again """
                gamethread.cancelDelayed('sourcerpg_databasesave')
                if str( saveType ) == "intervals":
                    gamethread.delayedname(float(saveLength), 'sourcerpg_databasesave', saveDatabase)
                
            currentTurboMode = newValue
                
            """ Ensure that all players are activated and their new values are reset """
            for player in es.getUseridList():
                debug.write("Reloading stats for player %s" % es.getplayername(player), 2)
                tokens = {}
                tokens['userid']     = player
                tokens['es_steamid'] = es.getplayersteamid(player)
                player_activate(tokens)
    debug.write("[SourceRPG] server_cvar handled", 1)
                
def player_death(event_var):
    """
    Executed upon the event "player_death". Add experience to the attacker.
    
    @PARAM event_var - an automatically passed argument as the event instance
    """
    debug.write("[SourceRPG] Handling player_death", 1)
    userid   = event_var['userid']
    attacker = event_var['attacker']
    """ Only pass if the user did not kill themselves and are not on the same team """
    if userid <> attacker:
        debug.write("Userid is not the same as the attacker", 2)
        if attacker.isdigit() and int(attacker) > 0:
            debug.write("Attacker is not of world spawn", 2)
            if event_var['es_userteam'] <> event_var['es_attacker']:
                """ If one of the players is a bot and is not legible for experience, return """
                debug.write("Users are on different teams", 2)
                if not canReceiveExperience(userid, attacker):
                    return
                debug.write("Adding the experience", 1)
                player = players[attacker]
                player.addXp( int(killXp) * players[userid]['level'], "making a kill" )
                if event_var['headshot'] and int(event_var['headshot']):
                    player.addXp( int(headshotXp), "scoring a headshot" )
                weapon = event_var['weapon']
                if weapon in weaponXp:
                    if weaponXp[weapon][1]:
                        useN = "n" if weapon[0] in ("a", "e", "i", "o", "u") else ""
                        player.addXp( weaponXp[weapon][1], "killing a player with a%s %s" % ( useN, weapon ))
    debug.write("Resetting player to default attributes", 1)
    players[userid].resetPlayerDefaultAttributes()
    debug.write("[SourceRPG] player_death handled", 1)
                    
def player_hurt(event_var):
    """
    Executed upon the event "player_hurt". Add experience to the attacker.
    
    @PARAM event_var - an automatically passed argument as the event instance.
    """
    debug.write("[SourceRPG] handling player_hurt", 1)
    userid   = event_var['userid']
    attacker = event_var['attacker']
    """ Only pass if the user did not kill themselves and are not on the same team """
    if userid <> attacker:
        debug.write("Playerids are not the same", 2)
        if attacker.isdigit() and int(attacker) > 0:
            debug.write("Attacker is not work spawn", 2)
            if event_var['es_userteam'] <> event_var['es_attacker']:
                """ If one of the players is a bot and is not legible for experience, return """
                debug.write("Players are not on the same team", 2)
                if not canReceiveExperience(userid, attacker):
                    return
                debug.write("Handling the experience rewards", 1)
                player = players[attacker]
                weapon = event_var['weapon']
                if weapon in weaponXp:
                    if weaponXp[weapon][0]:
                        player.addXp( weaponXp[weapon][0], tellUserOverride = False)
                else:
                    player.addXp( int(damageXp), tellUserOverride = False )
    debug.write("[SourceRPG] player_hurt handled", 1)
    
def bomb_planted(event_var):
    """
    Executed upon the event "bomb_planted". Add experience to the bomb planter.
    
    @PARAM event_var - an automatically passed argument as the event instance.
    """
    debug.write("[SourceRPG] Handling bomb_planted", 1)
    if isFairForTeam(event_var['es_userteam']) or not int(unfairAdvantage):
        if es.isbot(event_var['userid']) and not int(botsGetXpOnEvents):
            return
        player = players[event_var['userid']]
        player.addXp( int(bombPlantXp) * player['level'], 'planting the bomb' )
    debug.write("[SourceRPG] bomb_planted handled", 1)

def bomb_exploded(event_var):
    """
    Executed upon the event "bomb_exploded". Add experience to the bomb planter.
    
    @PARAM event_var - an automatically passed argument as the event instance.
    """
    debug.write("[SourceRPG] Handling bomb_exploded", 1)
    if isFairForTeam(event_var['es_userteam']) or not int(unfairAdvantage):
        if es.isbot(event_var['userid']) and not int(botsGetXpOnEvents):
            return
        player = players[event_var['userid']]
        player.addXp( int(bombExplodeXp) * player['level'], 'allowing the bomb to explode' )
    debug.write("[SourceRPG] bomb_exploded handled", 1)
    
def bomb_defused(event_var):
    """
    Executed upon the event "bomb_defused". Add experience to the defuser.
    
    @PARAM event_var - an automatically passed argument as the event instance.
    """
    debug.write("[SourceRPG] Handling bomb_defused", 1)
    if isFairForTeam(event_var['es_userteam']) or not int(unfairAdvantage):
        if es.isbot(event_var['userid']) and not int(botsGetXpOnEvents):
            return
        player = players[event_var['userid']]
        player.addXp( int(bombDefuseXp) * player['level'], 'defusing the bomb' )
    debug.write("[SourceRPG] bomb_defused handled", 1)
    
def hostage_follows(event_var):
    """
    Executed upon the event "hostage_follows". Add experience to the rescuer.
    
    @PARAM event_var - an automatically passed argument as the event instance.
    """
    debug.write("[SourceRPG] Handling hostage_follows", 1)
    if isFairForTeam(event_var['es_userteam']) or not int(unfairAdvantage):
        if es.isbot(event_var['userid']) and not int(botsGetXpOnEvents):
            return
        player = players[event_var['userid']]
        player.addXp( int(hostageFollowXp) * player['level'], 'making a hostage follow you' )
    debug.write("[SourceRPG] hostage_follows handled", 1)
    
def hostage_rescued(event_var):
    """
    Executed upon the event "hostage_rescued". Add experience to the rescuer
    
    @PARAM event_var - an automatically passed argument as the event instance.
    """
    debug.write("[SourceRPG] Handling hostage_rescued", 1)
    if isFairForTeam(event_var['es_userteam']) or not int(unfairAdvantage):
        if es.isbot(event_var['userid']) and not int(botsGetXpOnEvents):
            return
        player = players[event_var['userid']]
        player.addXp( int(hostageRescueXp) * player['level'], 'rescuing a hostage' )
    debug.write("[SourceRPG] hostage_rescued handled", 1)
    
def isFairForTeam(teamNumber):
    """
    This method returns whether there are currently any living players on the
    other team
    
    @PARAM teamNumber - test to see if it's fair for the players team
    @RETURN boolean - whether or not it's fair
    """
    debug.write("Testing if experience gain is fair for player teams", 2)
    if not 1 < int(teamNumber) < 4:
        debug.write("Incorrect teamnumber passed, teams are not fair", 2)
        return False 
    return bool( es.getlivingplayercount( 5 - int( teamNumber )))
    
def canReceiveExperience(userid, attacker):
    """
    This method tests players to see if it's possible for the attacker to gain
    experience when a bot is involved.
    
    @PARAM userid - the victim involved
    @PARAM attacker - the attacker involved
    @RETURN boolean - whether or not the attacker is legible for experience
    """
    debug.write("Testing if user can gain experience", 2)
    if not es.isbot(userid) and not es.isbot(attacker):
        debug.write("Both players are humans, experience gain is fair", 2)
        return True
    if es.isbot(attacker):
        debug.write("Attacker is a bot", 2)
        if es.isbot(attacker):
            debug.write("Victim is a bot", 2)
            return bool( int( botsGetXpVsBots ))
        else:
            debug.write("Victim is a human", 2)
            return bool( int( botsGetXpVsHumans ))
    else:
        debug.write("Victim is a bot, attacker is a human", 2)
        return bool( int( humansGetXpVsBots ))
    
def getSteamid(value):
    """
    This function will return the actual steamid of a value given. If a STEAMID
    was input then we return that in case we want an offline check. Otherwise
    we return the uniqueid of the players userid if on, otherwise None
    
    @PARAM value - the original input, either userid, partial name or steamid
    @RETURN string - the actual steamid of the input. 
    """
    value = str(value)
    if value.startswith( ("STEAM_", "BOT_")):
        return value
    userid = es.getuserid(value)
    if userid:
        steamid = playerlib.uniqueid( userid, True )
        return steamid
    return None
    
def saveDatabase():
    """
    This function will go through all the players and update the SQLiteDatabase
    with any different values from the ones stored. After all the queries
    have taken place, the database will save to disk
    """
    debug.write("saveDatabase processing", 1)
    """ Only process if turbo mode is off """
    if not currentTurboMode:
        debug.write("turbo mode off, process the save", 1)
        """ Update all the player's stats gained and commit the database"""
        for player in players:
            debug.write("Commiting indivudal players to the virtual database: %s" % player.name, 2)
            player.commit()
        debug.write("Attempting to save the database itself", 1)
        database.save()
        debug.write("SQLite database saved", 1)
        debug.write("Creating the event", 1)
        """ Create and fire the event """
        values = {"type":("setstring", str(saveType))}
        gamethread.delayed(0, fireEvent, ("sourcerpg_databasesaved", values))
        debug.write("Event fired", 1)
        
        """ Create a loop if we need to """
        if str( saveType ) == "intervals":
            gamethread.delayedname(float(saveLength), 'sourcerpg_databasesave', saveDatabase)
    debug.write("saveDatabase processed", 1)
    
def buildSkillMenu(userid):
    """
    This method creates a new unique popup instance for a certain user and
    adds all the current active skills to the list and tells them how much
    it will cost to upgrade if they can.
    
    @PARAM userid - the userid of the player the popup should be built for
    """
    if popuplib.isqueued("sourcerpg_skillsmenu_user%s" % userid, userid):
        return
    debug.write("[SourceRPG] Handling buildSkillMenu for userid %s" % userid, 2)
    player    = players[userid]
    debug.write("building popup", 2)
    skillMenu = popuplib.easymenu("sourcerpg_skillsmenu_user%s" % userid, "_popup_choice", checkSkillForUpgrading)
    skillMenu.settitle("Select a skill to upgrade:\nCredits: %s\nPage: " % player["credits"])
    for skill in skills:
        level = player[skill.name]
        if level >= int(skill.maxLevel):
            """ If the user has already maxed out the skill, then make the option unselectable """
            skillMenu.addoption(None, str(skill.name) + " [MAXED]" ,False)
        else:
            cost = level * int(skill.creditIncrement) + int(skill.startCredit)
            skillMenu.addoption(skill.name, skill.name + " => %s [COST %s]" % (level + 1, cost), bool(player['credits'] >= cost))
    skillMenu.c_exitformat = "0. Close"
    debug.write("popup built", 2)
    skillMenu.send(userid)
    debug.write("[SourceRPG] Handled buildSkillMenu for userid %s" % userid, 2)
    
def checkSkillForUpgrading(userid, choice, popupid, resend = True, useCredits = True):
    """
    This method is a return function from a popup return. It validates the user's
    choice, and if correct, deduct the credits and increment the skill
    
    @PARAM userid - the id of the user who chose an option from the menu
    @PARAM choice - the name of the skill of which to upgrade
    @PARAM popupid - the name of the popup
    """
    player = players[userid]
    skill  = skills[choice]
    creditsRequired = player[str(skill.name)] * int(skill.creditIncrement) + int(skill.startCredit)
    
    if player['credits'] >= creditsRequired or useCredits is not True:
        """ Remove the credits and increment the skill """
        if useCredits is True:
            player['credits'] -= creditsRequired
        player[str(skill.name)] += 1
        
        """ If the skill upgrade sound is not blank, emmit it from the player """
        if str(skillUpgradeSound):
            es.emitsound('player', userid, str(skillUpgradeSound), '0.7', '0.5')
            
        """ Notify the user of their purchase """
        tokens = {}
        tokens['level'] = player[skill.name]
        tokens['skill'] = str(skill.name)
        tell(userid, 'skill upgrade', tokens)
        
        """ Fire the skill upgrade event """
        values = {}
        values["userid"] = ("setint", userid)
        values["level"] = ("setint", player[str(skill.name)])
        values["cost"] = ("setint", creditsRequired)
        values["skill"] = ("setstring", skill.name)
        gamethread.delayed(0, fireEvent, ("sourcerpg_skillupgrade", values))
        
    else:
    
        """ Notify the user that they cannot purchase due to lack of credits """
        tokens = {}
        tokens['credreq'] = creditsRequired
        tokens['credits'] = player['credits']
        tokens['amountofcreds'] = creditsRequired - player['credits']
        
        tell(userid, 'insufficient credits', tokens)
    
    """ Rebuild the skill menu """
    if resend is True:
        buildSkillMenu(userid)
        
def buildSellMenu(userid):
    """
    This method creates a new unique popup instance for a certain user and
    adds all the current active skills to the list and tells them how much
    they will receive for selling a skill
    
    @PARAM userid - the userid of the player the popup should be built for
    """
    debug.write("[SourceRPG] Handling buildSellMenu for userid %s" % userid, 2)
    player   = players[userid]
    debug.write("building popups", 1)
    if popuplib.isqueued("sourcerpg_sellmenu_user%s" % userid, userid):
        return
    sellMenu = popuplib.easymenu("sourcerpg_sellmenu_user%s" % userid, "_popup_choice", checkSkillForSelling)
    sellMenu.settitle("Select a skill to sell:\nCredits: %s\nPage: " % player["credits"])
    for skill in skills:
        level = player[str(skill.name)]
        if level > 0:
            sellMenu.addoption(skill.name, skill.name + " => %s [GAIN %s]" % (level - 1, int( ( (level - 1) * int(skill.creditIncrement) + int(skill.startCredit)) * float(sellPercentage) / 100.)))
        else:
            sellMenu.addoption(None, skill.name + " [NEED LEVEL]", False)
    sellMenu.c_exitformat = "0. Close"
    debug.write("popup built", 1)
    sellMenu.send(userid)
    debug.write("[SourceRPG] Handled buildSellMenu for userid %s" % userid, 2)

def checkSkillForSelling(userid, choice, popupid, resend=True, gainCredits = True):
    """
    This method is a return function from a popup return. It validates the user's
    choice, and if correct, increments the credits and decrements the skill
    
    @PARAM userid - the id of the user who chose an option from the menu
    @PARAM choice - the name of the skill of which to downgrade
    @PARAM popupid - the name of the popup
    """
    player = players[userid]
    skill  = skills[choice]
    level  = player[skill.name]
    creditsGained = int( ( (level - 1) * int(skill.creditIncrement) + int(skill.startCredit)) * float(sellPercentage) / 100.)
    if creditsGained > 0 or gainCredits is not True:
        """ Only do the purchase if credits are obtained """
        player[skill.name] -= 1
        if gainCredits is True:
            player['credits']  += creditsGained
        
        """ If the skill downgrade sound is not blank, emmit it from the player """
        if str(skillDowngradeSound):
            es.emitsound('player', userid, str(skillDowngradeSound), '0.7', '0.5')
            
        """ Notify the user of their sale """
        tokens = {}
        tokens['level'] = player[skill.name]
        tokens['skill'] = skill.name 
        tell(userid, 'skill downgrade', tokens)
        
        """ Fire the skill downgrade event """
        values = {}
        values["userid"] = ("setint", userid)
        values["level"] = ("setint", player[skill.name])
        values["gained"] = ("setint", creditsGained)
        values["skill"] = ("setstring", skill.name)
        gamethread.delayed(0, fireEvent, ("sourcerpg_skilldowngrade", values))
        
    if resend is True:
        buildSellMenu(userid)
    
def buildStatsMenu(userid, playerToTest):
    """
    This function builds stats for a player and shows them more detailed stats
    in a popup.
    
    @PARAM userid - the userid who to send the popup to
    @PARAM playerToTest - the player who we shall get the stats of
    """
    if popuplib.isqueued("sourcerpg_statsmenu_user%s" % userid, userid):
        return
    player = players[playerToTest]
    statsmenu = popuplib.create('sourcerpg_statsmenu_user%s' % userid)
    statsmenu.addline("=== %s Stats ===" % prefix )
    statsmenu.addline("-" * 30)
    statsmenu.addline("Name: %s" % player['name'])
    statsmenu.addline("Last Disconnected: %s" % (time.strftime("%a %d %b %Y, %H:%M:%S", time.localtime( float( player['lastconnected'] )))))
    statsmenu.addline("Level: %s" % player['level'])
    statsmenu.addline("XP: %s/%s" % (player['xp'], (player['level'] - 1) * int(xpIncrement) + int(startXp)))
    statsmenu.addline("Rank: %s/%s" % ( ranks.getRank( player['steamid'] ), len( ranks )))
    statsmenu.addline("-" * 30)
    statsmenu.addline("->9. See skills")
    statsmenu.addline("0. Close")
    statsmenu.submenu(9, buildSkillsMenuForPlayer(userid, playerToTest, False))
    statsmenu.send(userid)

def buildSkillsMenuForPlayer(userid, playerToTest, send = True):
    """
    A function to build a skill menu for a certain player and show a user
    all their skills and the levels
    
    @PARAM userid - the userid who to send the popup to
    @PARAM playerToTest - the player who we shall get the levels of
    @PARAM OPTIONAL send - if this is true then the menu will be sent to the player
    @RETRUN string - the name of the popup
    """
    if popuplib.isqueued("sourcerpg_skillssmenu_user%s" % userid, userid):
        return 
    popupName = 'sourcerpg_skillsmenu_user%s' % userid
    statsmenu = popuplib.easymenu(popupName, '_popup_choice', None)
    statsmenu.settitle("=== %s Skill Menu ==" % prefix)
    
    player = players[playerToTest]
    if player is not None:
        for skill in skills:
            level = player[skill.name]
            statsmenu.addoption(None, "%s - %s" % (skill.name, level), False )
            
    if send is True:
        statsmenu.send(userid)
    return popupName
    
def buildSkillsReferencePopup(userid):
    """
    This function builds a new skills menu which lists all the skills. The
    callback function will build information on the skills
    
    @PARAM userid - the user to send the popup to
    """
    if popuplib.isqueued("sourcerpg_skillsinfo_user%s" % userid, userid):
        return
    skillsPopup = popuplib.easymenu('sourcerpg_skillsinfo_user%s' % userid, '_popup_choice', buildSkillInfo)
    skillsPopup.settitle("=== %s Skills info ===" % prefix)
    skillsPopup.setdescription("Select a skill to view information on it")
    for skill in skills:
        skillsPopup.addoption(skill.name, skill.name)
    skillsPopup.send(userid)
    
def buildSkillInfo(userid, choice, popupid):
    """
    This function builds information about a certain skill and displays it to
    a user.
    
    @userid - the user who to send the popup to
    @choice - the name of the skill
    @popupid - the name of the popup we just executed.
    """
    if popuplib.isqueued("sourcerpg_skilldescription_user%s" % userid, userid):
        return
    skill = skills[choice]
    info  = skill.info
    maxLevel    = skill.maxLevel
    startCredit = skill.startCredit
    creditIncrement = skill.creditIncrement
    
    lines = []
    if info is not None:
        info  = info.replace('\n', ' ').replace('//','')
        while len(info) > 30:
            space = info.find(' ', 29)
            if space == -1:
                break
            else:
                lines.append(info[:space])
                info = info[space + 1:]
        lines.append(info)
    
    skillInfo = popuplib.create("sourcerpg_skilldescription_user%s" % userid)
    skillInfo.addline("=== %s Skill Info ===" % prefix )
    skillInfo.addline("-" * 30)
    skillInfo.addline("%s Information:" % skill.name)
    skillInfo.addline(" ")
    if lines:
        for line in lines:
            skillInfo.addline(line)
    else:
        skillInfo.addline("[No additional information]")
    skillInfo.addline(" ")
    skillInfo.addline("Maximum Level: %s" % maxLevel)
    skillInfo.addline("Start Credit Cost: %s" % startCredit)
    skillInfo.addline("Credit Incremental Costs: %s" % creditIncrement)
    skillInfo.addline("-" * 30)
    skillInfo.addline("->8. Back")
    skillInfo.addline("0. Cancel")
    skillInfo.submenu(8, popupid)
    skillInfo.send(userid)
    
def tell(userid, textIdent, tokens = {} ):
    """
    This method acts as a wrapper for es.tell(). It ensures that we only need
    a minimum of 2 parameters instead of 5, and prefixes all messages with
    the prefix. Automatically retrieves the player's language
    
    @PARAM userid - the id of the user you wish to tell the message to
    @PARAM textIdent - the identification of the text from the ConfigObj object (langlib)
    @OPTIONAL PARAM tokens - a dictionary of variables you'd like to insert into the string
    """
    message = "#green%s #default- #lightgreen%s" % ( str(prefix), text(textIdent, tokens, playerlib.getPlayer(userid).get("lang"))) 
    es.tell(userid, '#multi', message)
    
def fireEvent(eventName, values):
    """
    Fires a custom event. This allows us to call the event with a tick delay
    to ensure that we arent' in any other event. In the OB engine, if we cause
    an event to be fired whilst we are in another event, it causes the event
    queue to mess up and crash the server. A 1 tick delay removes the chance of
    an event firing whilst in another event.
    
    @PARAM str eventName The name of the event to fire
    @PARAM dict values A dictionary containing all the event variables:
        Dict should be in this syntax:
        {<event_var name>} = (<setint/setstring...>, <event_var value>)
        e.g.:
        {"userid"} = ("setint", 2)
    """
    debug.write("[SourceRPG] Handling custom event firing", 1)
    debug.write("[SourceRPG] Firing event %s" % eventName, 1)
    es.event("initialize", eventName)
    for key, value in values.iteritems():
        debug.write("[SourceRPG] (Type: %s) %s=%s" % (value[0], key, value[1]), 2)
        es.event(value[0], eventName, key, value[1])
    debug.write("[SourceRPG] Firing event", 1)
    es.event("fire", eventName)
    debug.write("[SourceRPG] custom event firing handled", 1)