# # BigBrotherBot(B3) (www.bigbrotherbot.net) # Copyright (C) 2005 Michael "ThorN" Thornton # # 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 # # CHANGELOG # 04/14/2011 - 1.3.7 Courgette # * fix bug with !topstats and !topxp # 04/13/2011 - 1.3.6 Courgette # * import missing string module # 10/30/2010 - 1.3.5 GrosBedo # * added show_awards and show_awards_xp # 10/20/2010 - 1.3.4 GrosBedo # * clientKill and clientDamage separated from clientKillTeam and clientDamageTeam # 10/20/2010 - 1.3.3 GrosBedo # * No more teamKills if team is unknown (eg: parser can't detect the team) # 8/15/2010 - 1.3.2 GrosBedo # * Fixed disabling reset xp option # 8/14/2010 - 1.3.1 Courgette # * move commands in the commands section of config # * allow to define aliases in config # * add automated tests # 8/14/2010 - 1.3.0 GrosBedo # * Stats are now cleared at the beginning of next round (so they are still available at scoreboard) # * Moved the parameters to the xml config file (and added more) # * Added XP score and !topxp # * Setting to enable/disable score reset at round start # 9/5/2005 - 1.2.0 - ThorN # * Added !topstats command # 8/29/2005 - 1.1.0 - ThorN # * Converted to use new event handlers __author__ = 'ThorN, GrosBedo' __version__ = '1.3.7' import string import b3 import b3.events import b3.plugin #-------------------------------------------------------------------------------------------------- class StatsPlugin(b3.plugin.Plugin): _adminPlugin = None def onLoadConfig(self): try: self.mapstatslevel = self.config.getint('commands', 'mapstats') except: self.mapstatslevel = 0 self.debug('Using default value (%i) for commands::mapstats', self.mapstatslevel) try: self.testscorelevel = self.config.getint('commands', 'testscore') except: self.testscorelevel = 0 self.debug('Using default value (%i) for commands::testscore', self.testscorelevel) try: self.topstatslevel = self.config.getint('commands', 'topstats') except: self.topstatslevel = 2 self.debug('Using default value (%i) for commands::topstats', self.topstatslevel) try: self.topxplevel = self.config.getint('commands', 'topxp') except: self.topxplevel = 2 self.debug('Using default value (%i) for commands::topxp', self.topxplevel) try: self.startPoints = self.config.getint('settings', 'startPoints') except: self.startPoints = 100 self.debug('Using default value (%i) for settings::startPoints', self.startPoints) try: self.resetscore = self.config.getboolean('settings', 'resetscore') except: self.resetscore = False self.debug('Using default value (%s) for settings::resetscore', self.resetscore) try: self.resetxp = self.config.getboolean('settings', 'resetxp') except: self.resetxp = False self.debug('Using default value (%s) for settings::resetxp', self.resetxp) try: self.show_awards = self.config.getboolean('settings', 'show_awards') except: self.show_awards = False self.debug('Using default value (%s) for settings::show_awards', self.show_awards) try: self.show_awards_xp = self.config.getboolean('settings', 'show_awards_xp') except: self.show_awards_xp = False self.debug('Using default value (%s) for settings::show_awards_xp', self.show_awards_xp) def onStartup(self): self._adminPlugin = self.console.getPlugin('admin') if not self._adminPlugin: self.critical('Cannot find the admin plugin. Disabling Stats plugin') self.disable() return False # 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 = self.getCmd(cmd) if func: self._adminPlugin.registerCommand(self, cmd, level, func, alias) self.registerEvent(b3.events.EVT_CLIENT_DAMAGE_TEAM) self.registerEvent(b3.events.EVT_CLIENT_KILL_TEAM) self.registerEvent(b3.events.EVT_CLIENT_KILL) self.registerEvent(b3.events.EVT_CLIENT_DAMAGE) #self.registerEvent(b3.events.EVT_CLIENT_DISCONNECT) self.registerEvent(b3.events.EVT_GAME_EXIT) # used to show awards at the end of round #self.registerEvent(b3.events.EVT_GAME_ROUND_END) # used to show awards at the end of round self.registerEvent(b3.events.EVT_GAME_ROUND_START) # better to reinit stats at round start than round end, so that players can still query their stats at the end def onEvent(self, event): if event.type == b3.events.EVT_GAME_EXIT: if self.show_awards: self.cmd_topstats(None) if self.show_awards_xp: self.cmd_topxp(None) elif event.type == b3.events.EVT_GAME_ROUND_START: self.debug('Map Start: clearing stats') for cid, c in self.console.clients.items(): if c.maxLevel >= self.mapstatslevel: try: c.setvar(self, 'shotsTeamHit', 0) c.setvar(self, 'damageTeamHit', 0) c.setvar(self, 'shotsHit', 0) c.setvar(self, 'damageHit', 0) c.setvar(self, 'shotsGot', 0) c.setvar(self, 'damageGot', 0) c.setvar(self, 'teamKills', 0) c.setvar(self, 'kills', 0) c.setvar(self, 'deaths', 0) if self.resetscore: # skill points are reset at the beginning of each map c.setvar(self, 'pointsLost', 0) c.setvar(self, 'pointsWon', 0) c.setvar(self, 'points', self.startPoints) if self.resetxp: c.setvar(self, 'experience', 0) else: c.var(self, 'oldexperience', 0).value += c.var(self, 'experience', 0).value c.setvar(self, 'experience', 0) except: pass elif event.client: if event.type == b3.events.EVT_CLIENT_DAMAGE: self.clientDamage(event.client, event.target, int(event.data[0])) elif event.type == b3.events.EVT_CLIENT_DAMAGE_TEAM: self.clientDamageTeam(event.client, event.target, int(event.data[0])) elif event.type == b3.events.EVT_CLIENT_KILL: self.clientKill(event.client, event.target, int(event.data[0])) elif event.type == b3.events.EVT_CLIENT_KILL_TEAM: self.clientKillTeam(event.client, event.target, int(event.data[0])) def getCmd(self, cmd): """ return the method for a given command """ cmd = 'cmd_%s' % cmd if hasattr(self, cmd): func = getattr(self, cmd) return func return None def clientDamage(self, killer, victim, points): if points > 100: points = 100 killer.var(self, 'shotsHit', 0).value += 1 killer.var(self, 'damageHit', 0).value += points victim.var(self, 'shotsGot', 0).value += 1 victim.var(self, 'damageGot', 0).value += points return def clientDamageTeam(self, killer, victim, points): if points > 100: points = 100 killer.var(self, 'shotsTeamHit', 0).value += 1 killer.var(self, 'damageTeamHit', 0).value += points return def clientKill(self, killer, victim, points): if points > 100: points = 100 killer.var(self, 'shotsHit', 0).value += 1 killer.var(self, 'damageHit', 0).value += points victim.var(self, 'shotsGot', 0).value += 1 victim.var(self, 'damageGot', 0).value += points killer.var(self, 'kills', 0).value += 1 victim.var(self, 'deaths', 0).value += 1 score = self.score(killer, victim) killer.var(self, 'points', self.startPoints).value += score killer.var(self, 'pointsWon', 0).value += score victim.var(self, 'points', self.startPoints).value -= score victim.var(self, 'pointsLost', 0).value += score self.updateXP(killer) self.updateXP(victim) return def clientKillTeam(self, killer, victim, points): if points > 100: points = 100 killer.var(self, 'shotsTeamHit', 0).value += 1 killer.var(self, 'damageTeamHit', 0).value += points killer.var(self, 'teamKills', 0).value += 1 score = self.score(killer, victim) killer.var(self, 'points', self.startPoints).value -= score killer.var(self, 'pointsLost', 0).value += score self.updateXP(killer) self.updateXP(victim) return def updateXP(self, sclient): realpoints = sclient.var(self, 'pointsWon', 0).value - sclient.var(self, 'pointsLost', 0).value if sclient.var(self, 'deaths', 0).value != 0: experience = (sclient.var(self, 'kills', 0).value * realpoints) / sclient.var(self, 'deaths', 0).value else: experience = sclient.var(self, 'kills', 0).value * realpoints sclient.var(self, 'experience', 0).value = experience def cmd_mapstats(self, data, client, cmd=None): """\ [<name>] - list a players stats for the map """ if data: sclient = self._adminPlugin.findClientPrompt(data, client) if not sclient: return else: sclient = client message = '^3Stats ^7[ %s ^7] K ^2%s ^7D ^3%s ^7TK ^1%s ^7Dmg ^5%s ^7Skill ^3%1.02f ^7XP ^6%s' % (sclient.exactName, sclient.var(self, 'kills', 0).value, sclient.var(self, 'deaths', 0).value, sclient.var(self, 'teamKills', 0).value, sclient.var(self, 'damageHit', 0).value, round(sclient.var(self, 'points', self.startPoints).value, 2), round(sclient.var(self, 'oldexperience', 0).value + sclient.var(self, 'experience', 0).value, 2)) cmd.sayLoudOrPM(client, message) def cmd_testscore(self, data, client, cmd=None): """\ <name> - how much skill points you will get if you kill the player """ if not data: client.message('^7You must supply a player name to test') return sclient = self._adminPlugin.findClientPrompt(data, client) if not sclient: return elif sclient == client: client.message('^7You don\'t get points for killing yourself') return cmd.sayLoudOrPM(client, '^3Stats: ^7%s^7 will get ^3%s ^7skill points for killing %s^7' % (client.exactName, self.score(client, sclient), sclient.exactName)) def cmd_topstats(self, data, client=None, cmd=None): """\ List the top 5 map-stats players """ self.debug('Haha') scores = [] for c in self.console.clients.getList(): if c.isvar(self, 'points'): scores.append((c.exactName, round(c.var(self, 'points', self.startPoints).value, 2))) if len(scores): tmplist = [(x[1], x) for x in scores] tmplist.sort() scores = [x for (key, x) in tmplist] scores.reverse() i = 0 results = [] for name, score in scores: i += 1 if i >= 6: break results.append('^3#%s^7 %s ^7[^3%s^7]' % (i, name, score)) if client: client.message('^3Top Stats:^7 %s' % string.join(results,', ')) else: self.console.say('^3Top Stats:^7 %s' % string.join(results,', ')) else: client.message('^3Stats: ^7No top players') def cmd_topxp(self, data, client=None, cmd=None): """\ List the top 5 map-stats most experienced players """ scores = [] for c in self.console.clients.getList(): if c.isvar(self, 'experience'): scores.append((c.exactName, round(c.var(self, 'experience', self.startPoints).value, 2))) if len(scores): tmplist = [(x[1], x) for x in scores] tmplist.sort() scores = [x for (key, x) in tmplist] scores.reverse() i = 0 results = [] for name, score in scores: i += 1 if i >= 6: break results.append('^3#%s^7 %s ^7[^3%s^7]' % (i, name, score)) if client: client.message('^3Top Experienced Players:^7 %s' % string.join(results, ', ')) else: self.console.say('^3Top Experienced Players:^7 %s' % string.join(results, ', ')) else: client.message('^3Stats: ^7No top experienced players') def score(self, killer, victim): k = int(killer.var(self, 'points', self.startPoints).value) v = int(victim.var(self, 'points', self.startPoints).value) if k < 1: k = 1.00 if v < 1: v = 1.00 """ if k > v: high = k low = v else: high = v low = k vshift = float(high) / float(low) self.console.verbose('stats vshift %s' % vshift) #per = (vshift * 100) / 10 per = (vshift * 10.0) / 100.0 self.console.verbose('stats per %s' % per) if per > 100: per = 100.0 elif per < 1: per = 1.0 """ vshift = (float(v) / float(k)) / 2 self.console.verbose('stats vshift %s' % vshift) points = (15.00 * vshift) + 5 if points < 1: points = 1.00 elif points > 100: points = 100.00 return round(points, 2) """ #-------------------------------------------------------------------------------------------------- class ClientStats(DelayedSQLObject): _table = 'stats' timeAdd = IntCol(default=0) kills = IntCol(default=0) teamKills = IntCol(default=0) deaths = IntCol(default=0) score = IntCol(default=0) shotsGot = IntCol(default=0) shotsHit = IntCol(default=0) damageGot = IntCol(default=0) damageHit = IntCol(default=0) captures = IntCol(default=0) pickups = IntCol(default=0) rank = IntCol(default=0) gameName = StringCol(default='',length=3) gameType = StringCol(default='',length=3) pointsWon = IntCol(default=0) pointsLost = IntCol(default=0) playTime = IntCol(default=0) lastEventTime = 0 # client = ForeignKey('Clients.Client') def __init__(self): ClientStats.__init__(self) self.createTable(ifNotExists=True) def experiance(self): return ( self.kills + self.deaths ) / ( (self.pointsWon + self.pointsLost) / self.playTime ) def save(self): self.playTime += ( (time.time() - time.timezone) - self.lastEventTime ) / 60 #DelayedSQLObject.save(self) """ if __name__ == '__main__': """ Automated tests below """ from b3.fake import fakeConsole from b3.fake import superadmin, joe import time from b3.config import XmlConfigParser conf = XmlConfigParser() conf.setXml(""" <configuration plugin="stats"> <settings name="commands"> <set name="mapstats-mystatalias">0</set> <set name="testscore-tscr">0</set> <set name="topstats-tops">2</set> <set name="topxp-txp">2</set> </settings> <settings name="settings"> <set name="startPoints">100</set> <set name="resetscore">no</set> <set name="resetxp">no</set> </settings> </configuration> """) p = StatsPlugin(fakeConsole, conf) p.onStartup() p.onLoadConfig() time.sleep(1) joe.connects(cid=3) joe.says("!mapstats") joe.says("!mystatalias") joe.says("!testscore") joe.says("!tscr") joe.says("!topstats") joe.says("!tops") joe.says("!topxp") joe.says("!txp") superadmin.connects(cid=2) joe.kills(superadmin) joe.kills(superadmin) joe.kills(superadmin) superadmin.kills(joe) superadmin.says("!mapstats") superadmin.says("!mystatalias") superadmin.says("!testscore") superadmin.says("!tscr") superadmin.says("!topstats") superadmin.says("!tops") superadmin.says("!topxp") superadmin.says("!txp")