diff --git a/b3/conf/b3.distribution.ini b/b3/conf/b3.distribution.ini
index cb55f178b..c7c3a62fa 100644
--- a/b3/conf/b3.distribution.ini
+++ b/b3/conf/b3.distribution.ini
@@ -114,6 +114,12 @@ welcome: @conf/plugin_welcome.ini
## BF3
# poweradminbf3: @conf/plugin_poweradminbf3.ini
+## COD7
+# poweradmincod7: @conf/plugin_poweradmincod7.xml
+
+## MOH
+# poweradminmoh: @conf/plugin_poweradminmoh.xml
+
## UrT 4.2
# callvote: @conf/plugin_callvote.ini
# jumper: @conf/plugin_jumper.ini
diff --git a/b3/conf/b3.distribution.xml b/b3/conf/b3.distribution.xml
index 2e6e96c2c..33f4e81a7 100644
--- a/b3/conf/b3.distribution.xml
+++ b/b3/conf/b3.distribution.xml
@@ -143,6 +143,12 @@
+
+
+
+
+
+
diff --git a/b3/conf/plugin_poweradmincod7.xml b/b3/conf/plugin_poweradmincod7.xml
new file mode 100644
index 000000000..b41937aab
--- /dev/null
+++ b/b3/conf/plugin_poweradmincod7.xml
@@ -0,0 +1,28 @@
+
+
+
+ 40
+
+ 40
+ 100
+ 100
+ 100
+ 1
+ 40
+ 100
+ 100
+ 100
+ 100
+ 100
+
+ 40
+ 40
+ 40
+
+
\ No newline at end of file
diff --git a/b3/plugins/poweradmincod7/README.md b/b3/plugins/poweradmincod7/README.md
new file mode 100644
index 000000000..354c8b426
--- /dev/null
+++ b/b3/plugins/poweradmincod7/README.md
@@ -0,0 +1,60 @@
+PowerAdminCoD7 plugin for Big Brother Bot (www.bigbrotherbot.net)
+===================================================================
+
+Author: Freelander - freelander@bigbrotherbot.net
+Author URI: http://www.bigbrotherbot.net
+Author URI: http://www.fps-gamer.net
+
+Description
+-----------
+
+This plugin adds some extra functionality to B3 in both ranked and unranked CoD:Blackops servers.
+
+Commands
+--------
+
+Ranked servers only:
+ !pasetmap - Set the next map in rotation
+
+Ranked/Unranked servers:
+ !paplaylist - Display current playlist
+ !pagetplaylists - Display available playlists
+ !pasetplaylist - Set a playlist
+ !paexcludemaps - Excludes entered maps from rotation. Example: !paexludemaps mp_villa mp_nuked mp_array
+ !paversion - Identifies PowerAdminCoD7 version and creator
+ !paident [] - Show the ip and guid of a player
+ !paset - Set a server cvar to a certain value
+ !paget - Returns the value of a server cvar
+ !pasetdlc - Turn given DLC mappack on or off. Example: !pasetdlc 1 off.
+ !palistcfg - List available server config files in b3 conf folder
+ !paload - Load a server configfile.
+
+Unranked servers only:
+ !pafastrestart - Restart current map without reloading it
+ !pamaprestart - Restart current map
+ !pagametype - Change gametype. Example: !gametype tdm
+
+Changelog
+---------
+
+ * 19.04.2011 - v1.0
+ - Initial release
+ * 02.06.2011 - v1.1
+ - DLC2 maps added
+ * 09.06.2011 - v1.2
+ - !pasetmap (!setmap) command now works both with console map names and easy map names
+ as well as console map name without "mp_" at the beginning. (as printed out
+ by !maps command)
+ - Added new commands !pasetdlc, !palistcfg and !paload
+ * 28.07.2011 - 1.3
+ - DLC3 maps added
+ * 19.05.2015 - 1.4
+ - made the plugin official
+
+Notes
+-----
+
+ If you're loading a config file using !paload command, it may take a while to load the
+ complete file depending on the file size. Note that, during the loading process, other B3 commands
+ issued will be queued but they may timeout. Hence it is NOT recommended to load config files in peak
+ server times.
\ No newline at end of file
diff --git a/b3/plugins/poweradmincod7/__init__.py b/b3/plugins/poweradmincod7/__init__.py
new file mode 100644
index 000000000..cad9a5478
--- /dev/null
+++ b/b3/plugins/poweradmincod7/__init__.py
@@ -0,0 +1,492 @@
+#
+# PowerAdmin Plugin for BigBrotherBot(B3) (www.bigbrotherbot.net)
+# Copyright (C) 2011 Freelander - www.fps-gamer.net
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+__version__ = '1.4'
+__author__ = 'Freelander'
+
+import b3
+import b3.events
+import b3.plugin
+import os
+import threading
+import time
+import string
+
+from b3.functions import getCmd
+
+class Poweradmincod7Plugin(b3.plugin.Plugin):
+
+ requiresParsers = ['cod7']
+
+ _adminPlugin = None
+ _isranked = None
+ _issetmap = False
+ _isplaylist_enabled = True
+ _admin_excluded_maps = None
+
+ _cod7maps = {
+ 'mp_array': 'array',
+ 'mp_cracked': 'cracked',
+ 'mp_crisis': 'crisis',
+ 'mp_firingrange': 'firing range',
+ 'mp_duga': 'grid',
+ 'mp_hanoi': 'hanoi',
+ 'mp_cairo': 'havana',
+ 'mp_havoc': 'jungle',
+ 'mp_cosmodrome': 'launch',
+ 'mp_nuked': 'nuketown',
+ 'mp_radiation': 'radiation',
+ 'mp_mountain': 'summit',
+ 'mp_villa': 'villa',
+ 'mp_russianbase': 'wmd',
+ 'mp_berlinwall2': 'berlin wall',
+ 'mp_discovery': 'discovery',
+ 'mp_kowloon': 'kowloon',
+ 'mp_stadium': 'stadium',
+ 'mp_gridlock': 'convoy',
+ 'mp_hotel': 'hotel',
+ 'mp_outskirts': 'stockpile',
+ 'mp_zoo': 'zoo',
+ 'mp_area51': 'hangar 18',
+ 'mp_drivein': 'drive-in',
+ 'mp_silo': 'silo',
+ 'mp_golfcourse': 'hazard'
+ }
+
+ _playlists = {
+ 1: 'Team Deathmatch',
+ 2: 'Free For All',
+ 3: 'Capture The Flag',
+ 4: 'Search & Destroy',
+ 5: 'Headquarters',
+ 6: 'Domination',
+ 7: 'Sabotage',
+ 8: 'Demolition',
+ 9: 'Hardcore Team Deathmatch',
+ 10: 'Hardcore Free For All',
+ 11: 'Hardcore Capture The Flag',
+ 12: 'Hardcore Search & Destroy',
+ 13: 'Hardcore Headquarters',
+ 14: 'Hardcore Domination',
+ 15: 'Hardcore Sabotage',
+ 16: 'Hardcore Demolition',
+ 17: 'Barebones Team Deathmatch',
+ 18: 'Barebones Free For All',
+ 19: 'Barebones Capture The Flag',
+ 20: 'Barebones Search & Destroy',
+ 21: 'Barebones Headquarters',
+ 22: 'Barebones Domination',
+ 23: 'Barebones Sabotage',
+ 24: 'Barebones Demolition',
+ 25: 'Team Tactical'
+ }
+
+ ####################################################################################################################
+ # #
+ # STARTUP #
+ # #
+ ####################################################################################################################
+
+ def onStartup(self):
+ """
+ Initialize plugin settings
+ """
+ # get the admin plugin so we can register commands
+ self._adminPlugin = self.console.getPlugin('admin')
+ if not self._adminPlugin:
+ # something is wrong, can't start without admin plugin
+ raise AttributeError('could not find admin plugin')
+
+ # register our commands
+ if 'commands' in self.config.sections():
+ for cmd in self.config.options('commands'):
+ level = self.config.get('commands', cmd)
+ sp = cmd.split('-')
+ alias = None
+ if len(sp) == 2:
+ cmd, alias = sp
+ func = getCmd(self, cmd)
+ if func:
+ self._adminPlugin.registerCommand(self, cmd, level, func, alias)
+
+ # register our events
+ self.registerEvent('EVT_GAME_ROUND_START', self.onGameRoundStart)
+
+ # check if server is ranked/unranked
+ self._isranked = self.isranked()
+ self.debug('This server is %s' % 'ranked' if self._isranked else 'unranked')
+ if not self._isranked:
+ # check playlist status
+ self._isplaylist_enabled = self.isplaylist_enabled()
+
+ # get maplist excluded by server admin
+ m = self.console.getCvar('playlist_excludeMap')
+ if m:
+ self._admin_excluded_maps = m.value
+ if self._admin_excluded_maps != '':
+ self.debug('Excluded maps by admin are: %s' % self._admin_excluded_maps)
+ else:
+ self.debug('No maps found in map exclusion list')
+
+ def onGameRoundStart(self, _):
+ """
+ Handle EVT_GAME_ROUND_START.
+ """
+ if self._issetmap:
+ self.console.write ('setadmindvar playlist_excludeMap "%s"' % self._admin_excluded_maps, maxRetries=5)
+ self._issetmap = False
+ self.debug('clearing map exclusion list')
+
+ ####################################################################################################################
+ # #
+ # COMMANDS #
+ # #
+ ####################################################################################################################
+
+ def cmd_paplaylist(self, data, client, cmd):
+ """
+ Display current playlist.
+ (You can safely use the command without the 'pa' at the beginning)
+ """
+ cvar = self.console.getCvar('playlist')
+ if cvar:
+ playlist = cvar.getInt()
+ cmd.sayLoudOrPM(client, '^7Current Playlist is ^3%s - %s' % (playlist, self._playlists[playlist]))
+
+ def cmd_pagetplaylists(self, data, client, cmd):
+ """
+ Display available playlists
+ (You can safely use the command without the 'pa' at the beginning)
+ """
+ for n, p in sorted(self._playlists.iteritems()):
+ cmd.sayLoudOrPM(client, '%s - %s' % (n, p))
+ time.sleep(1)
+
+ def cmd_pasetplaylist(self, data, client, cmd):
+ """
+ - Set a playlist
+ (You can safely use the command without the 'pa' at the beginning)
+ """
+ if not self._isplaylist_enabled and not self._isranked:
+ client.message('Playlists are not enabled in this server. You can\'t set a playlist!')
+ return
+
+ _number_of_playlists = len(self._playlists)
+
+ if not data:
+ client.message('missing parameter, try !help pasetplaylist')
+ return
+
+ try:
+ float(data)
+ except ValueError:
+ client.message('Please use a playlist number, %s is not a numeric value' % data)
+ return
+
+ data = int(data)
+ if data not in range(1, _number_of_playlists + 1):
+ client.message('Playlist number %s out of range! Please enter a valid number' % data)
+ else:
+ self.console.write('setadmindvar playlist %s' % data, maxRetries=5)
+ client.message('Changing playlist to ^3%s - %s' % (data, self._playlists[data]))
+
+ def cmd_pasetmap(self, data, client, cmd=None):
+ """
+ - Set the next map in rotation
+ (You can safely use the command without the 'pa' at the beginning)
+ """
+ if not self._isranked:
+ client.message('This command is not functional in unranked servers. '
+ 'You can use ^2!map ^7or ^2!maprotate ^7 commands instead.')
+ return
+
+ if not data:
+ client.message('missing parameter, try !help pasetmap')
+ return
+
+ self.debug('Requested map for next round is %s' % data)
+ data = data.lower()
+
+ if data in self._cod7maps.keys():
+ mapname = data
+ self.debug('%s is a console mapname' % data)
+ elif ('mp_%s' % data) in self._cod7maps.keys():
+ mapname = ('mp_%s' % data)
+ self.debug('%s is considered to be %s' % (data, mapname))
+ elif data in self._cod7maps.values():
+ cod7maps_inverse = dict((self._cod7maps[k], k) for k in self._cod7maps)
+ mapname = cod7maps_inverse[data]
+ self.debug('%s is an easy mapname, console name is %s' % (data, mapname))
+ else:
+ client.message('%s is not a stock CoD7 map, please check your spelling and try again!' % data)
+ return
+
+ excludeMaps = self._cod7maps.keys()
+ excludeMaps.remove(mapname)
+ self.console.write('setadmindvar playlist_excludeMap "%s"' % ' '.join(excludeMaps), maxRetries=5)
+ client.message('Setting map ^3%s for next round' % self._cod7maps[mapname].title())
+ self._issetmap = True
+
+ def cmd_paexcludemaps(self, data, client, cmd=None):
+ """
+ Excludes entered maps from rotation.
+ Leave a space between mapnames. Example: !paexludemaps mp_villa mp_nuked mp_array
+ (You can safely use the command without the 'pa' at the beginning)
+ """
+ if not self._isplaylist_enabled and not self._isranked:
+ client.message('Playlists are not enabled in this server. You can\'t make an exclusion list!')
+ return
+
+ if not data:
+ client.message('missing parameter, try !help pasetplaylist')
+ return
+
+ # check if mapnames typed correctly
+ mapnames = data.split(' ')
+ for m in mapnames:
+ if m not in self._cod7maps.keys():
+ client.message('%s is not a valid mapname, please check your spelling and try again' % m)
+ return False
+
+ self.console.write('setadmindvar playlist_excludeMap "%s"' % data, maxRetries=5)
+ # updating exclusion list variable
+ self._admin_excluded_maps = data
+ self.debug('Maps: %s excluded from current playlist' % mapnames)
+
+ def cmd_paversion(self, data, client, cmd=None):
+ """
+ This command identifies PowerAdminCoD7 version and creator.
+ """
+ cmd.sayLoudOrPM(client, 'I am PowerAdminCoD7 version %s by %s' % (__version__, __author__))
+
+ def cmd_paident(self, data, client, cmd=None):
+ """
+ [] - show the ip and guid of a player
+ """
+ x = self._adminPlugin.parseUserCmd(data)
+ if not x:
+ try:
+ cmd.sayLoudOrPM(client, '%s: %s - %s' % (client.name, client.ip, client.guid))
+ except Exception, err:
+ client.message('Error, server replied %s' % err)
+ else:
+ try:
+ sclient = self._adminPlugin.findClientPrompt(x[0], client)
+ if sclient:
+ cmd.sayLoudOrPM(client, '%s: %s - %s' % (sclient.name, sclient.ip, sclient.guid))
+ except Exception, err:
+ client.message('Error, server replied %s' % err)
+
+ def cmd_paset(self, data, client, cmd=None):
+ """
+ - Set a server cvar to a certain value.
+ (You must use the command exactly as it is! )
+ """
+ if not data:
+ client.message('^7Invalid or missing data, try !help paset')
+ else:
+ x = data.split(' ',1)
+ cvarName = x[0].lower()
+ value = x[1]
+ self.console.setCvar(cvarName, value)
+ client.message('Setting %s to %s' % (cvarName, value))
+ # check if we are setting a new map exclusion list
+ # then we'll need to update exclusion list made by server admin
+ if cvarName.lower() == 'playlist_excludemap':
+ self._admin_excluded_maps = value
+
+ def cmd_paget(self, data, client, cmd=None):
+ """
+ - Returns the value of a servercvar.
+ (You must use the command exactly as it is! )
+ """
+ if not data:
+ client.message('^7Invalid or missing data, try !help paget')
+ else:
+ # are we still here? Let's write it to console
+ getcvar = data.split(' ')
+ getcvarvalue = self.console.getCvar( '%s' % getcvar[0] )
+ cmd.sayLoudOrPM(client, '%s' % getcvarvalue)
+
+ def cmd_pafastrestart(self, data, client, cmd):
+ """
+ Restart a map without reloading it
+ (You can safely use the command without the 'pa' at the beginning)
+ """
+ if self._isranked:
+ client.message('You can\'t restart a map on ranked servers')
+ else:
+ self.console.say('Fast restarting map in 2 seconds...')
+ time.sleep(2)
+ self.console.write('fast_restart', maxRetries=5)
+
+ def cmd_pamaprestart(self, data, client, cmd):
+ """
+ Restart a map
+ (You can safely use the command without the 'pa' at the beginning)
+ """
+ if self._isranked:
+ client.message('You can\'t restart a map on ranked servers')
+ else:
+ self.console.say('Restarting map in 2 seconds...')
+ time.sleep(2)
+ self.console.write('map_restart', maxRetries=5)
+
+ def cmd_pagametype(self, data, client, cmd):
+ """
+ Change gametype. Example: !gametype tdm
+ (You can safely use the command without the 'pa' at the beginning)
+ """
+ if self._isranked:
+ client.message('This command is not available on ranked servers!')
+ return
+
+ gametypes = {
+ 'dm' : 'Free-For-All',
+ 'tdm' : 'Team Deathmatch',
+ 'sd' : 'Search and Destroy',
+ 'dom' : 'Domination',
+ 'sab' : 'Sabotage',
+ 'ctf' : 'Capture the Flag',
+ 'koth' : 'Headquarters',
+ 'dem' : 'Demolition',
+ 'oic' : 'One in the Chamber',
+ 'hlnd' : 'Sticks and Stones',
+ 'gun' : 'Gun Game',
+ 'shrp' : 'Sharpshooter'
+ }
+
+ if not data:
+ client.message('missing parameter, try !help pagametype')
+ else:
+ # check if gametype is valid
+ data = data.split(' ')
+ gametype = data[0].lower()
+ for g in gametypes:
+ if gametype not in gametypes.keys():
+ client.message('^3%s ^7is not a valid gametype, please try again!' % gametype)
+ return False
+
+ self.console.say('Changing gametype to %s, map will restart in 5 seconds' % gametypes[gametype])
+ self.console.write('g_gametype %s' % gametype, maxRetries=5)
+ time.sleep(5)
+ self.console.write('map_restart', maxRetries=5)
+
+ def cmd_pasetdlc(self, data, client, cmd):
+ """
+ - Turn given DLC mappack on or off. Example: !pasetdlc 1 off.
+ (You can safely use the command without the 'pa' at the beginning)
+ """
+ if not data:
+ client.message('Missing parameter, try !help pasetdlc')
+ else:
+ data = data.split(' ')
+ if len(data) < 2:
+ client.message('Missing parameter, try !help pasetdlc')
+ return
+
+ if not data[0].isdigit():
+ client.message('Invalid DLC number!')
+ return
+ else:
+ data[0] = int(data[0])
+ # Treyarch is counting DLC numbers starting from 2
+ dlcnumber = data[0] + 1
+
+ if data[1].lower() in ('on', 'off'):
+ if data[1].lower() == 'off':
+ self.console.write('setadmindvar playlist_excludeDlc%s "1"' % dlcnumber, maxRetries=5)
+ client.message('DLC%s Mappack is OFF' % data[0])
+ if data[1].lower() == 'on':
+ self.console.write('setadmindvar playlist_excludeDlc%s "0"' % dlcnumber, maxRetries=5)
+ client.message('DLC%s Mappack is ON' % data[0])
+ else:
+ print("Invalid data, expecting 'on' or 'off'")
+
+ def cmd_palistcfg(self, data, client, cmd):
+ """
+ List available server config files in b3 conf folder
+ (You can safely use the command without the 'pa' at the beginning)
+ """
+ config_files = []
+ filenames = os.listdir(b3.getConfPath())
+ for filename in filenames:
+ if filename.endswith('.cfg'):
+ config_files.append(filename)
+
+ if not config_files:
+ client.message('No server config files found')
+ else:
+ client.message('^3Available config files are:^7 %s' % string.join(config_files, ', '))
+
+ def cmd_paload(self, data, client, cmd=None):
+ """
+ - Load a server configfile.
+ (You can safely use the command without the 'pa' at the beginning)
+ """
+ if not data:
+ client.message('^7Invalid or missing data, try !help paload')
+ return
+ if not data.endswith('.cfg'):
+ client.message('%s is not a valid server config file!' % data)
+ return
+ else:
+ if not os.path.isfile(os.path.join(b3.getConfPath(), data)):
+ client.message("Cannot find file %s in B3 config folder" % data)
+ else:
+ self.info("Config loader thread is starting")
+ client.message("^1Loading %s. This may take a while depending on the file size, please wait..." % data)
+ thread_configloader = threading.Thread(target=self._configloader(data))
+ thread_configloader.start()
+ client.message("^1%s successfully loaded!" % data)
+
+ ####################################################################################################################
+ # #
+ # OTHER METHODS #
+ # #
+ ####################################################################################################################
+
+ def _configloader(self, configfile):
+ """
+ Process each line of a config file
+ """
+ with open(os.path.join(b3.getConfPath(), configfile), 'r') as f:
+ for line in f:
+ if not line.startswith('//') and not line.startswith('\r\n'):
+ self.console.write(line, maxRetries=5)
+ time.sleep(1)
+
+ def isranked(self):
+ """
+ Returns true if server is ranked
+ """
+ rank = self.console.getCvar('sv_ranked')
+ return rank and rank.getInt() == 2
+
+ def isplaylist_enabled(self):
+ """
+ Returns true if playlists are enabled
+ """
+ status = self.console.getCvar('playlist_enabled')
+ if status:
+ if status.getBoolean():
+ self.debug('Playlist Enabled')
+ return True
+ else:
+ self.debug('Playlist Disabled')
+ return False
\ No newline at end of file