Guest User

WCpoweradminurt.py

a guest
Jun 26th, 2012
38
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 123.61 KB | None | 0 0
  1. #
  2. # PowerAdmin Plugin for BigBrotherBot(B3) (www.bigbrotherbot.net)
  3. # Copyright (C) 2008 Mark Weirath (xlr8or@xlr8or.com)
  4. #
  5. # This program is free software; you can redistribute it and/or modify
  6. # it under the terms of the GNU General Public License as published by
  7. # the Free Software Foundation; either version 2 of the License, or
  8. # (at your option) any later version.
  9. #
  10. # This program is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13. # GNU General Public License for more details.
  14. #
  15. # You should have received a copy of the GNU General Public License
  16. # along with this program; if not, write to the Free Software
  17. # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
  18. #
  19. # Changelog:
  20. #
  21. # 01:49 09/06/2008 by Courgette
  22. #  * add commands pagear (to change allowed weapons)
  23. #  * add commands paffa, patdm, pats, paftl, pacah, pactf, pabomb (to change g_gametype)
  24. #  * now namecheck is disabled during match mode
  25. #  * _smaxplayers is now set taking care of private slots (this is for speccheck)
  26. #  * fixes in Mark's code (I suppose I checkout an unstable version)
  27. #     for LoadRotationManager code, I'm not sure what you meant with all those successives "try:"
  28. #     maybe this is a python 2.5 syntax ? wasn't working for me
  29. # 01:21 09/07/2008 - Courgette
  30. # * add command !paident <player> : show date / ip / guid of player. Useful when moderators make demo of cheaters
  31. # 17/08/2008 - xlr8or
  32. # * added counter for max number of allowed client namechanges per map before being kicked
  33. # 20/10/2008 - xlr8or (v1.4.0b8)
  34. # * fixed a bug where balancing failed and disabled itself on rcon socket failure.
  35. # 10/21/2008 - 1.4.0b9 - mindriot
  36. # * added team_change_force_balance_enable to control force balance on client team change
  37. # 10/22/2008 - 1.4.0b10 - mindriot
  38. # * added autobalance_gametypes to specify which gametypes to autobalance
  39. # 10/22/2008 - 1.4.0b11 - mindriot
  40. # * if client does not have teamtime, provide new one
  41. # 10/23/2008 - 1.4.0b12 - mindriot
  42. # * onTeamChange is disabled during matchmode
  43. # 10/28/2008 - 1.4.0b13 - mindriot
  44. # * fixed teambalance to set newteam if dominance switches due to clients voluntarily switching teams during balance
  45. # 10/28/2008 - 1.4.0b14 - mindriot
  46. # * teambalance verbose typo
  47. # 12/07/2008 - 1.4.0b15 - xlr8or
  48. # * teamswitch-stats-harvest exploit penalty -> non legit switches become suicides
  49. # 2/9/2009 - 1.4.0b16 - xlr8or
  50. # * added locking mechanism to paforce. !paforce <player> <red/blue/s/free> <lock>
  51. # 2/9/2009 - 1.4.0b17 - xlr8or
  52. # * Fixed a default value onLoad for maximum teamdiff setting
  53. # 02:33 3/15/09 by FSK405|Fear
  54. # added more rcon cmds:
  55. #  !waverespawns <on/off> Turn waverespawns on/off
  56. #  !bluewave <seconds> Set the blue team wave respawn delay
  57. #  !redwave <seconds> Set the red team wave respawn delay
  58. #  !setnextmap <mapname> Set the nextmap
  59. #  !respawngod <seconds> Set the respawn protection
  60. #  !respawndelay <seconds> Set the respawn delay
  61. #  !caplimit <caps>
  62. #  !timelimit <mins>
  63. #  !fraglimit <frags>
  64. #  !hotpotato <mins>
  65. # 4/4/2009 - 1.4.0b18 - xlr8or
  66. # * Fixed locked force to stick and not continue with balancing
  67. # * Helmet and Kevlar messages only when connections < 20
  68. # 28/6/2009 - 1.4.0 - xlr8or
  69. # * Time to leave beta
  70. # * Teambalance raises warning instead of error
  71. # 10/8/2009 - 1.4.1 - naixn
  72. # * Improved forceteam locking mechanism and messaging
  73. # 10/8/2009 - 1.4.2 - xlr8or
  74. # * Added TeamLock release command '!paforce all free' and release on gameExit
  75. # 09/07/2009 - 1.4.3 by SGT
  76. # add use of dictionary for private password (papublic)
  77. # 09/21/2009 - 1.4.3 - SGT
  78. # new commands: !pasetwave, !pasetgravity
  79. # add individual config modes for game types and match mode
  80. # 27/10/2009 - 1.5.0 - Courgette
  81. # /!\ REQUIRES B3 v1.2.1 /!\
  82. # * add !pamap which works with partial map names
  83. # * update !pasetnextmap to work with partial map names
  84. # 27/10/2009 - 1.5.1 - Courgette
  85. # * debug !pamap and !pasetnextmap
  86. # * debug dictionnary use for !papublic
  87. # * !papublic can now use randnum even if dictionnary is not used
  88. # 31/01/2010 - 1.5.2 - xlr8or
  89. # * added ignore Set and Check functions for easier implementation in commands
  90. # * added ignoreSet(30) to swapteams and shuffleteams to temp disable auto checking functions
  91. #   Note: this will be overridden by the ignoreSet(60) when the new round starts after swapping/shuffling!
  92. # * Send rcon result to client on !paexec
  93. # 13/03/2010 - 1.5.3 - xlr8or
  94. # * fixed headshotcounter reset. now able to set it to 'no', 'round', or 'map'
  95. # 19/03/2010 - 1.5.4 - xlr8or
  96. # * fixed endless loop in ignoreCheck()
  97. # 30/06/2010 - 1.5.5 - xlr8or
  98. # * no longer set bot_enable var to 0 on startup when botsupport is disabled.
  99. # 20/09/2010 - 1.5.6 - Courgette
  100. # * debug !paslap and !panuke
  101. # * add tests
  102. # 20/09/2010 - 1.5.7 - BlackMamba
  103. # * fix !pamute - http://www.bigbrotherbot.net/forums/xlr-releases/poweradminurt-1-4-0-for-urban-terror!/msg15296/#msg15296
  104. # 20/09/2011 - 1.5.8 - SGT
  105. # * minor fix for b3 1.7 compatibility
  106. # * fix method onKillTeam
  107. # 25/09/2011 - 1.5.9 - xlr8or
  108. # * Code reformat by convention
  109.  
  110. __version__ = '1.5.9'
  111. __author__ = 'xlr8or'
  112.  
  113. import b3
  114. import time
  115. import thread
  116. import threading
  117. import re
  118. import os
  119. import random
  120. import string
  121. import traceback
  122. import b3.events
  123. import b3.plugin
  124. import b3.cron
  125. from b3.functions import soundex, levenshteinDistance
  126.  
  127. #--------------------------------------------------------------------------------------------------
  128. class PoweradminurtPlugin(b3.plugin.Plugin):
  129.     # ClientUserInfo and ClientUserInfoChanged lines return different names, unsanitized and sanitized
  130.     # this regexp designed to make sure either one is sanitized before namecomparison in onNameChange()
  131.     _reClean = re.compile(r'(\^.)|[\x00-\x20]|[\x7E-\xff]', re.I)
  132.  
  133.     _adminPlugin = None
  134.     _ignoreTill = 0
  135.     _checkdupes = False
  136.     _checkunknown = False
  137.     _checkbadnames = False
  138.     _checkchanges = False
  139.     _checkallowedchanges = 0
  140.     _ncronTab = None
  141.     _tcronTab = None
  142.     _scronTab = None
  143.     _skcronTab = None
  144.     _ninterval = 0
  145.     _tinterval = 0
  146.     _sinterval = 0
  147.     _skinterval = 0
  148.     _minbalinterval = 0 # minimum time in minutes between !bal or !sk for non-mods
  149.     _lastbal = 0 # time since last !bal or !sk
  150.     _oldadv = (None, None, None)
  151.     _teamred = 0
  152.     _teamblue = 0
  153.     _teamdiff = 0
  154.     _balancing = False
  155.     _origvote = 0
  156.     _lastvote = 0
  157.     _votedelay = 0
  158.     _smaxspectime = 0
  159.     _smaxlevel = 0
  160.     _smaxplayers = 0
  161.     _sv_maxclients = 0
  162.     _g_maxGameClients = 0
  163.     _teamsbalanced = False
  164.     _matchmode = False
  165.     _botenable = False
  166.     _botskill = 4
  167.     _botminplayers = 4
  168.     _botmaps = {}
  169.     _hsenable = False
  170.     _hsresetvars = True
  171.     _hsbroadcast = True
  172.     _hsall = True
  173.     _hspercent = True
  174.     _hspercentmin = 20
  175.     _hswarnhelmet = True
  176.     _hswarnhelmetnr = 7
  177.     _hswarnkevlar = True
  178.     _hswarnkevlarnr = 50
  179.     _rmenable = False
  180.     _dontcount = 0
  181.     _mapchanged = False
  182.     _playercount = -1
  183.     _oldplayercount = None
  184.     _currentrotation = 0
  185.     _switchcount1 = 12
  186.     _switchcount2 = 24
  187.     _hysteresis = 0
  188.     _rotation_small = ''
  189.     _rotation_medium = ''
  190.     _rotation_large = ''
  191.     _gamepath = ''
  192.     _origgear = 0
  193.     _team_change_force_balance_enable = True
  194.     _teamLocksPermanent = False
  195.     _autobalance_gametypes = 'tdm'
  196.     _autobalance_gametypes_array = []
  197.     _max_dic_size = 512000 #max dictionary size in bytes
  198.     _slapSafeLevel = None
  199.     _ignorePlus = 30
  200.     _full_ident_level = 20
  201.  
  202.     def startup(self):
  203.         """\
  204.        Initialize plugin settings
  205.        """
  206.  
  207.         # get the admin plugin so we can register commands
  208.         self._adminPlugin = self.console.getPlugin('admin')
  209.         if not self._adminPlugin:
  210.             # something is wrong, can't start without admin plugin
  211.             self.error('Could not find admin plugin')
  212.             return False
  213.  
  214.         # register our commands
  215.         if 'commands' in self.config.sections():
  216.             for cmd in self.config.options('commands'):
  217.                 level = self.config.get('commands', cmd)
  218.                 sp = cmd.split('-')
  219.                 alias = None
  220.                 if len(sp) == 2:
  221.                     cmd, alias = sp
  222.  
  223.                 func = self.getCmd(cmd)
  224.                 if func:
  225.                     self._adminPlugin.registerCommand(self, cmd, level, func, alias)
  226.         self._adminPlugin.registerCommand(self, 'paversion', 0, self.cmd_paversion, 'paver')
  227.  
  228.         # Register our events
  229.         self.verbose('Registering events')
  230.         self.createEvent('EVT_CLIENT_PUBLIC', 'Server Public Mode Changed')
  231.         self.registerEvent(b3.events.EVT_GAME_ROUND_START)
  232.         self.registerEvent(b3.events.EVT_GAME_EXIT)
  233.         #self.registerEvent(b3.events.EVT_CLIENT_JOIN)
  234.         self.registerEvent(b3.events.EVT_CLIENT_AUTH)
  235.         self.registerEvent(b3.events.EVT_CLIENT_DISCONNECT)
  236.         self.registerEvent(b3.events.EVT_CLIENT_TEAM_CHANGE)
  237.         self.registerEvent(b3.events.EVT_CLIENT_DAMAGE)
  238.         self.registerEvent(b3.events.EVT_CLIENT_NAME_CHANGE)
  239.         self.registerEvent(b3.events.EVT_CLIENT_KILL)
  240.         self.registerEvent(b3.events.EVT_CLIENT_KILL_TEAM)
  241.         self.registerEvent(b3.events.EVT_CLIENT_ACTION)
  242.  
  243.         # don't run cron-checks on startup
  244.         self.ignoreSet(self._ignorePlus)
  245.         self._balancing = False
  246.         self._killhistory = []
  247.  
  248.         # save original vote settings
  249.         try:
  250.             self._origvote = self.console.getCvar('g_allowvote').getInt()
  251.         except:
  252.             self._origvote = 0 # no votes
  253.  
  254.         # if by any chance on botstart g_allowvote is 0, we'll use the default UrT value
  255.         if self._origvote == 0:
  256.             self._origvote = 536871039
  257.         self._lastvote = self._origvote
  258.  
  259.         # how many players are allowed and if g_maxGameClients != 0 we will disable specchecking
  260.         self._sv_maxclients = self.console.getCvar('sv_maxclients').getInt()
  261.         self._g_maxGameClients = self.console.getCvar('g_maxGameClients').getInt()
  262.         self._slapSafeLevel = self.config.getint('special', 'slap_safe_level')
  263.  
  264.         self._full_ident_level = self.config.getint('special', 'paident_full_level')
  265.         # save original gear settings
  266.         try:
  267.             self._origgear = self.console.getCvar('g_gear').getInt()
  268.         except:
  269.             self._origgear = 0 # allow all weapons
  270.  
  271.         self.installCrontabs()
  272.  
  273.         self.debug('Started')
  274.  
  275.     def onLoadConfig(self):
  276.         self.LoadNameChecker()
  277.         self.LoadTeamBalancer()
  278.         self.LoadVoteDelayer()
  279.         self.LoadSpecChecker()
  280.         self.LoadSkillBalancer()
  281.         self.LoadMoonMode()
  282.         self.LoadPublicMode()
  283.         self.LoadMatchMode()
  284.         self.LoadBotSupport()
  285.         self.LoadHeadshotCounter()
  286.         self.LoadRotationManager()
  287.  
  288.     def LoadNameChecker(self):
  289.         # NAMECHECKING SETUP
  290.         try:
  291.             self._ninterval = self.config.getint('namechecker', 'ninterval')
  292.         except:
  293.             self._ninterval = 0
  294.             self.debug('Using default value (%s) for Names Interval', self._ninterval)
  295.  
  296.         # set a max interval for namechecker
  297.         if self._ninterval > 59:
  298.             self._ninterval = 59
  299.  
  300.         try:
  301.             self._checkdupes = self.config.getboolean('namechecker', 'checkdupes')
  302.         except:
  303.             self._checkdupes = True
  304.             self.debug('Using default value (%s) for checkdupes', self._checkdupes)
  305.         try:
  306.             self._checkunknown = self.config.getboolean('namechecker', 'checkunknown')
  307.         except:
  308.             self._checkunknown = True
  309.             self.debug('Using default value (%s) for checkunknown', self._checkunknown)
  310.         try:
  311.             self._checkbadnames = self.config.getboolean('namechecker', 'checkbadnames')
  312.         except:
  313.             self._checkbadnames = True
  314.             self.debug('Using default value (%s) for checkbadnames', self._checkbadnames)
  315.         try:
  316.             self._checkchanges = self.config.getboolean('namechecker', 'checkchanges')
  317.         except:
  318.             self._checkchanges = True
  319.             self.debug('Using default value (%s) for checkchanges', self._checkchanges)
  320.         try:
  321.             self._checkallowedchanges = self.config.getboolean('namechecker', 'checkallowedchanges')
  322.         except:
  323.             self._checkallowedchanges = 7
  324.             self.debug('Using default value (%s) for checkallowedchanges', self._checkallowedchanges)
  325.  
  326.         self.debug('Names Interval: %s' % (self._ninterval))
  327.         self.debug('Check badnames: %s' % (self._checkbadnames))
  328.         self.debug('Dupechecking: %s' % (self._checkdupes))
  329.         self.debug('Check unknowns: %s' % (self._checkunknown))
  330.         self.debug('Check namechanges: %s' % (self._checkchanges))
  331.         self.debug('Max. allowed namechanges per game: %s' % (self._checkallowedchanges))
  332.  
  333.     def LoadTeamBalancer(self):
  334.         # TEAMBALANCER SETUP
  335.         try:
  336.             self._tinterval = self.config.getint('teambalancer', 'tinterval')
  337.         except:
  338.             self._tinterval = 0
  339.             self.debug('Using default value (%s) for Teambalancer Interval', self._tinterval)
  340.  
  341.         # set a max interval for teamchecker
  342.         if self._tinterval > 59:
  343.             self._tinterval = 59
  344.  
  345.         try:
  346.             self._teamdiff = self.config.getint('teambalancer', 'teamdifference')
  347.         except:
  348.             self._teamdiff = 1
  349.             self.debug('Using default value (%s) for teamdiff', self._teamdiff)
  350.             # set a minimum/maximum teamdifference
  351.         if self._teamdiff < 1:
  352.             self._teamdiff = 1
  353.         if self._teamdiff > 9:
  354.             self._teamdiff = 9
  355.  
  356.         try:
  357.             self._tmaxlevel = self.config.getint('teambalancer', 'maxlevel')
  358.         except:
  359.             self._tmaxlevel = 20
  360.             self.debug('Using default value (%s) for tmaxlevel', self._tmaxlevel)
  361.         try:
  362.             self._announce = self.config.getint('teambalancer', 'announce')
  363.         except:
  364.             self._announce = 2
  365.             self.debug('Using default value (%s) for announce', self._announce)
  366.  
  367.         self.debug('TeamsInterval: %s' % (self._tinterval))
  368.         self.debug('Teambalance Difference: %s' % (self._teamdiff))
  369.         self.debug('Teambalance Maxlevel: %s' % (self._tmaxlevel))
  370.         self.debug('Teambalance Announce: %s' % (self._announce))
  371.  
  372.         # 10/21/2008 - 1.4.0b9 - mindriot
  373.         try:
  374.             self._team_change_force_balance_enable = self.config.getboolean('teambalancer',
  375.                                                                             'team_change_force_balance_enable')
  376.         except:
  377.             self._team_change_force_balance_enable = True
  378.             self.debug('Using default value (%s) for team_change_force_balance_enable',
  379.                        self._team_change_force_balance_enable)
  380.  
  381.         # 10/22/2008 - 1.4.0b10 - mindriot
  382.         try:
  383.             self._autobalance_gametypes = self.config.get('teambalancer', 'autobalance_gametypes')
  384.         except:
  385.             self._autobalance_gametypes = 'tdm'
  386.             self.debug('Using default value (%s) for autobalance_gametypes', self._autobalance_gametypes)
  387.  
  388.         self._autobalance_gametypes = self._autobalance_gametypes.lower()
  389.         self._autobalance_gametypes_array = re.split(r'[\s\,]+', self._autobalance_gametypes)
  390.  
  391.         try:
  392.             self._teamLocksPermanent = self.config.getboolean('teambalancer', 'teamLocksPermanent')
  393.         except:
  394.             self._teamLocksPermanent = False
  395.             self.debug('Using default value (%s) for teamLocksPermanent', self._teamLocksPermanent)
  396.  
  397.         try:
  398.             self._ignorePlus = self.config.getint('teambalancer', 'timedelay')
  399.         except:
  400.             self.debug('Using default value (%s) for timedelay', self._ignorePlus)
  401.  
  402.     def LoadSkillBalancer(self):
  403.         # SKILLBALANCER SETUP
  404.         try:
  405.             self._skinterval = self.config.getint('skillbalancer', 'interval')
  406.         except:
  407.             self._skinterval = 0
  408.             self.debug('Using default value (%s) for Skillbalancer Interval', self._skinterval)
  409.  
  410.         # set a max interval for skillchecker
  411.         if self._skinterval > 59:
  412.             self._skinterval = 59
  413.  
  414.         try:
  415.             self._skilldiff = self.config.getint('skillbalancer', 'difference')
  416.         except:
  417.             self._skilldiff = 0.5
  418.             self.debug('Using default value (%s) for skilldiff', self._skilldiff)
  419.         # set a minimum/maximum teamdifference
  420.         if self._skilldiff < 0.1:
  421.             self._skilldiff = 0.1
  422.         if self._skilldiff > 9:
  423.             self._skilldiff = 9
  424.  
  425.         try:
  426.             self._skill_balance_mode = self.config.getint('skillbalancer', 'mode')
  427.         except:
  428.             self._skill_balance_mode = 0
  429.             self.debug('Using default value (%s) for skill_balance_mode', self._skill_balance_mode)
  430.  
  431.         try:
  432.             self._minbalinterval = self.config.getint('skillbalancer', 'min_bal_interval')
  433.         except:
  434.             self._minbalinterval = 2
  435.             self.debug('Using default value (%s) for Skillbalancer Manual Balance Interval', self._minbalinterval)
  436.  
  437.     def LoadVoteDelayer(self):
  438.         #VOTEDELAYER SETUP
  439.         try:
  440.             self._votedelay = self.config.getint('votedelay', 'votedelay')
  441.         except:
  442.             self._votedelay = 0
  443.             self.debug('Using default value (%s) for Vote delayer', self._votedelay)
  444.             # set a max delay, setting it larger than timelimit would be foolish
  445.         timelimit = self.console.getCvar('timelimit').getInt()
  446.         if timelimit == 0 and self._votedelay != 0:
  447.             # endless map or frag limited settings
  448.             self._votedelay = 10
  449.         elif self._votedelay >= timelimit - 1:
  450.             # don't overlap rounds
  451.             self._votedelay = timelimit - 1
  452.         self.debug('Vote delay: %s' % (self._votedelay))
  453.  
  454.     def LoadSpecChecker(self):
  455.         # SPECTATOR CHECK SETUP
  456.         try:
  457.             self._sinterval = self.config.getint('speccheck', 'sinterval')
  458.         except:
  459.             self._sinterval = 0
  460.             self.debug('Using default value (%s) for speccheck interval', self._sinterval)
  461.         try:
  462.             self._smaxspectime = self.config.getint('speccheck', 'maxspectime')
  463.         except:
  464.             self._smaxspectime = 0
  465.             self.debug('Using default value (%s) for speccheck smaxspectime', self._smaxspectime)
  466.         try:
  467.             self._smaxlevel = self.config.getint('speccheck', 'maxlevel')
  468.         except:
  469.             self._smaxlevel = 0
  470.             self.debug('Using default value (%s) for speccheck maxlevel', self._smaxlevel)
  471.         try:
  472.             self._smaxplayers = self.config.getint('speccheck', 'maxplayers')
  473.         except:
  474.             #self._smaxplayers = 10
  475.             #self.debug('Using default value (%s) for speccheck maxplayers', self._smaxplayers)
  476.             self._smaxplayers = self.console.getCvar('sv_maxclients').getInt() - self.console.getCvar(
  477.                 'sv_privateClients').getInt()
  478.             self.debug('Using default server value (sv_maxclients - sv_privateClients = %s) for speccheck maxplayers',
  479.                        self._smaxplayers)
  480.  
  481.         self.debug('Speccheck interval: %s' % (self._sinterval))
  482.         self.debug('Max Spectime: %s' % (self._smaxspectime))
  483.         self.debug('Speccheck Maxlevel: %s' % (self._smaxlevel))
  484.         self.debug('Maxplayers: %s' % (self._smaxplayers))
  485.  
  486.     def LoadMoonMode(self):
  487.         #MOON MODE SETUP
  488.         try:
  489.             self._moon_on_gravity = self.config.getint('moonmode', 'gravity_on')
  490.         except:
  491.             self._moon_on_gravity = 100
  492.             self.debug('Using default value (%s) for moon mode ON', self._moon_on_gravity)
  493.         try:
  494.             self._moon_off_gravity = self.config.getint('moonmode', 'gravity_off')
  495.         except:
  496.             self._moon_off_gravity = 800
  497.             self.debug('Using default value (%s) for moon mode OFF', self._moon_off_gravity)
  498.  
  499.         self.debug('Moon ON gravity: %s' % (self._moon_on_gravity))
  500.         self.debug('Moon OFF gravity: %s' % (self._moon_off_gravity))
  501.  
  502.     def LoadPublicMode(self):
  503.         # PUBLIC MODE SETUP
  504.         try:
  505.             self.randnum = self.config.getint('publicmode', 'randnum')
  506.         except:
  507.             self.randnum = 0
  508.  
  509.         try:
  510.             self.pass_lines = None
  511.             padic = self.config.getboolean('publicmode', 'usedic')
  512.             if padic:
  513.                 padicfile = self.config.getpath('publicmode', 'dicfile')
  514.                 self.debug('trying to use password dictionnary %s' % padicfile)
  515.                 if os.path.exists(padicfile):
  516.                     stinfo = os.stat(padicfile)
  517.                     if stinfo.st_size > self._max_dic_size:
  518.                         self.warning('The dictionary file is too big. Switching to default.')
  519.                     else:
  520.                         dicfile = open(padicfile)
  521.                         text = dicfile.read().strip()
  522.                         dicfile.close()
  523.                         if text == "":
  524.                             self.warning('Dictionary file is empty. Switching to default.')
  525.                         else:
  526.                             self.pass_lines = text.splitlines()
  527.                     self.debug('Using dictionary password.')
  528.                 else:
  529.                     self.warning('Dictionary is enabled but the file doesn\'t exists. Switching to default.')
  530.         except:
  531.             traceback.print_exc()
  532.             self.debug('Cannot load dictionary config. Using default')
  533.  
  534.         try:
  535.             self._papublic_password = self.config.get('publicmode', 'g_password')
  536.             if self._papublic_password is None:
  537.                 self.warning('Can\'t setup papublic command because there is no password set in config')
  538.         except:
  539.             self._papublic_password = None
  540.             self.debug('Can\'t setup papublic command because there is no password set in config')
  541.         self.debug('papublic password set to : %s' % (self._papublic_password))
  542.  
  543.     def LoadMatchMode(self):
  544.         # MATCH MODE SETUP
  545.         self.match_plugin_disable = []
  546.         try:
  547.             self.debug('pamatch_plugins_disable/plugin : %s' % self.config.get('pamatch_plugins_disable/plugin'))
  548.             for e in self.config.get('pamatch_plugins_disable/plugin'):
  549.                 self.debug('pamatch_plugins_disable/plugin : %s' % e.text)
  550.                 self.match_plugin_disable.append(e.text)
  551.         except:
  552.             self.debug('Can\'t setup pamatch disable plugins because there is no plugins set in config')
  553.         self.gameconfig = {}
  554.         try:
  555.             for e in self.config.get('gameconfig/config'):
  556.                 self.gameconfig[e.attrib['name']] = e.text
  557.         except:
  558.             self.warning('Can\'t read gameconfig')
  559.  
  560.     def LoadBotSupport(self):
  561.         # BOT SUPPORT SETUP
  562.         try:
  563.             self._botenable = self.config.getboolean('botsupport', 'bot_enable')
  564.         except:
  565.             self._botenable = False
  566.             self.debug('Using default value (%s) for bot enable', self._botenable)
  567.         try:
  568.             self._botskill = self.config.getint('botsupport', 'bot_skill')
  569.             if self._botskill > 5:
  570.                 self._botskill = 5
  571.             elif self._botskill < 1:
  572.                 self._botskill = 1
  573.         except:
  574.             self._botskill = 4
  575.             self.debug('Using default value (%s) for bot skill', self._botskill)
  576.         try:
  577.             self._botminplayers = self.config.getint('botsupport', 'bot_minplayers')
  578.             if self._botminplayers > 16:
  579.                 self._botminplayers = 16
  580.             elif self._botminplayers < 0:
  581.                 self._botminplayers = 0
  582.         except:
  583.             self._botminplayers = 4
  584.             self.debug('Using default value (%s) for bot minimum players', self._botminplayers)
  585.         try:
  586.             maps = self.config.get('botsupport', 'bot_maps')
  587.             maps = maps.split(' ')
  588.             self._botmaps = maps
  589.         except:
  590.             self._botmaps = {}
  591.             self.debug('No maps for botsupport...')
  592.  
  593.         if self._botenable:
  594.             # if it isn't enabled already it takes a mapchange to activate
  595.             self.console.write('set bot_enable 1')
  596.             # set the correct botskill anyway
  597.         self.console.write('set g_spskill %s' % (self._botskill))
  598.  
  599.         self.debug('Bot enable: %s' % (self._botenable))
  600.         self.debug('Bot skill: %s' % (self._botskill))
  601.         self.debug('Bot minplayers: %s' % (self._botminplayers))
  602.         self.debug('Bot maps: %s' % (self._botmaps))
  603.  
  604.         # first check for botsupport
  605.         self.botsupport()
  606.  
  607.     def LoadHeadshotCounter(self):
  608.         # HEADSHOT COUNTER SETUP
  609.         try:
  610.             self._hsenable = self.config.getboolean('headshotcounter', 'hs_enable')
  611.         except:
  612.             self._hsenable = False
  613.             self.debug('Using default value (%s) for hs_enable', self._hsenable)
  614.         try:
  615.             self._hsresetvars = self.config.get('headshotcounter', 'reset_vars')
  616.             if not self._hsresetvars in ['no', 'map', 'round']:
  617.                 raise Exception('Config setting not valid.')
  618.         except:
  619.             self._hsresetvars = 'map'
  620.             self.debug('Using default value (%s) for reset_vars', self._hsresetvars)
  621.         try:
  622.             self._hsbroadcast = self.config.getboolean('headshotcounter', 'broadcast')
  623.         except:
  624.             self._hsbroadcast = True
  625.             self.debug('Using default value (%s) for broadcast', self._hsbroadcast)
  626.         try:
  627.             self._hsall = self.config.getboolean('headshotcounter', 'announce_all')
  628.         except:
  629.             self._hsall = True
  630.             self.debug('Using default value (%s) for announce_all', self._hsall)
  631.         try:
  632.             self._hspercent = self.config.getboolean('headshotcounter', 'announce_percentages')
  633.         except:
  634.             self._hspercent = True
  635.             self.debug('Using default value (%s) for announce_percentages', self._hspercent)
  636.         try:
  637.             self._hspercentmin = self.config.getint('headshotcounter', 'percent_min')
  638.         except:
  639.             self._hspercentmin = 20
  640.             self.debug('Using default value (%s) for percent_min', self._hspercentmin)
  641.         try:
  642.             self._hswarnhelmet = self.config.getboolean('headshotcounter', 'warn_helmet')
  643.         except:
  644.             self._hswarnhelmet = True
  645.             self.debug('Using default value (%s) for warn_helmet', self._hswarnhelmet)
  646.         try:
  647.             self._hswarnhelmetnr = self.config.getint('headshotcounter', 'warn_helmet_nr')
  648.         except:
  649.             self._hswarnhelmetnr = 7
  650.             self.debug('Using default value (%s) for warn_helmet_nr', self._hswarnhelmetnr)
  651.         try:
  652.             self._hswarnkevlar = self.config.getboolean('headshotcounter', 'warn_kevlar')
  653.         except:
  654.             self._hswarnkevlar = True
  655.             self.debug('Using default value (%s) for warn_kevlar', self._hswarnkevlar)
  656.         try:
  657.             self._hswarnkevlarnr = self.config.getint('headshotcounter', 'warn_kevlar_nr')
  658.         except:
  659.             self._hswarnkevlarnr = 50
  660.             self.debug('Using default value (%s) for warn_kevlar_nr', self._hswarnkevlarnr)
  661.             # making shure loghits is enabled to count headshots
  662.         if self._hsenable:
  663.             self.console.write('set g_loghits 1')
  664.  
  665.         self.debug('Headshotcounter enable: %s' % (self._hsenable))
  666.         self.debug('Broadcasting: %s' % (self._hsbroadcast))
  667.         self.debug('Announce all: %s' % (self._hsall))
  668.         self.debug('Announce percentages: %s' % (self._hspercent))
  669.         self.debug('Minimum percentage: %s' % (self._hspercentmin))
  670.         self.debug('Warn to use helmet: %s' % (self._hswarnhelmet))
  671.         self.debug('Warn after nr of hits in the head: %s' % (self._hswarnhelmetnr))
  672.         self.debug('Warn to use kevlar: %s' % (self._hswarnkevlar))
  673.         self.debug('Warn after nr of hits in the torso: %s' % (self._hswarnkevlarnr))
  674.  
  675.     def LoadRotationManager(self):
  676.         # ROTATION MANAGER SETUP
  677.         try:
  678.             self._rmenable = self.config.getboolean('rotationmanager', 'rm_enable')
  679.         except:
  680.             pass
  681.         if self._rmenable:
  682.             try:
  683.                 self._switchcount1 = self.config.getint('rotationmanager', 'switchcount1')
  684.             except:
  685.                 pass
  686.             try:
  687.                 self._switchcount2 = self.config.getint('rotationmanager', 'switchcount2')
  688.             except:
  689.                 pass
  690.             try:
  691.                 self._hysteresis = self.config.getint('rotationmanager', 'hysteresis')
  692.             except:
  693.                 pass
  694.             try:
  695.                 self._rotation_small = self.config.get('rotationmanager', 'smallrotation')
  696.             except:
  697.                 pass
  698.             try:
  699.                 self._rotation_medium = self.config.get('rotationmanager', 'mediumrotation')
  700.             except:
  701.                 pass
  702.             try:
  703.                 self._rotation_large = self.config.get('rotationmanager', 'largerotation')
  704.             except:
  705.                 pass
  706.             try:
  707.                 self._gamepath = self.config.get('rotationmanager', 'gamepath')
  708.             except:
  709.                 pass
  710.  
  711.             self.debug('Rotation Manager is enabled')
  712.             self.debug('Switchcount 1: %s' % (self._switchcount1))
  713.             self.debug('Switchcount 2: %s' % (self._switchcount2))
  714.             self.debug('Hysteresis: %s' % (self._hysteresis))
  715.             self.debug('Rotation small: %s' % (self._rotation_small))
  716.             self.debug('Rotation medium: %s' % (self._rotation_medium))
  717.             self.debug('Rotation large: %s' % (self._rotation_large))
  718.         else:
  719.             self.debug('Rotation Manager is disabled')
  720.  
  721.  
  722.     def installCrontabs(self):
  723.         # CRONTABS INSTALLATION
  724.         # Cleanup and Create the crontabs
  725.         if self._ncronTab:
  726.             # remove existing crontab
  727.             self.console.cron - self._ncronTab
  728.         if self._tcronTab:
  729.             # remove existing crontab
  730.             self.console.cron - self._tcronTab
  731.         if self._scronTab:
  732.             # remove existing crontab
  733.             self.console.cron - self._scronTab
  734.         if self._skcronTab:
  735.             # remove existing crontab
  736.             self.console.cron - self._skcronTab
  737.         if self._ninterval > 0:
  738.             self._ncronTab = b3.cron.PluginCronTab(self, self.namecheck, 0, '*/%s' % (self._ninterval))
  739.             self.console.cron + self._ncronTab
  740.         if self._tinterval > 0:
  741.             self._tcronTab = b3.cron.PluginCronTab(self, self.teamcheck, '*/%s' % (self._tinterval))
  742.             self.console.cron + self._tcronTab
  743.         if self._sinterval > 0:
  744.             self._scronTab = b3.cron.PluginCronTab(self, self.speccheck, 0, '*/%s' % (self._sinterval))
  745.             self.console.cron + self._scronTab
  746.         if self._skinterval > 0:
  747.             self._skcronTab = b3.cron.PluginCronTab(self, self.skillcheck, 0, '*/%s' % (self._skinterval))
  748.             self.console.cron + self._skcronTab
  749.  
  750.     def getCmd(self, cmd):
  751.         cmd = 'cmd_%s' % cmd
  752.         if hasattr(self, cmd):
  753.             func = getattr(self, cmd)
  754.             return func
  755.  
  756.         return None
  757.  
  758.  
  759.     def onEvent(self, event):
  760.         """\
  761.        Handle intercepted events
  762.        """
  763.         if event.type == b3.events.EVT_CLIENT_DISCONNECT:
  764.             if self._rmenable and self.console.time() > self._dontcount and self._mapchanged:
  765.                 self._playercount -= 1
  766.                 self.debug('PlayerCount: %s' % (self._playercount))
  767.                 self.adjustrotation(-1)
  768.         elif event.type == b3.events.EVT_CLIENT_AUTH:
  769.             if self._hsenable:
  770.                 self.setupVars(event.client)
  771.             if self._rmenable and self.console.time() > self._dontcount and self._mapchanged:
  772.                 self._playercount += 1
  773.                 self.debug('PlayerCount: %s' % (self._playercount))
  774.                 self.adjustrotation(+1)
  775.         elif event.type == b3.events.EVT_CLIENT_TEAM_CHANGE:
  776.             self.onTeamChange(event.data, event.client)
  777.         elif event.type == b3.events.EVT_CLIENT_DAMAGE:
  778.             self.headshotcounter(event.client, event.target, event.data)
  779.         elif event.type == b3.events.EVT_GAME_EXIT:
  780.             self._mapchanged = True
  781.             if self._botenable:
  782.                 self.botsdisable()
  783.             self.ignoreSet(self._ignorePlus)
  784.             # reset headshotcounter (per map) if applicable
  785.             if self._hsresetvars == 'map':
  786.                 self.resetVars()
  787.                 # reset number of Namechanges per client
  788.             self.resetNameChanges()
  789.             if not self._teamLocksPermanent:
  790.                 # release TeamLocks
  791.                 self.resetTeamLocks()
  792.                 #Setup timer for recounting players
  793.             if self._rmenable:
  794.                 time = 60
  795.                 self._dontcount = self.console.time() + time
  796.                 t2 = threading.Timer(time, self.recountplayers)
  797.                 self.debug('Starting RecountPlayers Timer: %s seconds' % (time))
  798.                 t2.start()
  799.         elif event.type == b3.events.EVT_GAME_ROUND_START:
  800.             self._forgetTeamContrib()
  801.             self._killhistory = []
  802.             self._lastbal = self.console.time()
  803.             # check for botsupport
  804.             if self._botenable:
  805.                 self.botsdisable()
  806.                 self.botsupport()
  807.                 # reset headshotcounter (per round) if applicable
  808.             if self._hsresetvars == 'round':
  809.                 self.resetVars()
  810.                 # ignore teambalance checking for 1 minute
  811.             self.ignoreSet(self._ignorePlus)
  812.             self._teamred = 0
  813.             self._teamblue = 0
  814.             # vote delay init
  815.             if self._votedelay > 0 and self.console.getCvar('g_allowvote').getInt() != 0:
  816.                 # delay voting
  817.                 data = 'off'
  818.                 self.votedelay(data)
  819.                 # re-enable voting
  820.                 time = self._votedelay * 60
  821.                 t1 = threading.Timer(time, self.votedelay)
  822.                 self.debug('Starting Vote delay Timer: %s seconds' % (time))
  823.                 t1.start()
  824.                 # recount players
  825.         elif event.type == b3.events.EVT_CLIENT_NAME_CHANGE:
  826.             self.onNameChange(event.data, event.client)
  827.         elif event.type == b3.events.EVT_CLIENT_KILL:
  828.             self.onKill(event.client, event.target, int(event.data[0]))
  829.         elif event.type == b3.events.EVT_CLIENT_KILL_TEAM:
  830.             self.onKillTeam(event.client, event.target, int(event.data[0]))
  831.         elif event.type == b3.events.EVT_CLIENT_ACTION:
  832.             self.onAction(event.client, event.data)
  833.         else:
  834.             self.dumpEvent(event)
  835.  
  836.     def onKill(self, killer, victim, points):
  837.         killer.var(self, 'kills', 0).value += 1
  838.         victim.var(self, 'deaths', 0).value += 1
  839.         now = self.console.time()
  840.         killer.var(self, 'teamcontribhist', []).value.append((now, 1))
  841.         victim.var(self, 'teamcontribhist', []).value.append((now, -1))
  842.         self._killhistory.append((now, killer.team))
  843.  
  844.     def onKillTeam(self, killer, victim, points):
  845.         killer.var(self, 'teamkills', 0).value += 1
  846.  
  847.     def onAction(self, client, actiontype):
  848.         if actiontype in ('flag_captured', 'flag_dropped', 'flag_returned', 'bomb_planted', 'bomb_defused'):
  849.             client.var(self, actiontype, 0).value += 1
  850.         if actiontype in ('team_CTF_redflag', 'team_CTF_blueflag'):
  851.             client.var(self, 'flag_taken', 0).value += 1
  852.  
  853.     def dumpEvent(self, event):
  854.         self.debug('poweradminurt.dumpEvent -- Type %s, Client %s, Target %s, Data %s',
  855.                    event.type, event.client, event.target, event.data)
  856.  
  857.     def _teamvar(self, client, var):
  858.         # return how much variable has changed since player joined its team
  859.         old = client.var(self, 'prev_' + var, 0).value
  860.         new = client.var(self, var, 0).value
  861.         return new - old
  862.  
  863.     def _saveTeamvars(self, client):
  864.       for var in ('kills', 'deaths', 'teamkills', 'headhits', 'helmethits',
  865.                   'flag_captured', 'flag_returned', 'bomb_planted', 'bomb_defused'):
  866.         old = client.var(self, var, 0).value
  867.         client.setvar(self, "prev_" + var, old)
  868.  
  869.     def _getScores(self, clients, usexlrstats=True):
  870.         xlrstats = usexlrstats and self.console.getPlugin('xlrstats')
  871.         playerstats = {}
  872.         maxstats = {}
  873.         minstats = {}
  874.         keys = 'hsratio', 'killratio', 'teamcontrib', 'xhsratio', 'xkillratio', 'flagperf', 'bombperf'
  875.         now = self.console.time()
  876.         for c in clients:
  877.             if not c.isvar(self, 'teamtime'):
  878.                 c.setvar(self, 'teamtime', now)
  879.             age = (now - c.var(self, 'teamtime', 0).value) / 60.0
  880.             kills = max(0, self._teamvar(c, 'kills'))
  881.             deaths = max(0, self._teamvar(c, 'deaths'))
  882.             teamkills = max(0, self._teamvar(c, 'teamkills'))
  883.             hs = self._teamvar(c, 'headhits') + self._teamvar(c, 'helmethits')
  884.             hsratio = min(1.0, hs / (1.0 + kills)) # hs can be greater than kills
  885.             killratio = kills / (1.0 + deaths + teamkills)
  886.             teamcontrib = (kills - deaths - teamkills) / (age + 1.0)
  887.             flag_taken = int(bool(c.var(self, 'flag_taken', 0).value)) # one-time bonus
  888.             flag_captured = self._teamvar(c, 'flag_captured')
  889.             flag_returned = self._teamvar(c, 'flag_returned')
  890.             flagperf = 10*flag_taken + 20*flag_captured + flag_returned
  891.             bomb_planted = self._teamvar(c, 'bomb_planted')
  892.             bomb_defused = self._teamvar(c, 'bomb_defused')
  893.             bombperf = bomb_planted + bomb_defused
  894.  
  895.             playerstats[c.id] = {
  896.                 'age': age,
  897.                 'hsratio': hsratio,
  898.                 'killratio': killratio,
  899.                 'teamcontrib': teamcontrib,
  900.                 'flagperf': flagperf,
  901.                 'bombperf': bombperf,
  902.                 }
  903.             stats = xlrstats and xlrstats.get_PlayerStats(c)
  904.             if stats:
  905.                 playerstats[c.id]['xkillratio'] = stats.ratio
  906.                 head = xlrstats.get_PlayerBody(playerid=c.cid, bodypartid=0).kills
  907.                 helmet = xlrstats.get_PlayerBody(playerid=c.cid, bodypartid=1).kills
  908.                 xhsratio = min(1.0, (head + helmet) / (1.0 + kills))
  909.                 playerstats[c.id]['xhsratio'] = xhsratio
  910.             else:
  911.                 playerstats[c.id]['xhsratio'] = 0.0
  912.                 playerstats[c.id]['xkillratio'] = 0.8
  913.             for key in keys:
  914.                 if key not in maxstats or maxstats[key] < playerstats[c.id][key]:
  915.                     maxstats[key] = playerstats[c.id][key]
  916.                 if key not in minstats or minstats[key] > playerstats[c.id][key]:
  917.                     minstats[key] = playerstats[c.id][key]
  918.         scores = {}
  919.         weights = {
  920.             'killratio': 1.0,
  921.             'teamcontrib': 0.5,
  922.             'hsratio': 0.3,
  923.             'xkillratio': 1.0,
  924.             'xhsratio': 0.5,
  925.             # weight score for mission objectives higher
  926.             'flagperf': 3.0,
  927.             'bombperf': 3.0,
  928.             }
  929.         weightsum = sum(weights[key] for key in keys)
  930.         self.debug("score: maxstats=%s" % maxstats)
  931.         self.debug("score: minstats=%s" % minstats)
  932.         for c in clients:
  933.             score = 0.0
  934.             T = min(1.0, playerstats[c.id]['age'] / 5.0) # reduce score for players who just joined
  935.             msg = []
  936.             for key in keys:
  937.                 denom = maxstats[key] - minstats[key]
  938.                 if denom < 0.0001: # accurate at ne nimis
  939.                     continue
  940.                 msg.append("%s=%.3f" % (key, playerstats[c.id][key]))
  941.                 keyscore = weights[key] * (playerstats[c.id][key] - minstats[key]) / denom
  942.                 if key in ('killratio', 'teamcontrib', 'hsratio'):
  943.                     score += T * keyscore
  944.                 else:
  945.                     score += keyscore
  946.             score /= weightsum
  947.             self.debug('score: %s %s score=%.3f age=%.2f %s' % (c.team, c.name, score,
  948.                                                                 playerstats[c.id]['age'], ' '.join(msg)))
  949.             scores[c.id] = score
  950.         return scores
  951.  
  952.     def _getRandomTeams(self, clients, checkforced=False):
  953.         blue = []
  954.         red = []
  955.         nonforced = []
  956.         for c in clients:
  957.             # ignore spectators
  958.             if c.team in (b3.TEAM_BLUE, b3.TEAM_RED):
  959.                 if checkforced and c.isvar(self, 'paforced'):
  960.                     if c.team == b3.TEAM_BLUE:
  961.                         blue.append(c)
  962.                     else:
  963.                         red.append(c)
  964.                 else:
  965.                     nonforced.append(c)
  966.             # distribute nonforced players
  967.         random.shuffle(nonforced)
  968.         n = (len(nonforced) + len(blue) + len(red)) / 2 - len(blue)
  969.         blue.extend(nonforced[:n])
  970.         red.extend(nonforced[n:])
  971.         return blue, red
  972.  
  973.     def _getTeamScore(self, team, scores):
  974.         return sum(scores.get(c.id, 0.0) for c in team)
  975.  
  976.     def _getTeamScoreDiff(self, blue, red, scores):
  977.         bluescore = self._getTeamScore(blue, scores)
  978.         redscore = self._getTeamScore(red, scores)
  979.         return bluescore - redscore
  980.  
  981.     def _getTeamScoreDiffForAdvise(self, minplayers=None):
  982.         clients = self.console.clients.getList()
  983.         gametype = self._getGameType()
  984.         tdm = (gametype == 'tdm')
  985.         scores = self._getScores(clients, usexlrstats=tdm)
  986.         blue = [c for c in clients if c.team == b3.TEAM_BLUE]
  987.         red = [c for c in clients if c.team == b3.TEAM_RED]
  988.         self.debug("advise: numblue=%d numred=%d" % (len(blue), len(red)))
  989.         if minplayers and len(blue) + len(red) < minplayers:
  990.             self.debug('advise: too few players')
  991.             return None, None
  992.         diff = self._getTeamScoreDiff(blue, red, scores)
  993.         if tdm:
  994.             bs, rs = self._getAvgKillsRatios(blue, red)
  995.             avgdiff = bs - rs
  996.             self.debug('advise: TDM blue=%.2f red=%.2f avgdiff=%.2f skilldiff=%.2f' %\
  997.                        (bs, rs, avgdiff, diff))
  998.         else:
  999.             # Just looking at kill ratios doesn't work well for CTF, so we base
  1000.             # the balance diff on the skill diff for now.
  1001.             sinceLast = self.console.time() - self._lastbal
  1002.             damping = min(1.0, sinceLast/(1.0+60.0*self._minbalinterval))
  1003.             avgdiff = 1.21*diff*damping
  1004.             self.debug('advise: CTF/BOMB avgdiff=%.2f skilldiff=%.2f damping=%.2f' %\
  1005.                        (avgdiff, diff, damping))
  1006.         return avgdiff, diff
  1007.  
  1008.     def cmd_paadvise(self, data, client, cmd=None):
  1009.         """\
  1010.        Report team skill balance, and give advice if teams are unfair
  1011.        """
  1012.         avgdiff, diff = self._getTeamScoreDiffForAdvise()
  1013.         self.console.say('Avg kill ratio diff is %.2f, skill diff is %.2f' %\
  1014.                            (avgdiff, diff))
  1015.         self._advise(avgdiff, 1)
  1016.  
  1017.     def _getRecentKills(self, T):
  1018.         t0 = self.console.time() - T
  1019.         i = len(self._killhistory)-1
  1020.         while i >= 0:
  1021.             t, team = self._killhistory[i]
  1022.             if t < t0:
  1023.                 break
  1024.             i -= 1
  1025.             yield t, team
  1026.  
  1027.     def _getAvgKillsRatios(self, blue, red):
  1028.         if not blue or not red:
  1029.             return 0.0, 0.0
  1030.         Tmin = 2.0
  1031.         Tmax = 4.0
  1032.         totkpm = len(list((self._getRecentKills(60))))
  1033.         T = max(Tmin, Tmax-0.1*totkpm)
  1034.         self.debug('recent: totkpm=%d T=%.2f' % (totkpm, T))
  1035.         recentcontrib = {}
  1036.         t0 = self.console.time() - T * 60
  1037.         for c in blue + red:
  1038.             hist = c.var(self, 'teamcontribhist', []).value
  1039.             k = 0
  1040.             d = 0
  1041.             for t, s in hist:
  1042.                 if t0 < t:
  1043.                     if s > 0:
  1044.                         k += 1
  1045.                     elif s < 0:
  1046.                         d += 1
  1047.             recentcontrib[c.id] = k/(1.0+d)
  1048.         self.debug('recent: %s' % recentcontrib)
  1049.  
  1050.         def contribcmp(a, b):
  1051.             return cmp(recentcontrib[b.id], recentcontrib[a.id])
  1052.  
  1053.         blue = sorted(blue, cmp=contribcmp)
  1054.         red = sorted(red, cmp=contribcmp)
  1055.         n = min(len(blue), len(red))
  1056.         if n > 3:
  1057.             n = 3 + int((n-3)/2)
  1058.         bs = float(sum(recentcontrib[c.id] for c in blue[:n]))/n/T
  1059.         rs = float(sum(recentcontrib[c.id] for c in red[:n]))/n/T
  1060.         self.debug('recent: n=%d T=%.2f %.2f %.2f' % (n, T, bs, rs))
  1061.         return bs, rs
  1062.  
  1063.     def _forgetTeamContrib(self):
  1064.         self._oldadv = (None, None, None)
  1065.         clients = self.console.clients.getList()
  1066.         for c in clients:
  1067.             c.setvar(self, 'teamcontribhist', [])
  1068.             self._saveTeamvars(c)
  1069.  
  1070.     def cmd_paunskuffle(self, data, client, cmd=None):
  1071.         """\
  1072.        Create unbalanced teams. Used to test !paskuffle and !pabalance.
  1073.        """
  1074.         self._balancing = True
  1075.         clients = self.console.clients.getList()
  1076.         scores = self._getScores(clients)
  1077.         decorated = [(scores.get(c.id, 0), c) for c in clients
  1078.                                               if c.team in (b3.TEAM_BLUE, b3.TEAM_RED)]
  1079.         decorated.sort()
  1080.         players = [c for score, c in decorated]
  1081.         n = len(players) / 2
  1082.         blue = players[:n]
  1083.         red = players[n:]
  1084.         self.console.write('bigtext "Unskuffling! Noobs beware!"')
  1085.         self._move(blue, red)
  1086.         self._forgetTeamContrib()
  1087.         self._balancing = False
  1088.  
  1089.     def cmd_paskuffle(self, data=None, client=None, cmd=None):
  1090.         """\
  1091.        Skill shuffle. Shuffle players to balanced teams by numbers and skill.
  1092.        Locked players are also moved.
  1093.        """
  1094.         now = self.console.time()
  1095.         sinceLast = now - self._lastbal
  1096.         if client and client.maxLevel < 20 and self.ignoreCheck() and sinceLast < 60*self._minbalinterval:
  1097.             client.message('Teams changed recently, please wait a while')
  1098.             return None
  1099.         self._balancing = True
  1100.         olddiff, bestdiff, blue, red, scores = self._randTeams(100, 0.1)
  1101.         if client:
  1102.             if (client.team == b3.TEAM_BLUE and\
  1103.                 client.cid not in [c.cid for c in blue]) or\
  1104.                (client.team == b3.TEAM_RED and\
  1105.                 client.cid not in [c.cid for c in red]):
  1106.                 # don't move player who initiated skuffle
  1107.                 blue, red = red, blue
  1108.         moves = 0
  1109.         if bestdiff is not None:
  1110.             self.console.write('bigtext "Skill Shuffle in Progress!"')
  1111.             moves = self._move(blue, red, scores)
  1112.         if moves:
  1113.             self.console.say('^4Team skill difference was ^1%.2f^4, is now ^1%.2f' % (
  1114.                 olddiff, bestdiff))
  1115.         else:
  1116.             self.console.say('^1Cannot improve team balance!')
  1117.         self._forgetTeamContrib()
  1118.         self._balancing = False
  1119.         self._lastbal = now
  1120.  
  1121.     def _countSnipers(self, team):
  1122.         n = 0
  1123.         for c in team:
  1124.             kills = max(0, c.var(self, 'kills', 0).value)
  1125.             deaths = max(0, c.var(self, 'deaths', 0).value)
  1126.             ratio = kills / (1.0 + deaths)
  1127.             if ratio < 1.2:
  1128.                 # Ignore sniper noobs
  1129.                 continue
  1130.                 # Count players with SR8 and PSG1
  1131.             gear = getattr(c, 'gear', '')
  1132.             if 'Z' in gear or 'N' in gear:
  1133.                 n += 1
  1134.         return n
  1135.  
  1136.     def _move(self, blue, red, scores=None):
  1137.         self.debug('move: final blue team: ' + ' '.join(c.name for c in blue))
  1138.         self.debug('move: final red team: ' + ' '.join(c.name for c in red))
  1139.  
  1140.         # Filter out players already in correct team
  1141.         blue = [c for c in blue if c.team != b3.TEAM_BLUE]
  1142.         red = [c for c in red if c.team != b3.TEAM_RED]
  1143.  
  1144.         if not blue and not red:
  1145.             return 0
  1146.  
  1147.         bestscore = None
  1148.  
  1149.         if scores:
  1150.             bestscore = max(scores[c.id] for c in blue + red)
  1151.  
  1152.         clients = self.console.clients.getList()
  1153.         numblue = len([c for c in clients if c.team == b3.TEAM_BLUE])
  1154.         numred = len([c for c in clients if c.team == b3.TEAM_RED])
  1155.         self.debug('move: num players: blue=%d red=%d' % (numblue, numred))
  1156.         self.ignoreSet(30)
  1157.  
  1158.         # We have to make sure we don't get a "too many players" error from the
  1159.         # server when we move the players. Start moving from the team with most
  1160.         # players. If the teams are equal in numbers, temporarily put one player in
  1161.         # spec mode.
  1162.         moves = len(blue) + len(red)
  1163.         spec = None
  1164.         self.debug('move: need to do %d moves' % moves)
  1165.         self.debug('move: will go to blue team: ' + ' '.join(c.name for c in blue))
  1166.         self.debug('move: will go to red team: ' + ' '.join(c.name for c in red))
  1167.  
  1168.         if blue and numblue == numred:
  1169.             random.shuffle(blue)
  1170.             spec = blue.pop()
  1171.             self.console.write('forceteam %s spectate' % spec.cid)
  1172.             numred -= 1
  1173.             moves -= 1
  1174.             self.debug('move: moved %s from red to spec' % spec.name)
  1175.  
  1176.         queue = []
  1177.  
  1178.         for _ in xrange(moves):
  1179.             newteam = None
  1180.             if (blue and numblue < numred) or (blue and not red):
  1181.                 c = blue.pop()
  1182.                 newteam = 'blue'
  1183.                 self.console.write('forceteam %s %s' % (c.cid, newteam))
  1184.                 numblue += 1
  1185.                 numred -= 1
  1186.                 self.debug('move: moved %s to blue' % c.name)
  1187.             elif red:
  1188.                 c = red.pop()
  1189.                 newteam = 'red'
  1190.                 self.console.write('forceteam %s %s' % (c.cid, newteam))
  1191.                 numblue -= 1
  1192.                 numred += 1
  1193.                 self.debug('move: moved %s to red' % c.name)
  1194.             if newteam and scores:
  1195.                 if newteam == "red":
  1196.                     colorpfx = '^1'
  1197.                     oldteam = "blue"
  1198.                 else:
  1199.                     colorpfx = '^4'
  1200.                     oldteam = "red"
  1201.                 if scores[c.id] == bestscore:
  1202.                     messages = [
  1203.                         "You were moved because %n team needs more noobs.",
  1204.                         "I wanted to move the best player but settled for you instead.",
  1205.                         "If you learnt to aim before you shoot I wouldn't have to move you!",
  1206.                     ]
  1207.                 else:
  1208.                     messages = [
  1209.                         "%n team needs your help! Try not to die too many times...",
  1210.                         "You have new friends now. Try not to kill them...",
  1211.                         "You have no friends now but try to kill %o team anyway...",
  1212.                         "You were moved to %n team for balance.",
  1213.                     ]
  1214.                 msg = random.choice(messages)
  1215.                 team = None
  1216.                 if '%n' in msg:
  1217.                     team = newteam
  1218.                     msg = msg.replace('%n', '%s')
  1219.                 if '%o' in msg:
  1220.                     team = oldteam
  1221.                     msg = msg.replace('%o', '%s')
  1222.                 if msg.startswith('%'):
  1223.                     team = team.capitalize()
  1224.                 if '%s' in msg:
  1225.                     msg = msg % team
  1226.                 # send priv msg after all joins, seem like we can lose the msg
  1227.                 # otherwise...
  1228.                 queue.append((c, colorpfx+msg))
  1229.  
  1230.         if spec:
  1231.             self.console.write('forceteam %s blue' % spec.cid)
  1232.             self.debug('move: moved %s from spec to blue' % spec.name)
  1233.  
  1234.         for c, msg in queue:
  1235.             c.message(msg)
  1236.  
  1237.         return moves
  1238.  
  1239.     def cmd_pabalance(self, data=None, client=None, cmd=None):
  1240.         """\
  1241.        Move as few players as needed to create teams balanced by numbers AND skill.
  1242.        Locked players are not moved.
  1243.        """
  1244.         now = self.console.time()
  1245.         sinceLast = now - self._lastbal
  1246.         if client and client.maxLevel < 20 and self.ignoreCheck() and sinceLast < 60*self._minbalinterval:
  1247.             client.message('Teams changed recently, please wait a while')
  1248.             return None
  1249.         self._balancing = True
  1250.         # always allow at least 2 moves, but don't move more than 30% of the
  1251.         # players
  1252.         olddiff, bestdiff, bestblue, bestred, scores =\
  1253.             self._randTeams(100, 0.1, 0.3)
  1254.         if bestdiff is not None:
  1255.             self.console.write('bigtext "Balancing teams!"')
  1256.             self._move(bestblue, bestred, scores)
  1257.             self.console.say('^4Team skill difference was ^1%.2f^4, is now ^1%.2f' % (
  1258.                 olddiff, bestdiff))
  1259.         else:
  1260.             # we couldn't beat the previous diff by moving only a few players, do a full skuffle
  1261.             self.cmd_paskuffle(data, client, cmd)
  1262.         self._forgetTeamContrib()
  1263.         self._balancing = False
  1264.         self._lastbal = now
  1265.  
  1266.     def _randTeams(self, times, slack, maxmovesperc=None):
  1267.         # randomize teams a few times and pick the most balanced
  1268.         clients = self.console.clients.getList()
  1269.         scores = self._getScores(clients)
  1270.         oldblue = [c for c in clients if c.team == b3.TEAM_BLUE]
  1271.         oldred = [c for c in clients if c.team == b3.TEAM_RED]
  1272.         n = len(oldblue) + len(oldred)
  1273.         olddiff = self._getTeamScoreDiff(oldblue, oldred, scores)
  1274.         self.debug('rand: n=%s' % n)
  1275.         self.debug('rand: olddiff=%.2f' % olddiff)
  1276.         bestdiff = None # best balance diff so far when diff > slack
  1277.         sbestdiff = None # best balance diff so far when diff < slack
  1278.         bestnumdiff = None # best difference in number of snipers so far
  1279.         bestblue = bestred = None # best teams so far when diff > slack
  1280.         sbestblue = sbestred = None # new teams so far when diff < slack
  1281.         epsilon = 0.0001
  1282.         if not maxmovesperc and abs(len(oldblue) - len(oldred)) > 1:
  1283.             # Teams are unbalanced by count, force both teams two have equal number
  1284.             # of players
  1285.             self.debug('rand: force new teams')
  1286.             bestblue, bestred = self._getRandomTeams(clients, checkforced=True)
  1287.             bestdiff = self._getTeamScoreDiff(bestblue, bestred, scores)
  1288.         for _ in xrange(times):
  1289.             blue, red = self._getRandomTeams(clients, checkforced=True)
  1290.             m = self._countMoves(oldblue, blue) + self._countMoves(oldred, red)
  1291.             if maxmovesperc and m > max(2, int(round(maxmovesperc * n))):
  1292.                 continue
  1293.             diff = self._getTeamScoreDiff(blue, red, scores)
  1294.             if abs(diff) <= slack:
  1295.                 # balance below slack threshold, try to distribute the snipers instead
  1296.                 numdiff = abs(self._countSnipers(blue) - self._countSnipers(red))
  1297.                 if bestnumdiff is None or numdiff < bestnumdiff:
  1298.                     # got better sniper num diff
  1299.                     if bestnumdiff is None:
  1300.                         self.debug('rand: first numdiff %d (sdiff=%.2f)' % (numdiff, diff))
  1301.                     else:
  1302.                         self.debug('rand: found better numdiff %d < %d (sdiff=%.2f)' % (numdiff, bestnumdiff, diff))
  1303.                     sbestblue, sbestred = blue, red
  1304.                     sbestdiff, bestnumdiff = diff, numdiff
  1305.                 elif numdiff == bestnumdiff and abs(diff) < abs(sbestdiff) - epsilon:
  1306.                     # same number of snipers but better balance diff
  1307.                     self.debug('rand: found better sdiff %.2f < %.2f (numdiff=%d bestnumdiff=%d)' % (
  1308.                     abs(diff), abs(sbestdiff), numdiff, bestnumdiff))
  1309.                     sbestblue, sbestred = blue, red
  1310.                     sbestdiff = diff
  1311.             elif bestdiff is None or abs(diff) < abs(bestdiff) - epsilon:
  1312.                 # balance above slack threshold
  1313.                 if bestdiff is None:
  1314.                     self.debug('rand: first diff %.2f' % abs(diff))
  1315.                 else:
  1316.                     self.debug('rand: found better diff %.2f < %.2f' % (abs(diff), abs(bestdiff)))
  1317.                 bestblue, bestred = blue, red
  1318.                 bestdiff = diff
  1319.         if bestdiff is not None:
  1320.             self.debug('rand: bestdiff=%.2f' % bestdiff)
  1321.         if sbestdiff is not None:
  1322.             self.debug('rand: sbestdiff=%.2f bestnumdiff=%d' % (sbestdiff, bestnumdiff))
  1323.             self.debug('rand: snipers: blue=%d red=%d' %\
  1324.                        (self._countSnipers(sbestblue), self._countSnipers(sbestred)))
  1325.             return olddiff, sbestdiff, sbestblue, sbestred, scores
  1326.         return olddiff, bestdiff, bestblue, bestred, scores
  1327.  
  1328.     def _countMoves(self, old, new):
  1329.         i = 0
  1330.         newnames = [c.name for c in new]
  1331.         for c in old:
  1332.             if c.name not in newnames:
  1333.                 i += 1
  1334.         return i
  1335.  
  1336.     def skillcheck(self):
  1337.         if self._balancing or self.ignoreCheck():
  1338.             return
  1339.  
  1340.         gametype = self._getGameType()
  1341.  
  1342.         # run skillbalancer only if current gametype is in autobalance_gametypes list
  1343.         try:
  1344.             self._autobalance_gametypes_array.index(gametype)
  1345.         except:
  1346.             self.debug('Current gametype (%s) is not specified in autobalance_gametypes - skillbalancer disabled',
  1347.                        self.console.game.gameType)
  1348.             return None
  1349.  
  1350.         if self._skill_balance_mode == 0:
  1351.             # disabled
  1352.             return
  1353.  
  1354.         avgdiff, diff = self._getTeamScoreDiffForAdvise(minplayers=3)
  1355.  
  1356.         if avgdiff is None:
  1357.             return None
  1358.  
  1359.         absdiff = abs(avgdiff)
  1360.         unbalanced = False
  1361.  
  1362.         if absdiff >= self._skilldiff:
  1363.             unbalanced = True
  1364.  
  1365.         if unbalanced or self._skill_balance_mode == 1:
  1366.             if absdiff > 0.2:
  1367.                 self.console.say('Avg kill ratio diff is %.2f, skill diff is %.2f' %\
  1368.                     (avgdiff, diff))
  1369.                 if self._skill_balance_mode == 1:
  1370.                     # Give advice if teams are unfair
  1371.                     self._advise(avgdiff, 2)
  1372.                 else:
  1373.                     # Only report stronger team, we will balance/skuffle below
  1374.                     self._advise(avgdiff, 0)
  1375.  
  1376.         if unbalanced:
  1377.             if self._skill_balance_mode == 2:
  1378.                 self.cmd_pabalance()
  1379.             if self._skill_balance_mode == 3:
  1380.                 self.cmd_paskuffle()
  1381.  
  1382.         return None
  1383.  
  1384.     def _advise(self, avgdiff, mode):
  1385.         # mode 0: no advice
  1386.         # mode 1: give advice
  1387.         # mode 2: give advice if teams are unfair
  1388.         absdiff = 5*abs(avgdiff)
  1389.         unfair = absdiff > 2.31 # constant carefully reviewed by an eminent team of trained Swedish scientistians :)
  1390.         word = None
  1391.         same = 'remains '
  1392.         stronger = 'has become '
  1393.         if 1 <= absdiff < 2:
  1394.             word = 'stronger'
  1395.         if 2 <= absdiff < 4:
  1396.             word = 'dominating'
  1397.         if 4 <= absdiff < 6:
  1398.             word = 'overpowering'
  1399.         if 6 <= absdiff < 8:
  1400.             word = 'supreme'
  1401.         if 8 <= absdiff < 10:
  1402.             word = 'Godlike!'
  1403.         if 10 <= absdiff:
  1404.             word = 'probably cheating :P'
  1405.             same = 'is '
  1406.             stronger = 'is '
  1407.         if word:
  1408.             oldteam, oldword, oldabsdiff = self._oldadv
  1409.             self.debug('advise: oldteam=%s oldword=%s oldabsdiff=%s' % \
  1410.                 (oldteam, oldword, oldabsdiff))
  1411.             team = avgdiff < 0 and 'Red' or 'Blue'
  1412.             if team == oldteam:
  1413.                 if word == oldword:
  1414.                     msg = '%s team %s%s' % (team, same, word)
  1415.                 elif absdiff > oldabsdiff:
  1416.                     # Stronger team is becoming even stronger
  1417.                     msg = '%s team %s%s' % (team, stronger, word)
  1418.                 elif absdiff < oldabsdiff:
  1419.                     # Stronger team is becoming weaker
  1420.                     msg = '%s team is just %s' % (team, word)
  1421.                     if absdiff < 4:
  1422.                         # Difference not too big, teams may soon be fair
  1423.                         unfair = False
  1424.             else:
  1425.                 msg = '%s team is now %s' % (team, word)
  1426.             if unfair and (mode == 1 or mode == 2):
  1427.                 msg += ', use !bal to balance the teams'
  1428.             if not unfair and mode == 1:
  1429.                 msg += ', but no action necessary yet'
  1430.             self.debug('advise: team=%s word=%s absdiff=%s' % \
  1431.                 (team, word, absdiff))
  1432.             self._oldadv = (team, word, absdiff)
  1433.         else:
  1434.             msg = 'Teams seem fair'
  1435.             self._oldadv = (None, None, None)
  1436.         self.console.say(msg)
  1437.  
  1438.     def cmd_paautoskuffle(self, data, client, cmd=None):
  1439.         modes = ["0-none", "1-advise", "2-autobalance", "3-autoskuffle"]
  1440.         if not data:
  1441.             mode = modes[self._skill_balance_mode]
  1442.             self.console.say("Skill balancer mode is '%s'" % mode)
  1443.             self.console.say("Options are %s" % ', '.join(modes))
  1444.             return
  1445.         mode = None
  1446.         try:
  1447.             mode = int(data)
  1448.         except ValueError:
  1449.             for i, m in enumerate(modes):
  1450.                 if data in m:
  1451.                     mode = i
  1452.         if mode is not None and 0 <= mode <= 3:
  1453.             self._skill_balance_mode = mode
  1454.             self.console.say("Skill balancer mode is now '%s'" % modes[mode])
  1455.             self.skillcheck()
  1456.         else:
  1457.             self.console.say("Valid options are %s" % ', '.join(modes))
  1458.  
  1459.     def cmd_paswap(self, data, client, cmd=None):
  1460.         """\
  1461.        <player1> [player2] - Swap two teams for 2 clients. If player2 is not specified, the admin
  1462.        using the command is swapped with player1. Doesn't work with spectators (exception for calling admin).
  1463.        """
  1464.         #Check the input
  1465.         input = self._adminPlugin.parseUserCmd(data)
  1466.         #Check for input. If none, exist with a message.
  1467.         if input:
  1468.             #Check if the first player exists. If none, exit.
  1469.             client1 = self._adminPlugin.findClientPrompt(input[0], client)
  1470.             if not client1:
  1471.                 return False
  1472.         else:
  1473.             client.message("Invalid parameters, try !swap client1 [client2]")
  1474.             return False
  1475.             #Check if there's a second, valid input. If no input, mark the admin to be changed.
  1476.         #If the specified player doesn't exist, exit.
  1477.         if input[1] is not None:
  1478.             client2 = self._adminPlugin.findClientPrompt(input[1], client)
  1479.             if not client2:
  1480.                 return False
  1481.         else:
  1482.             client2 = client
  1483.         if client1.team == b3.TEAM_SPEC:
  1484.             client.message("%s is a spectator! - Can't be swapped" % (client1.name))
  1485.             return False
  1486.         if client2.team == b3.TEAM_SPEC:
  1487.             client.message("%s is a spectator! - Can't be swapped" % (client2.name))
  1488.             return False
  1489.         if client1.team == client2.team:
  1490.             client.message("%s and %s are on the same team! - Swapped them anyway :p" % ((client1.name), client2.name))
  1491.             return False
  1492.         if client1.team == b3.TEAM_RED:
  1493.             self._move([client1], [client2])
  1494.         else:
  1495.             self._move([client2], [client1])
  1496.             # No need to send the message twice to the switching admin :-)
  1497.         if (client1 != client):
  1498.             client1.message("^4You were swapped with %s by the admin." % (client2.name))
  1499.         if (client2 != client):
  1500.             client2.message("^4You were swapped with %s by the admin." % (client1.name))
  1501.         client.message("^3Successfully swapped %s and %s." % (client1.name, client2.name))
  1502.         return True
  1503.  
  1504.  
  1505.     #--Commands implementation ------------------------------------------------------------------------
  1506.     # /rcon commands:
  1507.     # slap <clientnum>
  1508.     # nuke <clientnum>
  1509.     # forceteam <clientnum> <red/blue/s>
  1510.     # veto (vote cancellen)
  1511.     # mute <clientnum> <seconds>
  1512.     # pause
  1513.     # swapteams
  1514.     # shuffleteams
  1515.  
  1516.     def cmd_pateams(self, data, client, cmd=None):
  1517.         """\
  1518.        Force teambalancing (all gametypes!)
  1519.        The player with the least time in a team will be switched.
  1520.        """
  1521.         if self.teambalance():
  1522.             if self._teamsbalanced:
  1523.                 client.message('^7Teams are already balanced.')
  1524.             else:
  1525.                 client.message('^7Teams are now balanced.')
  1526.                 self._teamsbalanced = True
  1527.         else:
  1528.             client.message('^7Teambalancing failed, please try a again in a few moments.')
  1529.         return None
  1530.  
  1531.     def cmd_pavote(self, data, client=None, cmd=None):
  1532.         """\
  1533.        <on/off/reset> - Set voting on, off or reset to original value at bot start.
  1534.        Setting vote on will set the vote back to the value when it was set off.
  1535.        """
  1536.         if not data:
  1537.             if client:
  1538.                 client.message('^7Invalid or missing data, try !help pavote')
  1539.             else:
  1540.                 self.debug('No data sent to cmd_pavote')
  1541.             return False
  1542.         else:
  1543.             if data in ('on', 'off', 'reset'):
  1544.                 if client:
  1545.                     client.message('^7Voting: ^1%s' % (data))
  1546.                 else:
  1547.                     self.debug('Voting: %s' % (data))
  1548.             else:
  1549.                 if client:
  1550.                     client.message('^7Invalid data, try !help pavote')
  1551.                 else:
  1552.                     self.debug('Invalid data sent to cmd_pavote')
  1553.                 return False
  1554.  
  1555.         if data == 'off':
  1556.             curvalue = self.console.getCvar('g_allowvote').getInt()
  1557.             if curvalue != 0:
  1558.                 self._lastvote = curvalue
  1559.             self.console.setCvar('g_allowvote', '0')
  1560.         elif data == 'on':
  1561.             self.console.setCvar('g_allowvote', '%s' % self._lastvote)
  1562.         elif data == 'reset':
  1563.             self.console.setCvar('g_allowvote', '%s' % self._origvote)
  1564.         else:
  1565.             return False
  1566.  
  1567.         return True
  1568.  
  1569.     def cmd_paversion(self, data, client, cmd=None):
  1570.         """\
  1571.        This command identifies PowerAdminUrt version and creator.
  1572.        """
  1573.         cmd.sayLoudOrPM(client, 'I am PowerAdminUrt version %s by %s' % (__version__, __author__))
  1574.         return None
  1575.  
  1576.     def cmd_paexec(self, data, client, cmd=None):
  1577.         """\
  1578.        <configfile.cfg> - Execute a server configfile.
  1579.        (You must use the command exactly as it is! )
  1580.        """
  1581.         if not data:
  1582.             client.message('^7Invalid or missing data, try !help paexec')
  1583.             return False
  1584.         else:
  1585.             if re.match('^[a-z0-9_.]+.cfg$', data, re.I):
  1586.                 self.debug('Executing configfile = [%s]', data)
  1587.                 result = self.console.write('exec %s' % data)
  1588.                 cmd.sayLoudOrPM(client, result)
  1589.             else:
  1590.                 self.error('%s is not a valid configfile', data)
  1591.  
  1592.         return True
  1593.  
  1594.     def cmd_pacyclemap(self, data, client, cmd=None):
  1595.         """\
  1596.        Cycle to the next map.
  1597.        (You can safely use the command without the 'pa' at the beginning)
  1598.        """
  1599.         time.sleep(1)
  1600.         self.console.write('cyclemap')
  1601.         return True
  1602.  
  1603.     def cmd_pamaprestart(self, data, client, cmd=None):
  1604.         """\
  1605.        Restart the current map.
  1606.        (You can safely use the command without the 'pa' at the beginning)
  1607.        """
  1608.         self.console.write('map_restart')
  1609.         return True
  1610.  
  1611.     def cmd_pamapreload(self, data, client, cmd=None):
  1612.         """\
  1613.        Reload the current map.
  1614.        (You can safely use the command without the 'pa' at the beginning)
  1615.        """
  1616.         self.console.write('reload')
  1617.         return True
  1618.  
  1619.     def cmd_paset(self, data, client, cmd=None):
  1620.         """\
  1621.        <cvar> <value> - Set a server cvar to a certain value.
  1622.        (You must use the command exactly as it is! )
  1623.        """
  1624.         if not data:
  1625.             client.message('^7Invalid or missing data, try !help paset')
  1626.             return False
  1627.         else:
  1628.             # are we still here? Let's write it to console
  1629.             input = data.split(' ', 1)
  1630.             cvarName = input[0]
  1631.             value = input[1]
  1632.             self.console.setCvar(cvarName, value)
  1633.  
  1634.         return True
  1635.  
  1636.     def cmd_paget(self, data, client, cmd=None):
  1637.         """\
  1638.        <cvar> - Returns the value of a servercvar.
  1639.        (You must use the command exactly as it is! )
  1640.        """
  1641.         if not data:
  1642.             client.message('^7Invalid or missing data, try !help paget')
  1643.             return False
  1644.         else:
  1645.             # are we still here? Let's write it to console
  1646.             getcvar = data.split(' ')
  1647.             getcvarvalue = self.console.getCvar('%s' % getcvar[0])
  1648.             cmd.sayLoudOrPM(client, '%s' % getcvarvalue)
  1649.  
  1650.         return True
  1651.  
  1652.     def cmd_pabigtext(self, data, client, cmd=None):
  1653.         """\
  1654.        <message> - Print a Bold message on the center of all screens.
  1655.        (You can safely use the command without the 'pa' at the beginning)
  1656.        """
  1657.         if not data:
  1658.             client.message('^7Invalid or missing data, try !help pabigtext')
  1659.             return False
  1660.         else:
  1661.             # are we still here? Let's write it to console
  1662.             self.console.write('bigtext "%s"' % data)
  1663.  
  1664.         return True
  1665.  
  1666.     def cmd_pamute(self, data, client, cmd=None):
  1667.         """\
  1668.        <player> [<duration>] - Mute a player.
  1669.        (You can safely use the command without the 'pa' at the beginning)
  1670.        """
  1671.         # this will split the player name and the message
  1672.         input = self._adminPlugin.parseUserCmd(data)
  1673.         if input:
  1674.             # input[0] is the player id
  1675.             sclient = self._adminPlugin.findClientPrompt(input[0], client)
  1676.             if not sclient:
  1677.                 # a player matchin the name was not found, a list of closest matches will be displayed
  1678.                 # we can exit here and the user will retry with a more specific player
  1679.                 return False
  1680.         else:
  1681.             client.message('^7Invalid data, try !help pamute')
  1682.             return False
  1683.  
  1684.         if sclient.maxLevel > client.maxLevel:
  1685.             client.message('^7You don\'t have enought privileges to mute this player')
  1686.             return False
  1687.         if input[1] is not None and re.match('^([0-9]+)\s*$', input[1]):
  1688.             duration = int(input[1])
  1689.         else:
  1690.             duration = ''
  1691.  
  1692.         # are we still here? Let's write it to console
  1693.         self.console.write('mute %s %s' % (sclient.cid, duration))
  1694.  
  1695.         return True
  1696.  
  1697.     def cmd_papause(self, data, client, cmd=None):
  1698.         """\
  1699.        <message> - Pause the game. Type again to resume
  1700.        """
  1701.         result = self.console.write('pause')
  1702.         cmd.sayLoudOrPM(client, result)
  1703.  
  1704.         return True
  1705.  
  1706.     def cmd_paslap(self, data, client, cmd=None):
  1707.         """\
  1708.        <player> [<ammount>] - (multi)Slap a player.
  1709.        (You can safely use the command without the 'pa' at the beginning)
  1710.        """
  1711.         # this will split the player name and the message
  1712.         input = self._adminPlugin.parseUserCmd(data)
  1713.         if input:
  1714.             # input[0] is the player id
  1715.             sclient = self._adminPlugin.findClientPrompt(input[0], client)
  1716.             if not sclient:
  1717.                 # a player matchin the name was not found, a list of closest matches will be displayed
  1718.                 # we can exit here and the user will retry with a more specific player
  1719.                 return False
  1720.         else:
  1721.             client.message('^7Invalid data, try !help paslap')
  1722.             return False
  1723.  
  1724.         if sclient.maxLevel >= self._slapSafeLevel and client.maxLevel < 90:
  1725.             client.message('^7You don\'t have enough privileges to slap an Admin')
  1726.             return False
  1727.  
  1728.         if input[1]:
  1729.             try:
  1730.                 x = int(input[1])
  1731.             except:
  1732.                 client.message('^7Invalid data, try !help paslap')
  1733.                 return False
  1734.             if x in range(1, 26):
  1735.                 thread.start_new_thread(self.multipunish, (x, sclient, client, 'slap'))
  1736.             else:
  1737.                 client.message('^7Number of punishments out of range, must be 1 to 25')
  1738.         else:
  1739.             self.debug('Performing single slap...')
  1740.             self.console.write('slap %s' % (sclient.cid))
  1741.  
  1742.         return True
  1743.  
  1744.     def cmd_panuke(self, data, client, cmd=None):
  1745.         """\
  1746.        <player> [<ammount>] - (multi)Nuke a player.
  1747.        (You can safely use the command without the 'pa' at the beginning)
  1748.        """
  1749.         # this will split the player name and the message
  1750.         input = self._adminPlugin.parseUserCmd(data)
  1751.         if input:
  1752.             # input[0] is the player id
  1753.             sclient = self._adminPlugin.findClientPrompt(input[0], client)
  1754.             if not sclient:
  1755.                 # a player matchin the name was not found, a list of closest matches will be displayed
  1756.                 # we can exit here and the user will retry with a more specific player
  1757.                 return False
  1758.         else:
  1759.             client.message('^7Invalid data, try !help panuke')
  1760.             return False
  1761.  
  1762.         if input[1]:
  1763.             try:
  1764.                 x = int(input[1])
  1765.             except:
  1766.                 client.message('^7Invalid data, try !help panuke')
  1767.                 return False
  1768.             if x in range(1, 26):
  1769.                 thread.start_new_thread(self.multipunish, (x, sclient, client, 'nuke'))
  1770.             else:
  1771.                 client.message('^7Number of punishments out of range, must be 1 to 25')
  1772.         else:
  1773.             self.debug('Performing single nuke...')
  1774.             self.console.write('nuke %s' % (sclient.cid))
  1775.  
  1776.         return True
  1777.  
  1778.     def multipunish(self, x, sclient, client, cmd):
  1779.         self.debug('Entering multipunish...')
  1780.         #self.debug('x: %s, sclient.cid: %s, client.cid: %s, cmd: %s' %(x, sclient.cid, client.cid, cmd))
  1781.         c = 0
  1782.         while c < x:
  1783.             self.console.write('%s %s' % (cmd, sclient.cid))
  1784.             time.sleep(1)
  1785.             c += 1
  1786.  
  1787.     def cmd_paveto(self, data, client, cmd=None):
  1788.         """\
  1789.        Veto current running Vote.
  1790.        (You can safely use the command without the 'pa' at the beginning)
  1791.        """
  1792.         self.console.write('veto')
  1793.  
  1794.         return True
  1795.  
  1796.     def cmd_paforce(self, data, client, cmd=None):
  1797.         """\
  1798.        <player> <red/blue/spec/free> <lock> - Force a client to red/blue/spec or release the force (free)
  1799.        adding 'lock' will lock the player where it is forced to, default this is off.
  1800.        using 'all free' wil release all locks.
  1801.        (You can safely use the command without the 'pa' at the beginning)
  1802.        """
  1803.         # this will split the player name and the message
  1804.         input = self._adminPlugin.parseUserCmd(data)
  1805.         if input:
  1806.             # check if all Locks should be released
  1807.             if input[0] == "all" and input[1] == "free":
  1808.                 self.resetTeamLocks()
  1809.                 self.console.say('All TeamLocks were released')
  1810.                 return None
  1811.  
  1812.             # input[0] is the player id
  1813.             sclient = self._adminPlugin.findClientPrompt(input[0], client)
  1814.             if not sclient:
  1815.                 # a player matchin the name was not found, a list of closest matches will be displayed
  1816.                 # we can exit here and the user will retry with a more specific player
  1817.                 return False
  1818.         else:
  1819.             client.message('^7Invalid data, try !help paforce')
  1820.             return False
  1821.  
  1822.         if not len(input[1]):
  1823.             client.message('^7Missing data, try !help paforce')
  1824.             return False
  1825.  
  1826.         tdata = input[1].split(' ')
  1827.         team = tdata[0]
  1828.  
  1829.         try:
  1830.             if tdata[1] == 'lock':
  1831.                 lock = True
  1832.         except:
  1833.             lock = False
  1834.  
  1835.         if team == 'spec' or team == 'spectator':
  1836.             team = 's'
  1837.         if team == 'b':
  1838.             team = 'blue'
  1839.         if team == 'r':
  1840.             team = 'red'
  1841.  
  1842.         if team == 's':
  1843.             teamname = 'spectator'
  1844.         else:
  1845.             teamname = team
  1846.  
  1847.         if team == 'free':
  1848.             if sclient.isvar(self, 'paforced'):
  1849.                 sclient.message('^3Your are released by the admin')
  1850.                 client.message('^7%s ^3was released.' % (sclient.name))
  1851.                 sclient.delvar(self, 'paforced')
  1852.                 return False
  1853.             else:
  1854.                 client.message('^3There was no lock on ^7%s' % (sclient.name))
  1855.         elif team in ('red', 'blue', 's') and lock:
  1856.             sclient.message('^3Your are forced and locked to: ^7%s' % (teamname))
  1857.         elif team in ('red', 'blue', 's'):
  1858.             sclient.message('^3Your are forced to: ^7%s' % (teamname))
  1859.         else:
  1860.             client.message('^7Invalid or missing data, try !help paforce')
  1861.             return False
  1862.  
  1863.         if lock:
  1864.             sclient.setvar(self, 'paforced', team) # s, red or blue
  1865.         else:
  1866.             sclient.delvar(self, 'paforced')
  1867.  
  1868.         # are we still here? Let's write it to console
  1869.         self.console.write('forceteam %s %s' % (sclient.cid, team))
  1870.         client.message('^3%s ^7forced to ^3%s' % (sclient.name, teamname))
  1871.         return True
  1872.  
  1873.     def cmd_paswapteams(self, data, client, cmd=None):
  1874.         """\
  1875.        Swap teams.
  1876.        (You can safely use the command without the 'pa' at the beginning)
  1877.        """
  1878.         # Ignore automatic checking before giving the command
  1879.         self.ignoreSet(30)
  1880.         self.console.write('swapteams')
  1881.  
  1882.         return True
  1883.  
  1884.     def cmd_pashuffleteams(self, data, client, cmd=None):
  1885.         """\
  1886.        Shuffle teams.
  1887.        (You can safely use the command without the 'pa' at the beginning)
  1888.        """
  1889.         # Ignore automatic checking before giving the command
  1890.         self.ignoreSet(30)
  1891.         self.console.write('shuffleteams')
  1892.  
  1893.         return True
  1894.  
  1895.     def cmd_pamoon(self, data, client, cmd=None):
  1896.         """\
  1897.        Set moon mode <on/off>
  1898.        (You can safely use the command without the 'pa' at the beginning)
  1899.        """
  1900.         if not data or data not in ('on', 'off'):
  1901.             client.message('^7Invalid or missing data, try !help pamoon')
  1902.             return False
  1903.         else:
  1904.             if data == 'on':
  1905.                 self.console.setCvar('g_gravity', self._moon_on_gravity)
  1906.                 self.console.say('^7Moon mode: ^2ON')
  1907.             elif data == 'off':
  1908.                 self.console.setCvar('g_gravity', self._moon_off_gravity)
  1909.                 self.console.say('^7Moon mode: ^9OFF')
  1910.         return True
  1911.  
  1912.     def cmd_papublic(self, data, client, cmd=None):
  1913.         """\
  1914.        Set server public mode on/off
  1915.        (You can safely use the command without the 'pa' at the beginning)
  1916.        """
  1917.         if not data or data not in ('on', 'off'):
  1918.             client.message('^7Invalid or missing data, try !help papublic')
  1919.             return False
  1920.         else:
  1921.             if data == 'on':
  1922.                 self.console.setCvar('g_password', '')
  1923.                 self.console.say('^7public mode: ^2ON')
  1924.                 self.console.queueEvent(b3.events.Event(b3.events.EVT_CLIENT_PUBLIC, '', client))
  1925.             elif data == 'off':
  1926.                 newpassword = self._papublic_password
  1927.                 if self.pass_lines is not None:
  1928.                     i = random.randint(0, len(self.pass_lines) - 1)
  1929.                     newpassword = self.pass_lines[i]
  1930.  
  1931.                 for i in range(0, self.randnum):
  1932.                     newpassword += str(random.randint(1, 9))
  1933.  
  1934.                 self.debug('Private password set to: %s' % newpassword)
  1935.  
  1936.                 if newpassword is None:
  1937.                     client.message(
  1938.                         '^4ERROR :^7 can\'t set public mode off because there is no password specified in the config file')
  1939.                     return False
  1940.                 else:
  1941.                     self.console.setCvar('g_password', '%s' % (newpassword))
  1942.                     self.console.say('^7public mode: ^9OFF')
  1943.                     client.message('^7password is \'^4%s^7\'' % (newpassword))
  1944.                     client.message('^7type ^5!mapreload^7 to apply change')
  1945.                     self.console.write('bigtext "^7Server going ^3PRIVATE^7 soon !!"')
  1946.                     self.console.queueEvent(b3.events.Event(b3.events.EVT_CLIENT_PUBLIC, newpassword, client))
  1947.         return True
  1948.  
  1949.     def cmd_pamatch(self, data, client, cmd=None):
  1950.         """\
  1951.        Set server match mode on/off
  1952.        (You can safely use the command without the 'pa' at the beginning)
  1953.        """
  1954.         if not data or data not in ('on', 'off'):
  1955.             client.message('^7Invalid or missing data, try !help pamatch')
  1956.             return False
  1957.         else:
  1958.             if data == 'on':
  1959.                 self._matchmode = True
  1960.                 self.console.setCvar('g_matchmode', '1')
  1961.                 self.console.say('^7match mode: ^2ON')
  1962.                 self.console.write('bigtext "^7MATCH starting soon !!"')
  1963.                 for e in self.match_plugin_disable:
  1964.                     self.debug('Disabling plugin %s' % e)
  1965.                     plugin = self.console.getPlugin(e)
  1966.                     if plugin:
  1967.                         plugin.disable()
  1968.                         client.message('^7plugin %s disabled' % e)
  1969.                 client.message('^7type ^5!mapreload^7 to apply change')
  1970.                 self.console.write('bigtext "^7MATCH starting soon !!"')
  1971.  
  1972.             elif data == 'off':
  1973.                 self._matchmode = False
  1974.                 self.console.setCvar('g_matchmode', '0')
  1975.                 self.console.say('^7match mode: ^9OFF')
  1976.  
  1977.                 for e in self.match_plugin_disable:
  1978.                     self.debug('enabling plugin %s' % e)
  1979.                     plugin = self.console.getPlugin(e)
  1980.                     if plugin:
  1981.                         plugin.enable()
  1982.                         client.message('^7plugin %s enabled' % e)
  1983.                 client.message('^7type ^5!mapreload^7 to apply change')
  1984.         self.set_configmode(None)
  1985.         return True
  1986.  
  1987.  
  1988.     def cmd_pagear(self, data, client=None, cmd=None):
  1989.         """\
  1990.        <all/none/reset/[+-](nade|snipe|spas|pistol|auto|negev)> - Set allowed weapons.
  1991.        """
  1992.         cur_gear = self.console.getCvar('g_gear').getInt()
  1993.         if not data:
  1994.             if client:
  1995.                 nade = (cur_gear & 1) != 1
  1996.                 snipe = (cur_gear & 2) != 2
  1997.                 spas = (cur_gear & 4) != 4
  1998.                 pist = (cur_gear & 8) != 8
  1999.                 auto = (cur_gear & 16) != 16
  2000.                 nege = (cur_gear & 32) != 32
  2001.  
  2002.                 self.console.write('^7current gear: %s (Nade:%d, Sniper:%d, Spas:%d, Pistol:%d, Auto:%d, Negev:%d)' %
  2003.                                    (cur_gear, nade, snipe, spas, pist, auto, nege))
  2004.             return False
  2005.         else:
  2006.             if not data[:5] in ('all', 'none', 'reset',
  2007.                                 '+nade', '+snip', '+spas', '+pist', '+auto', '+nege',
  2008.                                 '-nade', '-snip', '-spas', '-pist', '-auto', '-nege'):
  2009.                 if client:
  2010.                     client.message('^7Invalid data, try !help pagear')
  2011.                 else:
  2012.                     self.debug('Invalid data sent to cmd_pagear')
  2013.                 return False
  2014.  
  2015.         if data[:5] == 'all':
  2016.             self.console.setCvar('g_gear', '0')
  2017.         elif data[:5] == 'none':
  2018.             self.console.setCvar('g_gear', '63')
  2019.         elif data[:5] == 'reset':
  2020.             self.console.setCvar('g_gear', '%s' % self._origgear)
  2021.         else:
  2022.             if data[1:5] == 'nade':
  2023.                 bit = 1
  2024.             elif data[1:5] == 'snip':
  2025.                 bit = 2
  2026.             elif data[1:5] == 'spas':
  2027.                 bit = 4
  2028.             elif data[1:5] == 'pist':
  2029.                 bit = 8
  2030.             elif data[1:5] == 'auto':
  2031.                 bit = 16
  2032.             elif data[1:5] == 'nege':
  2033.                 bit = 32
  2034.             else:
  2035.                 return False
  2036.  
  2037.             if data[:1] == '+':
  2038.                 self.console.setCvar('g_gear', '%s' % (cur_gear & (63 - bit)))
  2039.             elif data[:1] == '-':
  2040.                 self.console.setCvar('g_gear', '%s' % (cur_gear | bit))
  2041.             else:
  2042.                 return False
  2043.  
  2044.         return True
  2045.  
  2046.     def cmd_paffa(self, data, client, cmd=None):
  2047.         """\
  2048.        Change game type to Free For All
  2049.        (You can safely use the command without the 'pa' at the beginning)
  2050.        """
  2051.         self.console.write('g_gametype 0')
  2052.         if client:
  2053.             client.message('^7game type changed to ^4Free For All')
  2054.         self.set_configmode('ffa')
  2055.         return True
  2056.  
  2057.     def cmd_patdm(self, data, client, cmd=None):
  2058.         """\
  2059.        Change game type to Team Death Match
  2060.        (You can safely use the command without the 'pa' at the beginning)
  2061.        """
  2062.         self.console.write('g_gametype 3')
  2063.         if client:
  2064.             client.message('^7game type changed to ^4Team Death Match')
  2065.         self.set_configmode('tdm')
  2066.         return True
  2067.  
  2068.     def cmd_pats(self, data, client, cmd=None):
  2069.         """\
  2070.        Change game type to Team Survivor
  2071.        (You can safely use the command without the 'pa' at the beginning)
  2072.        """
  2073.         self.console.write('g_gametype 4')
  2074.         if client:
  2075.             client.message('^7game type changed to ^4Team Survivor')
  2076.         self.set_configmode('ts')
  2077.         return True
  2078.  
  2079.     def cmd_paftl(self, data, client, cmd=None):
  2080.         """\
  2081.        Change game type to Follow The Leader
  2082.        (You can safely use the command without the 'pa' at the beginning)
  2083.        """
  2084.         self.console.write('g_gametype 5')
  2085.         if client:
  2086.             client.message('^7game type changed to ^4Follow The Leader')
  2087.         self.set_configmode('ftl')
  2088.         return True
  2089.  
  2090.     def cmd_pacah(self, data, client, cmd=None):
  2091.         """\
  2092.        Change game type to Capture And Hold
  2093.        (You can safely use the command without the 'pa' at the beginning)
  2094.        """
  2095.         self.console.write('g_gametype 6')
  2096.         if client:
  2097.             client.message('^7game type changed to ^4Capture And Hold')
  2098.         self.set_configmode('cah')
  2099.         return True
  2100.  
  2101.     def cmd_pactf(self, data, client, cmd=None):
  2102.         """\
  2103.        Change game type to Capture The Flag
  2104.        (You can safely use the command without the 'pa' at the beginning)
  2105.        """
  2106.         self.console.write('g_gametype 7')
  2107.         if client:
  2108.             client.message('^7game type changed to ^4Capture The Flag')
  2109.         self.set_configmode('ctf')
  2110.         return True
  2111.  
  2112.     def cmd_pabomb(self, data, client, cmd=None):
  2113.         """\
  2114.        Change game type to Bomb
  2115.        (You can safely use the command without the 'pa' at the beginning)
  2116.        """
  2117.         self.console.write('g_gametype 8')
  2118.         if client:
  2119.             client.message('^7game type changed to ^4Bomb')
  2120.         self.set_configmode('bomb')
  2121.         return True
  2122.  
  2123.  
  2124.     def cmd_paident(self, data, client=None, cmd=None):
  2125.         """\
  2126.        <name> - show the ip and guid of a player
  2127.        (You can safely use the command without the 'pa' at the beginning)
  2128.        """
  2129.         input = self._adminPlugin.parseUserCmd(data)
  2130.         if not input:
  2131.             cmd.sayLoudOrPM(client, 'Your id is ^2@%s' % (client.id))
  2132.             return True
  2133.         else:
  2134.             # input[0] is the player id
  2135.             sclient = self._adminPlugin.findClientPrompt(input[0], client)
  2136.             if not sclient:
  2137.                 # a player matchin the name was not found, a list of closest matches will be displayed
  2138.                 # we can exit here and the user will retry with a more specific player
  2139.                 return False
  2140.  
  2141.         if client.maxLevel < self._full_ident_level:
  2142.             cmd.sayLoudOrPM(client,
  2143.                             '%s ^4@%s ^2%s' % (self.console.formatTime(self.console.time()), sclient.id, sclient.exactName))
  2144.         else:
  2145.             cmd.sayLoudOrPM(client, '%s ^4@%s ^2%s ^2%s ^2%s' % (
  2146.             self.console.formatTime(self.console.time()), sclient.id, sclient.exactName, sclient.ip,
  2147.             self.console.formatTime(sclient.timeAdd)))
  2148.         return True
  2149.  
  2150.     #---Teambalance Mechanism--------------------------------------------------------------------------
  2151.     """\
  2152.    /g_redteamlist en /g_blueteamlist
  2153.       they return which clients are in the red or blue team
  2154.       not with numbers but characters (clientnum 0 = A, clientnum 1 = B, etc.)
  2155.    """
  2156.  
  2157.     def onTeamChange(self, team, client):
  2158.         #store the time of teamjoin for autobalancing purposes
  2159.         client.setvar(self, 'teamtime', self.console.time())
  2160.         self.verbose('Client variable teamtime set to: %s' % client.var(self, 'teamtime').value)
  2161.         # remember current stats so we can tell how the player
  2162.         # is performing on the new team
  2163.         self._saveTeamvars(client)
  2164.  
  2165.         if not self._matchmode and client.isvar(self, 'paforced'):
  2166.             forcedTeam = client.var(self, 'paforced').value
  2167.             if team != b3.TEAM_UNKNOWN and team != self.console.getTeam(forcedTeam):
  2168.                 self.console.write('forceteam %s %s' % (client.cid, forcedTeam))
  2169.                 client.message('^1You are LOCKED! You are NOT allowed to switch!')
  2170.                 self.verbose('%s was locked and forced back to %s' % (client.name, forcedTeam))
  2171.                 # Break out of this function, nothing more to do here
  2172.             return None
  2173.  
  2174.         # 10/21/2008 - 1.4.0b9 - mindriot
  2175.         # 10/23/2008 - 1.4.0b12 - mindriot
  2176.         if self._team_change_force_balance_enable and not self._matchmode:
  2177.             # if the round just started, don't do anything
  2178.             if self.ignoreCheck():
  2179.                 return None
  2180.  
  2181.             if self.isEnabled() and not self._balancing:
  2182.             # set balancing flag
  2183.                 self._balancing = True
  2184.                 self.verbose('Teamchanged cid: %s, name: %s, team: %s' % (client.cid, client.name, team))
  2185.  
  2186.                 # are we supposed to be balanced?
  2187.                 if client.maxLevel >= self._tmaxlevel:
  2188.                     # done balancing
  2189.                     self._balancing = False
  2190.                     return None
  2191.  
  2192.                 # did player join spectators?
  2193.                 if team == b3.TEAM_SPEC:
  2194.                     self.verbose('Player joined specs')
  2195.                     # done balancing
  2196.                     self._balancing = False
  2197.                     return None
  2198.                 elif team == b3.TEAM_UNKNOWN:
  2199.                     self.verbose('Team is unknown')
  2200.                     # done balancing
  2201.                     self._balancing = False
  2202.                     return None
  2203.  
  2204.                 # check if player was allowed to join this team
  2205.                 if not self.countteams():
  2206.                     self._balancing = False
  2207.                     self.error('Aborting teambalance. Counting teams failed!')
  2208.                     return False
  2209.                 if abs(self._teamred - self._teamblue) <= self._teamdiff:
  2210.                     # teams are balanced
  2211.                     self.verbose('Teams are balanced, red: %s, blue: %s' % (self._teamred, self._teamblue))
  2212.                     # done balancing
  2213.                     self._balancing = False
  2214.                     return None
  2215.                 else:
  2216.                     # teams are not balanced
  2217.                     self.verbose('Teams are NOT balanced, red: %s, blue: %s' % (self._teamred, self._teamblue))
  2218.  
  2219.                     if self._teamred > self._teamblue:
  2220.                         # join the blue team
  2221.                         self.verbose('Forcing %s to the Blue team' % client.name)
  2222.                         self.console.write('forceteam %s blue' % client.cid)
  2223.                     else:
  2224.                         # join the red team
  2225.                         self.verbose('Forcing %s to the Red team' % client.name)
  2226.                         self.console.write('forceteam %s red' % client.cid)
  2227.  
  2228.                 # done balancing
  2229.                 self._balancing = False
  2230.  
  2231.         else:
  2232.             self.debug('onTeamChange DISABLED')
  2233.  
  2234.         return None
  2235.  
  2236.     def countteams(self):
  2237.         try:
  2238.             self._teamred = len(self.console.getCvar('g_redteamlist').getString())
  2239.             self._teamblue = len(self.console.getCvar('g_blueteamlist').getString())
  2240.             return True
  2241.         except:
  2242.             return False
  2243.  
  2244.     def _getGameType(self):
  2245.         # g_gametype //0=FreeForAll=dm, 3=TeamDeathMatch=tdm, 4=Team Survivor=ts,
  2246.         # 5=Follow the Leader=ftl, 6=Capture and Hold=cah, 7=Capture The Flag=ctf, 8=Bombmode=bm
  2247.  
  2248.         # 10/22/2008 - 1.4.0b10 - mindriot
  2249.         # if gametype is unknown when B3 is started in the middle of a game
  2250.         if self.console.game.gameType == None:
  2251.             try:
  2252.                 # find and set current gametype
  2253.                 self.console.game.gameType = self.console.defineGameType(self.console.getCvar('g_gametype').getString())
  2254.                 self.debug('Current gametype found - changed to (%s)', self.console.game.gameType)
  2255.             except:
  2256.                 self.debug('Unable to determine current gametype - remains at (%s)', self.console.game.gameType)
  2257.  
  2258.         return self.console.game.gameType
  2259.  
  2260.     def teamcheck(self):
  2261.         gametype = self._getGameType()
  2262.  
  2263.         # run teambalance only if current gametype is in autobalance_gametypes list
  2264.         try:
  2265.             self._autobalance_gametypes_array.index(gametype)
  2266.         except:
  2267.             self.debug('Current gametype (%s) is not specified in autobalance_gametypes - teambalancer disabled',
  2268.                        self.console.game.gameType)
  2269.             return None
  2270.  
  2271.         if self._skill_balance_mode != 0:
  2272.             self.debug('Skill balancer is active, not performing classic teamcheck');
  2273.  
  2274.         if self.console.time() > self._ignoreTill:
  2275.             self.teambalance()
  2276.  
  2277.         return None
  2278.  
  2279.     def teambalance(self):
  2280.         if self.isEnabled() and not self._balancing and not self._matchmode:
  2281.             #set balancing flag
  2282.             self._balancing = True
  2283.             self.verbose('Checking for balancing')
  2284.  
  2285.             if not self.countteams():
  2286.                 self._balancing = False
  2287.                 self.warning('Aborting teambalance. Counting teams failed!')
  2288.                 return False
  2289.  
  2290.             if abs(self._teamred - self._teamblue) <= self._teamdiff:
  2291.                 #teams are balanced
  2292.                 self._teamsbalanced = True
  2293.                 self.verbose('Teambalance: Teams are balanced, red: %s, blue: %s (diff: %s)' % (
  2294.                 self._teamred, self._teamblue, self._teamdiff))
  2295.                 #done balancing
  2296.                 self._balancing = False
  2297.                 return True
  2298.             else:
  2299.                 #teams are not balanced
  2300.                 self._teamsbalanced = False
  2301.                 self.verbose('Teambalance: Teams are NOT balanced, red: %s, blue: %s (diff: %s)' % (
  2302.                 self._teamred, self._teamblue, self._teamdiff))
  2303.                 if self._announce == 1:
  2304.                     self.console.write('say Autobalancing Teams!')
  2305.                 elif self._announce == 2:
  2306.                     self.console.write('bigtext "Autobalancing Teams!"')
  2307.  
  2308.                 if self._teamred > self._teamblue:
  2309.                     newteam = 'blue'
  2310.                     oldteam = b3.TEAM_RED
  2311.                 else:
  2312.                     newteam = 'red'
  2313.                     oldteam = b3.TEAM_BLUE
  2314.                 self.verbose('Smaller team is: %s' % newteam)
  2315.  
  2316.                 #endless loop protection
  2317.                 count = 25
  2318.                 while abs(self._teamred - self._teamblue) > self._teamdiff and count > 0:
  2319.                     stime = self.console.upTime()
  2320.                     self.verbose('Uptime bot: %s' % stime)
  2321.                     forceclient = None
  2322.                     clients = self.console.clients.getList()
  2323.                     for c in clients:
  2324.                         if not c.isvar(self, 'teamtime'):
  2325.                             self.debug('client has no variable teamtime')
  2326.                             # 10/22/2008 - 1.4.0b11 - mindriot
  2327.                             # store the time of teamjoin for autobalancing purposes
  2328.                             c.setvar(self, 'teamtime', self.console.time())
  2329.                             self.verbose('Client variable teamtime set to: %s' % c.var(self, 'teamtime').value)
  2330.  
  2331.                         if self.console.time() - c.var(self,
  2332.                                                        'teamtime').value < stime and c.team == oldteam and c.maxLevel < self._tmaxlevel and not c.isvar(
  2333.                             self, 'paforced'):
  2334.                             forceclient = c.cid
  2335.                             stime = self.console.time() - c.var(self, 'teamtime').value
  2336.  
  2337.                     if forceclient:
  2338.                         if newteam:
  2339.                             self.verbose('Forcing client: %s to team: %s' % (forceclient, newteam))
  2340.                             self.console.write('forceteam %s %s' % (forceclient, newteam))
  2341.                         else:
  2342.                             self.debug('No new team to force to')
  2343.                     else:
  2344.                         self.debug('No client to force')
  2345.                     count -= 1
  2346.                     #recount the teams... do we need to balance once more?
  2347.                     if not self.countteams():
  2348.                         self._balancing = False
  2349.                         self.error('Aborting teambalance. Counting teams failed!')
  2350.                         return False
  2351.  
  2352.                     # 10/28/2008 - 1.4.0b13 - mindriot
  2353.                     self.verbose(
  2354.                         'Teambalance: red: %s, blue: %s (diff: %s)' % (self._teamred, self._teamblue, self._teamdiff))
  2355.                     if self._teamred > self._teamblue:
  2356.                         newteam = 'blue'
  2357.                         oldteam = b3.TEAM_RED
  2358.                     else:
  2359.                         newteam = 'red'
  2360.                         oldteam = b3.TEAM_BLUE
  2361.                     self.verbose('Smaller team is: %s' % newteam)
  2362.  
  2363.             #done balancing
  2364.             self._balancing = False
  2365.         return True
  2366.  
  2367.     def resetTeamLocks(self):
  2368.         if self.isEnabled():
  2369.             clients = self.console.clients.getList()
  2370.             for c in clients:
  2371.                 if c.isvar(self, 'paforced'):
  2372.                     c.delvar(self, 'paforced')
  2373.             self.debug('TeamLocks Released')
  2374.         return None
  2375.  
  2376.     #---Dupes/Forbidden Names Mechanism----------------------------------------------------------------
  2377.     def namecheck(self):
  2378.         if self._matchmode:
  2379.             return None
  2380.  
  2381.         self.debug('Checking Names')
  2382.         d = {}
  2383.         if self.isEnabled() and (self.console.time() > self._ignoreTill):
  2384.             for player in self.console.clients.getList():
  2385.                 if not d.has_key(player.name):
  2386.                     d[player.name] = [player.cid]
  2387.                 else:
  2388.                     #l = d[player.name]
  2389.                     #l.append(cid)
  2390.                     #d[player.name]=l
  2391.                     d[player.name].append(player.cid)
  2392.  
  2393.             for pname, cidlist in d.items():
  2394.                 if (self._checkdupes and len(cidlist) > 1) or (self._checkunknown and (pname == 'New UrT Player')) or (
  2395.                 self._checkbadnames and (pname == 'all')):
  2396.                     self.debug('Warning Players')
  2397.                     for cid in cidlist:
  2398.                         client = self.console.clients.getByCID(cid)
  2399.                         self._adminPlugin.warnClient(client, 'badname')
  2400.                 else:
  2401.                     self.debug('No players to warn')
  2402.  
  2403.         return None
  2404.  
  2405.     def onNameChange(self, name, client):
  2406.         if self.isEnabled() and self._checkchanges and client.maxLevel < 9:
  2407.             if not client.isvar(self, 'namechanges'):
  2408.                 client.setvar(self, 'namechanges', 0)
  2409.                 client.setvar(self, 'savedname', self.clean(client.exactName))
  2410.  
  2411.             cleanedname = self.clean(client.exactName)
  2412.             ## also check if the name is ending with '_<slot num>' (happens with clients having deconnections)
  2413.             if cleanedname.endswith('_' + str(client.cid)):
  2414.                 cleanedname = cleanedname[:-len('_' + str(client.cid))]
  2415.  
  2416.             if cleanedname != client.var(self, 'savedname').value:
  2417.                 n = client.var(self, 'namechanges').value + 1
  2418.                 oldname = client.var(self, 'savedname').value
  2419.                 client.setvar(self, 'savedname', cleanedname)
  2420.                 self.debug('%s changed name %s times. His name was %s' % (cleanedname, n, oldname))
  2421.                 if n > self._checkallowedchanges:
  2422.                     client.kick('Too many namechanges!')
  2423.                 else:
  2424.                     client.setvar(self, 'namechanges', n)
  2425.                     if self._checkallowedchanges - n < 4:
  2426.                         r = self._checkallowedchanges - n
  2427.                         client.message('^1WARNING:^7 ^2%s^7 more namechanges allowed during this map!' % r)
  2428.  
  2429.         return None
  2430.  
  2431.     def resetNameChanges(self):
  2432.         if self.isEnabled() and self._checkchanges:
  2433.             clients = self.console.clients.getList()
  2434.             for c in clients:
  2435.                 if c.isvar(self, 'namechanges'):
  2436.                     c.setvar(self, 'namechanges', 0)
  2437.             self.debug('Namechanges Reset')
  2438.         return None
  2439.  
  2440.  
  2441.     #---Vote delayer at round start--------------------------------------------------------------------
  2442.     def votedelay(self, data=None):
  2443.         if not data:
  2444.             data = 'on'
  2445.         self.cmd_pavote(data)
  2446.  
  2447.  
  2448.     #---Spectator Checking-----------------------------------------------------------------------------
  2449.     def speccheck(self):
  2450.         self.debug('Checking for idle Spectators')
  2451.         if self.isEnabled() and (
  2452.         self.console.time() > self._ignoreTill) and self._g_maxGameClients == 0 and not self._matchmode:
  2453.             clients = self.console.clients.getList()
  2454.             if len(clients) < self._smaxplayers:
  2455.                 self.verbose('Clients online (%s) < maxplayers (%s), ignoring' % (len(clients), self._smaxplayers))
  2456.                 return None
  2457.  
  2458.             for c in clients:
  2459.                 if not c.isvar(self, 'teamtime'):
  2460.                     self.debug('client has no variable teamtime')
  2461.                     # 10/22/2008 - 1.4.0b11 - mindriot
  2462.                     # store the time of teamjoin for autobalancing purposes
  2463.                     c.setvar(self, 'teamtime', self.console.time())
  2464.                     self.verbose('Client variable teamtime set to: %s' % c.var(self, 'teamtime').value)
  2465.  
  2466.                 if c.maxLevel >= self._smaxlevel:
  2467.                     self.debug('%s is allowed to idle in spec.' % c.name)
  2468.                     continue
  2469.                 elif c.isvar(self, 'paforced'):
  2470.                     self.debug('%s is forced by an admin.' % c.name)
  2471.                     continue
  2472.                 elif c.team == b3.TEAM_SPEC and ( self.console.time() - c.var(self, 'teamtime').value ) > (
  2473.                 self._smaxspectime * 60 ):
  2474.                     self.debug('Warning %s for speccing on full server.' % c.name)
  2475.                     self._adminPlugin.warnClient(c, 'spec')
  2476.  
  2477.         return None
  2478.  
  2479.  
  2480.     #---Bot support------------------------------------------------------------------------------------
  2481.     def botsupport(self, data=None):
  2482.         self.debug('Checking for bot support')
  2483.         if self.isEnabled() and not self._matchmode:
  2484.             try:
  2485.                 test = self.console.game.mapName
  2486.             except:
  2487.                 self.debug('mapName not yet available')
  2488.                 return None
  2489.  
  2490.             if not self._botenable:
  2491.                 return None
  2492.             for m in self._botmaps:
  2493.                 if m == self.console.game.mapName:
  2494.                     # we got ourselves a winner
  2495.                     self.debug('Enabling bots for this map')
  2496.                     self.botsenable()
  2497.  
  2498.         return None
  2499.  
  2500.     def botsdisable(self):
  2501.         self.debug('Disabling the bots')
  2502.         self.console.write('set bot_minplayers 0')
  2503.         return None
  2504.  
  2505.     def botsenable(self):
  2506.         self.debug('Enabling the bots')
  2507.         self.console.write('set bot_minplayers %s' % (self._botminplayers))
  2508.         return None
  2509.  
  2510.  
  2511.     #---Headshot Counter-------------------------------------------------------------------------------
  2512.     def setupVars(self, client):
  2513.         if not client.isvar(self, 'totalhits'):
  2514.             client.setvar(self, 'totalhits', 0.00)
  2515.         if not client.isvar(self, 'totalhitted'):
  2516.             client.setvar(self, 'totalhitted', 0.00)
  2517.         if not client.isvar(self, 'headhits'):
  2518.             client.setvar(self, 'headhits', 0.00)
  2519.         if not client.isvar(self, 'headhitted'):
  2520.             client.setvar(self, 'headhitted', 0.00)
  2521.         if not client.isvar(self, 'helmethits'):
  2522.             client.setvar(self, 'helmethits', 0.00)
  2523.         if not client.isvar(self, 'torsohitted'):
  2524.             client.setvar(self, 'torsohitted', 0.00)
  2525.         client.setvar(self, 'hitvars', True)
  2526.         self.debug('ClientVars set up for %s' % client.name)
  2527.  
  2528.     def resetVars(self):
  2529.         if self.isEnabled() and self._hsenable:
  2530.             clients = self.console.clients.getList()
  2531.             for c in clients:
  2532.                 if c.isvar(self, 'hitvars'):
  2533.                     c.setvar(self, 'totalhits', 0.00)
  2534.                     c.setvar(self, 'totalhitted', 0.00)
  2535.                     c.setvar(self, 'headhits', 0.00)
  2536.                     c.setvar(self, 'headhitted', 0.00)
  2537.                     c.setvar(self, 'helmethits', 0.00)
  2538.                     c.setvar(self, 'torsohitted', 0.00)
  2539.             self.debug('ClientVars Reset')
  2540.         return None
  2541.  
  2542.     def headshotcounter(self, attacker, victim, data):
  2543.         if self.isEnabled() and self._hsenable and attacker.isvar(self, 'hitvars') and victim.isvar(self,
  2544.                                                                                                     'hitvars') and not self._matchmode:
  2545.             headshots = 0
  2546.             #damage = int(data[0])
  2547.             weapon = int(data[1])
  2548.             hitloc = int(data[2])
  2549.  
  2550.             # set totals
  2551.             t = attacker.var(self, 'totalhits').value + 1
  2552.             attacker.setvar(self, 'totalhits', t)
  2553.             t = victim.var(self, 'totalhitted').value + 1
  2554.             victim.setvar(self, 'totalhitted', t)
  2555.  
  2556.             # headshots... no helmet!
  2557.             if hitloc == 0:
  2558.                 t = attacker.var(self, 'headhits').value + 1
  2559.                 attacker.setvar(self, 'headhits', t)
  2560.                 t = victim.var(self, 'headhitted').value + 1
  2561.                 victim.setvar(self, 'headhitted', t)
  2562.  
  2563.             # helmethits
  2564.             elif hitloc == 1:
  2565.                 t = attacker.var(self, 'helmethits').value + 1
  2566.                 attacker.setvar(self, 'helmethits', t)
  2567.  
  2568.             # torso... no kevlar!
  2569.             elif hitloc == 2:
  2570.                 t = victim.var(self, 'torsohitted').value + 1
  2571.                 victim.setvar(self, 'torsohitted', t)
  2572.  
  2573.             # announce headshots
  2574.             if self._hsall == True and (hitloc == 0 or hitloc == 1):
  2575.                 headshots = attacker.var(self, 'headhits').value + attacker.var(self, 'helmethits').value
  2576.                 hstext = 'headshots'
  2577.                 if headshots == 1:
  2578.                     hstext = 'headshot'
  2579.  
  2580.                 percentage = int(headshots / attacker.var(self, 'totalhits').value * 100)
  2581.                 if self._hspercent == True and headshots > 5 and percentage > self._hspercentmin:
  2582.                     message = ('^2%s^7: %s %s! ^7(%s percent)' % (attacker.name, int(headshots), hstext, percentage))
  2583.                 else:
  2584.                     message = ('^2%s^7: %s %s!' % (attacker.name, int(headshots), hstext))
  2585.  
  2586.                 if self._hsbroadcast == True:
  2587.                     self.console.write(message)
  2588.                 else:
  2589.                     self.console.say(message)
  2590.  
  2591.             # wear a helmet!
  2592.             if self._hswarnhelmet == True and victim.connections < 20 and victim.var(self,
  2593.                                                                                      'headhitted').value == self._hswarnhelmetnr and hitloc == 0:
  2594.                 victim.message('You were hit in the head %s times! Consider wearing a helmet!' % self._hswarnhelmetnr)
  2595.  
  2596.             # wear kevlar!
  2597.             if self._hswarnkevlar == True and victim.connections < 20 and victim.var(self,
  2598.                                                                                      'torsohitted').value == self._hswarnkevlarnr and hitloc == 2:
  2599.                 victim.message(
  2600.                     'You were hit in the torso %s times! Wearing kevlar will reduce your number of deaths!' % self._hswarnkevlarnr)
  2601.  
  2602.         return None
  2603.  
  2604.     #---Rotation Manager-------------------------------------------------------------------------------
  2605.     def adjustrotation(self, delta):
  2606.         # if the round just started, don't do anything
  2607.         if self.console.time() < self._dontcount:
  2608.             return None
  2609.  
  2610.         if delta == +1:
  2611.             if self._playercount > (self._switchcount2 + self._hysteresis):
  2612.                 self.setrotation(3)
  2613.             elif self._playercount > (self._switchcount1 + self._hysteresis):
  2614.                 self.setrotation(2)
  2615.             else:
  2616.                 self.setrotation(1)
  2617.  
  2618.         elif delta == -1 or delta == 0:
  2619.             if self._playercount < (self._switchcount1 + (delta * self._hysteresis)):
  2620.                 self.setrotation(1)
  2621.             elif self._playercount < (self._switchcount2 + (delta * self._hysteresis)):
  2622.                 self.setrotation(2)
  2623.             else:
  2624.                 self.setrotation(3)
  2625.  
  2626.         else:
  2627.             self.error('Error: Invalid delta passed to adjustrotation')
  2628.  
  2629.         return None
  2630.  
  2631.     def setrotation(self, newrotation):
  2632.         if not self._gamepath or not self._rotation_small or not self._rotation_medium or not self._rotation_large or not self._mapchanged:
  2633.             return None
  2634.  
  2635.         if newrotation == self._currentrotation:
  2636.             return None
  2637.  
  2638.         if newrotation == 1:
  2639.             rotname = "small"
  2640.             rotation = self._rotation_small
  2641.         elif newrotation == 2:
  2642.             rotname = "medium"
  2643.             rotation = self._rotation_medium
  2644.         elif newrotation == 3:
  2645.             rotname = "large"
  2646.             rotation = self._rotation_large
  2647.         else:
  2648.             self.error('Error: Invalid newrotation passed to setrotation.')
  2649.             return None
  2650.  
  2651.         self.debug('Adjusting to %s mapRotation' % rotname)
  2652.         self.console.setCvar('g_mapcycle', rotation)
  2653.         self._currentrotation = newrotation
  2654.  
  2655.     def recountplayers(self):
  2656.         # reset, recount and set a rotation
  2657.         self._oldplayercount = self._playercount
  2658.         self._playercount = 0
  2659.  
  2660.         for p in self.console.clients.getList():
  2661.             self._playercount += 1
  2662.  
  2663.         self.debug('Initial PlayerCount: %s' % (self._playercount))
  2664.  
  2665.         if self._oldplayercount == -1:
  2666.             self.adjustrotation(0)
  2667.         elif self._playercount > self._oldplayercount:
  2668.             self.adjustrotation(+1)
  2669.         elif self._playercount < self._oldplayercount:
  2670.             self.adjustrotation(-1)
  2671.         else:
  2672.             pass
  2673.  
  2674.         #--Support Functions------------------------------------------------------------------------------
  2675.  
  2676.     def clean(self, data):
  2677.         return re.sub(self._reClean, '', data)[:20]
  2678.  
  2679.     def ignoreSet(self, data=60):
  2680.         """
  2681.        Sets the ignoreflag for an amount of seconds
  2682.        self._ignoreTill is a plugin flag that holds a time which ignoreCheck checks against
  2683.        """
  2684.         self._ignoreTill = self.console.time() + data
  2685.         return None
  2686.  
  2687.     def ignoreDel(self):
  2688.         self._ignoreTill = 0
  2689.         return None
  2690.  
  2691.     def ignoreCheck(self):
  2692.         """
  2693.        Tests if the ignore flag is set, to disable certain automatic functions when unwanted
  2694.        Returns True if the functionality should be ignored
  2695.        """
  2696.         if self._ignoreTill - self.console.time() > 0:
  2697.             return True
  2698.         else:
  2699.             return False
  2700.  
  2701.  
  2702.         #--Rcon commands------by:FSK405|Fear--------------------------------------------------------------
  2703.         # setnextmap <mapname>
  2704.         # respawngod <seconds>
  2705.         # respawndelay <seconds>
  2706.         # caplimit <caps>
  2707.         # fraglimit <frags>
  2708.         # waverespawns <on/off>
  2709.         # bluewave <seconds>
  2710.         # redwave <seconds>
  2711.         # timelimit <minutes>
  2712.         # hotpotato <minutes>
  2713.  
  2714.     def cmd_pawaverespawns(self, data, client, cmd=None):
  2715.         """\
  2716.        <on/off> - Set waverespawns on, or off.
  2717.        """
  2718.         if not data or data not in ('on', 'off'):
  2719.             client.message('^7Invalid or missing data, try !help waverespawns')
  2720.             return False
  2721.         else:
  2722.             if data == 'on':
  2723.                 self.console.setCvar('g_waverespawns', '1')
  2724.                 self.console.say('^7Wave Respawns: ^2ON')
  2725.             elif data == 'off':
  2726.                 self.console.setCvar('g_waverespawns', '0')
  2727.                 self.console.say('^7Wave Respawns: ^9OFF')
  2728.         return True
  2729.  
  2730.     def cmd_pasetnextmap(self, data, client=None, cmd=None):
  2731.         """\
  2732.        <mapname> - Set the nextmap (partial map name works)
  2733.        """
  2734.         if not data:
  2735.             client.message('^7Invalid or missing data, try !help setnextmap')
  2736.             return False
  2737.         else:
  2738.             match = self.getMapsSoundingLike(data)
  2739.             if len(match) > 1:
  2740.                 client.message('do you mean : %s ?' % string.join(match, ', '))
  2741.                 return True
  2742.             if len(match) == 1:
  2743.                 mapname = match[0]
  2744.                 self.console.write('g_nextmap %s' % mapname)
  2745.         if client:
  2746.             client.message('^7nextmap set to %s' % mapname)
  2747.         else:
  2748.             client.message('^7cannot find any map like [^4%s^7].' % data)
  2749.             return False
  2750.  
  2751.     def cmd_parespawngod(self, data, client, cmd=None):
  2752.         """\
  2753.        <seconds> - Set the respawn protection in seconds.
  2754.        """
  2755.         if not data:
  2756.             client.message('^7Invalid or missing data, try !help respawngod')
  2757.             return False
  2758.         else:
  2759.             self.console.write('g_respawnProtection "%s"' % data)
  2760.         return True
  2761.  
  2762.     def cmd_parespawndelay(self, data, client, cmd=None):
  2763.         """\
  2764.        <seconds> - Set the respawn delay in seconds.
  2765.        """
  2766.         if not data:
  2767.             client.message('^7Invalid or missing data, try !help respawndelay')
  2768.             return False
  2769.         else:
  2770.             self.console.write('g_respawnDelay "%s"' % data)
  2771.         return True
  2772.  
  2773.     def cmd_pacaplimit(self, data, client, cmd=None):
  2774.         """\
  2775.        <caps> - Set the ammount of flagcaps before map is over.
  2776.        """
  2777.         if not data:
  2778.             client.message('^7Invalid or missing data, try !help caplimit')
  2779.             return False
  2780.         else:
  2781.             self.console.write('capturelimit "%s"' % data)
  2782.         return True
  2783.  
  2784.     def cmd_patimelimit(self, data, client, cmd=None):
  2785.         """\
  2786.        <minutes> - Set the minutes before map is over.
  2787.        """
  2788.         if not data:
  2789.             client.message('^7Invalid or missing data, try !help timelimit')
  2790.             return False
  2791.         else:
  2792.             self.console.write('timelimit "%s"' % data)
  2793.         return True
  2794.  
  2795.     def cmd_pafraglimit(self, data, client, cmd=None):
  2796.         """\
  2797.        <frags> - Set the ammount of points to be scored before map is over.
  2798.        """
  2799.         if not data:
  2800.             client.message('^7Invalid or missing data, try !help fraglimit')
  2801.             return False
  2802.         else:
  2803.             self.console.write('fraglimit "%s"' % data)
  2804.         return True
  2805.  
  2806.     def cmd_pabluewave(self, data, client, cmd=None):
  2807.         """\
  2808.        <seconds> - Set the blue wave respawn time.
  2809.        """
  2810.         if not data:
  2811.             client.message('^7Invalid or missing data, try !help bluewave')
  2812.             return False
  2813.         else:
  2814.             self.console.write('g_bluewave "%s"' % data)
  2815.         return True
  2816.  
  2817.     def cmd_paredwave(self, data, client, cmd=None):
  2818.         """\
  2819.        <seconds> - Set the red wave respawn time.
  2820.        """
  2821.         if not data:
  2822.             client.message('^7Invalid or missing data, try !help redwave')
  2823.             return False
  2824.         else:
  2825.             self.console.write('g_redwave "%s"' % data)
  2826.         return True
  2827.  
  2828.     def cmd_pahotpotato(self, data, client, cmd=None):
  2829.         """\
  2830.        <minutes> - Set the flag explode time.
  2831.        """
  2832.         if not data:
  2833.             client.message('^7Invalid or missing data, try !help hotpotato')
  2834.             return False
  2835.         else:
  2836.             self.console.write('g_hotpotato "%s"' % data)
  2837.         return True
  2838.  
  2839.     def cmd_pamap(self, data, client, cmd=None):
  2840.         """\
  2841.        <map> - switch current map
  2842.        """
  2843.         if not data:
  2844.             client.message('^7You must supply a map to change to.')
  2845.             return
  2846.         match = self.getMapsSoundingLike(data)
  2847.         if len(match) > 1:
  2848.             client.message('do you mean : %s' % string.join(match, ', '))
  2849.             return True
  2850.         if len(match) == 1:
  2851.             mapname = match[0]
  2852.         else:
  2853.             client.message('^7cannot find any map like [^4%s^7].' % data)
  2854.             return False
  2855.  
  2856.         self.console.say('^7Changing map to %s' % mapname)
  2857.         time.sleep(1)
  2858.         self.console.write('map %s' % mapname)
  2859.         return True
  2860.  
  2861.  
  2862.     def getMapsSoundingLike(self, mapname):
  2863.         maplist = self.console.getMaps()
  2864.         data = mapname.strip()
  2865.  
  2866.         soundex1 = soundex(string.replace(string.replace(data, 'ut4_', ''), 'ut_', ''))
  2867.         #self.debug('soundex %s : %s' % (data, soundex1))
  2868.  
  2869.         match = []
  2870.         if data in maplist:
  2871.             match = [data]
  2872.         else:
  2873.             for m in maplist:
  2874.                 s = soundex(string.replace(string.replace(m, 'ut4_', ''), 'ut_', ''))
  2875.                 #self.debug('soundex %s : %s' % (m, s))
  2876.                 if s == soundex1:
  2877.                     #self.debug('probable map : %s', m)
  2878.                     match.append(m)
  2879.  
  2880.         if len(match) == 0:
  2881.             # suggest closest spellings
  2882.             shortmaplist = []
  2883.             for m in maplist:
  2884.                 if m.find(data) != -1:
  2885.                     shortmaplist.append(m)
  2886.             if len(shortmaplist) > 0:
  2887.                 shortmaplist.sort(key=lambda map: levenshteinDistance(data, string.replace(
  2888.                     string.replace(map.strip(), 'ut4_', ''), 'ut_', '')))
  2889.                 self.debug("shortmaplist sorted by distance : %s" % shortmaplist)
  2890.                 match = shortmaplist[:3]
  2891.             else:
  2892.                 maplist.sort(key=lambda map: levenshteinDistance(data,
  2893.                                                                  string.replace(string.replace(map.strip(), 'ut4_', ''),
  2894.                                                                                 'ut_', '')))
  2895.                 self.debug("maplist sorted by distance : %s" % maplist)
  2896.                 match = maplist[:3]
  2897.         return match
  2898.  
  2899.     #------------- SGT --------------------------------------------
  2900.     def cmd_pasetwave(self, data, client, cmd=None):
  2901.         """\
  2902.        <seconds> - Set the wave respawn time for both teams.
  2903.        """
  2904.         if not data:
  2905.             client.message('^7Invalid or missing data, try !help setwave')
  2906.             return False
  2907.         else:
  2908.             self.console.write('g_bluewave "%s"' % data)
  2909.             self.console.write('g_redwave "%s"' % data)
  2910.             return True
  2911.  
  2912.     def cmd_pasetgravity(self, data, client, cmd=None):
  2913.         """\
  2914.        <value> - Set the gravity value. default = 800 (less means less gravity)
  2915.        """
  2916.         if not data:
  2917.             client.message('^7Invalid or missing data, try !help pasetgravity')
  2918.             return False
  2919.         if data == 'def':
  2920.             data = 800
  2921.         self.console.setCvar('g_gravity', data)
  2922.         client.message('^7Gravity: %s' % data)
  2923.         return True
  2924.  
  2925.     def set_configmode(self, mode=None):
  2926.         if mode:
  2927.             if self.gameconfig.has_key('mode_%s' % mode):
  2928.                 cfgfile = self.gameconfig('mode_%s' % mode)
  2929.                 filename = os.path.join(self.console.game.fs_homepath, self.console.game.fs_game, cfgfile)
  2930.                 if os.path.isfile(filename):
  2931.                     self.debug('Executing configfile = [%s]', cfgfile)
  2932.                     self.console.write('exec %s' % cfgfile)
  2933.         cfgfile = None
  2934.         if self._matchmode:
  2935.             if self.gameconfig.has_key('matchon'):
  2936.                 cfgfile = self.gameconfig.get('matchon')
  2937.             else:
  2938.                 if self.gameconfig.has_key('matchoff'):
  2939.                     cfgfile = self.gameconfig.get('matchoff')
  2940.         if cfgfile:
  2941.             filename = os.path.join(self.console.game.fs_homepath, self.console.game.fs_game, cfgfile)
  2942.             if os.path.isfile(filename):
  2943.                 self.debug('Executing configfile = [%s]', cfgfile)
  2944.                 self.console.write('exec %s' % cfgfile)
  2945.  
  2946. if __name__ == '__main__':
  2947.     ############# setup test environment ##################
  2948.     from b3.fake import FakeConsole, joe, superadmin
  2949.     from b3.parsers.iourt41 import Iourt41Parser
  2950.     from b3.config import XmlConfigParser
  2951.  
  2952.     ## inherits from both FakeConsole and Iourt41Parser
  2953.  
  2954.     class FakeUrtConsole(FakeConsole, Iourt41Parser):
  2955.         def getCvar(self, cvarName):
  2956.             if self._reCvarName.match(cvarName):
  2957.                 #"g_password" is:"^7" default:"scrim^7"
  2958.                 val = self.writercon(cvarName)
  2959.                 self.debug('Get cvar %s = [%s]', cvarName, val)
  2960.                 if val is None:
  2961.                     return None
  2962.                     #sv_mapRotation is:gametype sd map mp_brecourt map mp_carentan map mp_dawnville map mp_depot map mp_harbor map mp_hurtgen map mp_neuville map mp_pavlov map mp_powcamp map mp_railyard map mp_rocket map mp_stalingrad^7 default:^7
  2963.  
  2964.                 for f in self._reCvar:
  2965.                     m = re.match(f, val)
  2966.                     if m:
  2967.                         #self.debug('line matched %s' % f.pattern)
  2968.                         break
  2969.  
  2970.                 if m:
  2971.                     #self.debug('m.lastindex %s' % m.lastindex)
  2972.                     if m.group('cvar').lower() == cvarName.lower() and m.lastindex > 3:
  2973.                         return b3.cvar.Cvar(m.group('cvar'), value=m.group('value'), default=m.group('default'))
  2974.                     elif m.group('cvar').lower() == cvarName.lower():
  2975.                         return b3.cvar.Cvar(m.group('cvar'), value=m.group('value'), default=m.group('value'))
  2976.                 else:
  2977.                     return None
  2978.  
  2979.         def writercon(self, msg, maxRetries=None):
  2980.             """Write a message to Rcon/Console"""
  2981.             outputrcon = b3.parsers.q3a.rcon.Rcon(self, (\
  2982.                 self.config.get('server', 'rcon_ip'),\
  2983.                 self.config.getint('server', 'port')),\
  2984.                                                   self.config.get('server', 'rcon_password'))
  2985.             res = outputrcon.write(msg, maxRetries=maxRetries)
  2986.             outputrcon.flush()
  2987.             return res
  2988.  
  2989.     b3xml = XmlConfigParser()
  2990.     b3xml.load('C:/Users/Thomas/workspace/b3/conf-urt-local/b3.xml')
  2991.     fakeConsole = FakeUrtConsole(b3xml)
  2992.     fakeConsole.startup()
  2993.  
  2994.     p = PoweradminurtPlugin(fakeConsole, config=os.path.dirname(os.path.abspath(__file__)) + '/conf/poweradminurt.xml')
  2995.     p.onStartup()
  2996.  
  2997.     ########################## ok lets test ###########################
  2998.  
  2999.     joe.connects(3)
  3000.     superadmin.connects(1)
  3001.  
  3002.     superadmin.says('!slap joe')
  3003.     superadmin.says('!slap joe 5')
  3004.  
  3005.     time.sleep(30)
Add Comment
Please, Sign In to add comment